Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
galaxyproject
GitHub Repository: galaxyproject/training-material
Path: blob/main/assets/js/Chart.bundle.js
1677 views
1
/*!
2
* Chart.js v2.9.3
3
* https://www.chartjs.org
4
* (c) 2019 Chart.js Contributors
5
* Released under the MIT License
6
*/
7
(function (global, factory) {
8
typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() :
9
typeof define === 'function' && define.amd ? define(factory) :
10
(global = global || self, global.Chart = factory());
11
}(this, (function () { 'use strict';
12
13
var commonjsGlobal = typeof globalThis !== 'undefined' ? globalThis : typeof window !== 'undefined' ? window : typeof global !== 'undefined' ? global : typeof self !== 'undefined' ? self : {};
14
15
function commonjsRequire () {
16
throw new Error('Dynamic requires are not currently supported by rollup-plugin-commonjs');
17
}
18
19
function createCommonjsModule(fn, module) {
20
return module = { exports: {} }, fn(module, module.exports), module.exports;
21
}
22
23
function getCjsExportFromNamespace (n) {
24
return n && n['default'] || n;
25
}
26
27
var colorName = {
28
"aliceblue": [240, 248, 255],
29
"antiquewhite": [250, 235, 215],
30
"aqua": [0, 255, 255],
31
"aquamarine": [127, 255, 212],
32
"azure": [240, 255, 255],
33
"beige": [245, 245, 220],
34
"bisque": [255, 228, 196],
35
"black": [0, 0, 0],
36
"blanchedalmond": [255, 235, 205],
37
"blue": [0, 0, 255],
38
"blueviolet": [138, 43, 226],
39
"brown": [165, 42, 42],
40
"burlywood": [222, 184, 135],
41
"cadetblue": [95, 158, 160],
42
"chartreuse": [127, 255, 0],
43
"chocolate": [210, 105, 30],
44
"coral": [255, 127, 80],
45
"cornflowerblue": [100, 149, 237],
46
"cornsilk": [255, 248, 220],
47
"crimson": [220, 20, 60],
48
"cyan": [0, 255, 255],
49
"darkblue": [0, 0, 139],
50
"darkcyan": [0, 139, 139],
51
"darkgoldenrod": [184, 134, 11],
52
"darkgray": [169, 169, 169],
53
"darkgreen": [0, 100, 0],
54
"darkgrey": [169, 169, 169],
55
"darkkhaki": [189, 183, 107],
56
"darkmagenta": [139, 0, 139],
57
"darkolivegreen": [85, 107, 47],
58
"darkorange": [255, 140, 0],
59
"darkorchid": [153, 50, 204],
60
"darkred": [139, 0, 0],
61
"darksalmon": [233, 150, 122],
62
"darkseagreen": [143, 188, 143],
63
"darkslateblue": [72, 61, 139],
64
"darkslategray": [47, 79, 79],
65
"darkslategrey": [47, 79, 79],
66
"darkturquoise": [0, 206, 209],
67
"darkviolet": [148, 0, 211],
68
"deeppink": [255, 20, 147],
69
"deepskyblue": [0, 191, 255],
70
"dimgray": [105, 105, 105],
71
"dimgrey": [105, 105, 105],
72
"dodgerblue": [30, 144, 255],
73
"firebrick": [178, 34, 34],
74
"floralwhite": [255, 250, 240],
75
"forestgreen": [34, 139, 34],
76
"fuchsia": [255, 0, 255],
77
"gainsboro": [220, 220, 220],
78
"ghostwhite": [248, 248, 255],
79
"gold": [255, 215, 0],
80
"goldenrod": [218, 165, 32],
81
"gray": [128, 128, 128],
82
"green": [0, 128, 0],
83
"greenyellow": [173, 255, 47],
84
"grey": [128, 128, 128],
85
"honeydew": [240, 255, 240],
86
"hotpink": [255, 105, 180],
87
"indianred": [205, 92, 92],
88
"indigo": [75, 0, 130],
89
"ivory": [255, 255, 240],
90
"khaki": [240, 230, 140],
91
"lavender": [230, 230, 250],
92
"lavenderblush": [255, 240, 245],
93
"lawngreen": [124, 252, 0],
94
"lemonchiffon": [255, 250, 205],
95
"lightblue": [173, 216, 230],
96
"lightcoral": [240, 128, 128],
97
"lightcyan": [224, 255, 255],
98
"lightgoldenrodyellow": [250, 250, 210],
99
"lightgray": [211, 211, 211],
100
"lightgreen": [144, 238, 144],
101
"lightgrey": [211, 211, 211],
102
"lightpink": [255, 182, 193],
103
"lightsalmon": [255, 160, 122],
104
"lightseagreen": [32, 178, 170],
105
"lightskyblue": [135, 206, 250],
106
"lightslategray": [119, 136, 153],
107
"lightslategrey": [119, 136, 153],
108
"lightsteelblue": [176, 196, 222],
109
"lightyellow": [255, 255, 224],
110
"lime": [0, 255, 0],
111
"limegreen": [50, 205, 50],
112
"linen": [250, 240, 230],
113
"magenta": [255, 0, 255],
114
"maroon": [128, 0, 0],
115
"mediumaquamarine": [102, 205, 170],
116
"mediumblue": [0, 0, 205],
117
"mediumorchid": [186, 85, 211],
118
"mediumpurple": [147, 112, 219],
119
"mediumseagreen": [60, 179, 113],
120
"mediumslateblue": [123, 104, 238],
121
"mediumspringgreen": [0, 250, 154],
122
"mediumturquoise": [72, 209, 204],
123
"mediumvioletred": [199, 21, 133],
124
"midnightblue": [25, 25, 112],
125
"mintcream": [245, 255, 250],
126
"mistyrose": [255, 228, 225],
127
"moccasin": [255, 228, 181],
128
"navajowhite": [255, 222, 173],
129
"navy": [0, 0, 128],
130
"oldlace": [253, 245, 230],
131
"olive": [128, 128, 0],
132
"olivedrab": [107, 142, 35],
133
"orange": [255, 165, 0],
134
"orangered": [255, 69, 0],
135
"orchid": [218, 112, 214],
136
"palegoldenrod": [238, 232, 170],
137
"palegreen": [152, 251, 152],
138
"paleturquoise": [175, 238, 238],
139
"palevioletred": [219, 112, 147],
140
"papayawhip": [255, 239, 213],
141
"peachpuff": [255, 218, 185],
142
"peru": [205, 133, 63],
143
"pink": [255, 192, 203],
144
"plum": [221, 160, 221],
145
"powderblue": [176, 224, 230],
146
"purple": [128, 0, 128],
147
"rebeccapurple": [102, 51, 153],
148
"red": [255, 0, 0],
149
"rosybrown": [188, 143, 143],
150
"royalblue": [65, 105, 225],
151
"saddlebrown": [139, 69, 19],
152
"salmon": [250, 128, 114],
153
"sandybrown": [244, 164, 96],
154
"seagreen": [46, 139, 87],
155
"seashell": [255, 245, 238],
156
"sienna": [160, 82, 45],
157
"silver": [192, 192, 192],
158
"skyblue": [135, 206, 235],
159
"slateblue": [106, 90, 205],
160
"slategray": [112, 128, 144],
161
"slategrey": [112, 128, 144],
162
"snow": [255, 250, 250],
163
"springgreen": [0, 255, 127],
164
"steelblue": [70, 130, 180],
165
"tan": [210, 180, 140],
166
"teal": [0, 128, 128],
167
"thistle": [216, 191, 216],
168
"tomato": [255, 99, 71],
169
"turquoise": [64, 224, 208],
170
"violet": [238, 130, 238],
171
"wheat": [245, 222, 179],
172
"white": [255, 255, 255],
173
"whitesmoke": [245, 245, 245],
174
"yellow": [255, 255, 0],
175
"yellowgreen": [154, 205, 50]
176
};
177
178
var conversions = createCommonjsModule(function (module) {
179
/* MIT license */
180
181
182
// NOTE: conversions should only return primitive values (i.e. arrays, or
183
// values that give correct `typeof` results).
184
// do not use box values types (i.e. Number(), String(), etc.)
185
186
var reverseKeywords = {};
187
for (var key in colorName) {
188
if (colorName.hasOwnProperty(key)) {
189
reverseKeywords[colorName[key]] = key;
190
}
191
}
192
193
var convert = module.exports = {
194
rgb: {channels: 3, labels: 'rgb'},
195
hsl: {channels: 3, labels: 'hsl'},
196
hsv: {channels: 3, labels: 'hsv'},
197
hwb: {channels: 3, labels: 'hwb'},
198
cmyk: {channels: 4, labels: 'cmyk'},
199
xyz: {channels: 3, labels: 'xyz'},
200
lab: {channels: 3, labels: 'lab'},
201
lch: {channels: 3, labels: 'lch'},
202
hex: {channels: 1, labels: ['hex']},
203
keyword: {channels: 1, labels: ['keyword']},
204
ansi16: {channels: 1, labels: ['ansi16']},
205
ansi256: {channels: 1, labels: ['ansi256']},
206
hcg: {channels: 3, labels: ['h', 'c', 'g']},
207
apple: {channels: 3, labels: ['r16', 'g16', 'b16']},
208
gray: {channels: 1, labels: ['gray']}
209
};
210
211
// hide .channels and .labels properties
212
for (var model in convert) {
213
if (convert.hasOwnProperty(model)) {
214
if (!('channels' in convert[model])) {
215
throw new Error('missing channels property: ' + model);
216
}
217
218
if (!('labels' in convert[model])) {
219
throw new Error('missing channel labels property: ' + model);
220
}
221
222
if (convert[model].labels.length !== convert[model].channels) {
223
throw new Error('channel and label counts mismatch: ' + model);
224
}
225
226
var channels = convert[model].channels;
227
var labels = convert[model].labels;
228
delete convert[model].channels;
229
delete convert[model].labels;
230
Object.defineProperty(convert[model], 'channels', {value: channels});
231
Object.defineProperty(convert[model], 'labels', {value: labels});
232
}
233
}
234
235
convert.rgb.hsl = function (rgb) {
236
var r = rgb[0] / 255;
237
var g = rgb[1] / 255;
238
var b = rgb[2] / 255;
239
var min = Math.min(r, g, b);
240
var max = Math.max(r, g, b);
241
var delta = max - min;
242
var h;
243
var s;
244
var l;
245
246
if (max === min) {
247
h = 0;
248
} else if (r === max) {
249
h = (g - b) / delta;
250
} else if (g === max) {
251
h = 2 + (b - r) / delta;
252
} else if (b === max) {
253
h = 4 + (r - g) / delta;
254
}
255
256
h = Math.min(h * 60, 360);
257
258
if (h < 0) {
259
h += 360;
260
}
261
262
l = (min + max) / 2;
263
264
if (max === min) {
265
s = 0;
266
} else if (l <= 0.5) {
267
s = delta / (max + min);
268
} else {
269
s = delta / (2 - max - min);
270
}
271
272
return [h, s * 100, l * 100];
273
};
274
275
convert.rgb.hsv = function (rgb) {
276
var rdif;
277
var gdif;
278
var bdif;
279
var h;
280
var s;
281
282
var r = rgb[0] / 255;
283
var g = rgb[1] / 255;
284
var b = rgb[2] / 255;
285
var v = Math.max(r, g, b);
286
var diff = v - Math.min(r, g, b);
287
var diffc = function (c) {
288
return (v - c) / 6 / diff + 1 / 2;
289
};
290
291
if (diff === 0) {
292
h = s = 0;
293
} else {
294
s = diff / v;
295
rdif = diffc(r);
296
gdif = diffc(g);
297
bdif = diffc(b);
298
299
if (r === v) {
300
h = bdif - gdif;
301
} else if (g === v) {
302
h = (1 / 3) + rdif - bdif;
303
} else if (b === v) {
304
h = (2 / 3) + gdif - rdif;
305
}
306
if (h < 0) {
307
h += 1;
308
} else if (h > 1) {
309
h -= 1;
310
}
311
}
312
313
return [
314
h * 360,
315
s * 100,
316
v * 100
317
];
318
};
319
320
convert.rgb.hwb = function (rgb) {
321
var r = rgb[0];
322
var g = rgb[1];
323
var b = rgb[2];
324
var h = convert.rgb.hsl(rgb)[0];
325
var w = 1 / 255 * Math.min(r, Math.min(g, b));
326
327
b = 1 - 1 / 255 * Math.max(r, Math.max(g, b));
328
329
return [h, w * 100, b * 100];
330
};
331
332
convert.rgb.cmyk = function (rgb) {
333
var r = rgb[0] / 255;
334
var g = rgb[1] / 255;
335
var b = rgb[2] / 255;
336
var c;
337
var m;
338
var y;
339
var k;
340
341
k = Math.min(1 - r, 1 - g, 1 - b);
342
c = (1 - r - k) / (1 - k) || 0;
343
m = (1 - g - k) / (1 - k) || 0;
344
y = (1 - b - k) / (1 - k) || 0;
345
346
return [c * 100, m * 100, y * 100, k * 100];
347
};
348
349
/**
350
* See https://en.m.wikipedia.org/wiki/Euclidean_distance#Squared_Euclidean_distance
351
* */
352
function comparativeDistance(x, y) {
353
return (
354
Math.pow(x[0] - y[0], 2) +
355
Math.pow(x[1] - y[1], 2) +
356
Math.pow(x[2] - y[2], 2)
357
);
358
}
359
360
convert.rgb.keyword = function (rgb) {
361
var reversed = reverseKeywords[rgb];
362
if (reversed) {
363
return reversed;
364
}
365
366
var currentClosestDistance = Infinity;
367
var currentClosestKeyword;
368
369
for (var keyword in colorName) {
370
if (colorName.hasOwnProperty(keyword)) {
371
var value = colorName[keyword];
372
373
// Compute comparative distance
374
var distance = comparativeDistance(rgb, value);
375
376
// Check if its less, if so set as closest
377
if (distance < currentClosestDistance) {
378
currentClosestDistance = distance;
379
currentClosestKeyword = keyword;
380
}
381
}
382
}
383
384
return currentClosestKeyword;
385
};
386
387
convert.keyword.rgb = function (keyword) {
388
return colorName[keyword];
389
};
390
391
convert.rgb.xyz = function (rgb) {
392
var r = rgb[0] / 255;
393
var g = rgb[1] / 255;
394
var b = rgb[2] / 255;
395
396
// assume sRGB
397
r = r > 0.04045 ? Math.pow(((r + 0.055) / 1.055), 2.4) : (r / 12.92);
398
g = g > 0.04045 ? Math.pow(((g + 0.055) / 1.055), 2.4) : (g / 12.92);
399
b = b > 0.04045 ? Math.pow(((b + 0.055) / 1.055), 2.4) : (b / 12.92);
400
401
var x = (r * 0.4124) + (g * 0.3576) + (b * 0.1805);
402
var y = (r * 0.2126) + (g * 0.7152) + (b * 0.0722);
403
var z = (r * 0.0193) + (g * 0.1192) + (b * 0.9505);
404
405
return [x * 100, y * 100, z * 100];
406
};
407
408
convert.rgb.lab = function (rgb) {
409
var xyz = convert.rgb.xyz(rgb);
410
var x = xyz[0];
411
var y = xyz[1];
412
var z = xyz[2];
413
var l;
414
var a;
415
var b;
416
417
x /= 95.047;
418
y /= 100;
419
z /= 108.883;
420
421
x = x > 0.008856 ? Math.pow(x, 1 / 3) : (7.787 * x) + (16 / 116);
422
y = y > 0.008856 ? Math.pow(y, 1 / 3) : (7.787 * y) + (16 / 116);
423
z = z > 0.008856 ? Math.pow(z, 1 / 3) : (7.787 * z) + (16 / 116);
424
425
l = (116 * y) - 16;
426
a = 500 * (x - y);
427
b = 200 * (y - z);
428
429
return [l, a, b];
430
};
431
432
convert.hsl.rgb = function (hsl) {
433
var h = hsl[0] / 360;
434
var s = hsl[1] / 100;
435
var l = hsl[2] / 100;
436
var t1;
437
var t2;
438
var t3;
439
var rgb;
440
var val;
441
442
if (s === 0) {
443
val = l * 255;
444
return [val, val, val];
445
}
446
447
if (l < 0.5) {
448
t2 = l * (1 + s);
449
} else {
450
t2 = l + s - l * s;
451
}
452
453
t1 = 2 * l - t2;
454
455
rgb = [0, 0, 0];
456
for (var i = 0; i < 3; i++) {
457
t3 = h + 1 / 3 * -(i - 1);
458
if (t3 < 0) {
459
t3++;
460
}
461
if (t3 > 1) {
462
t3--;
463
}
464
465
if (6 * t3 < 1) {
466
val = t1 + (t2 - t1) * 6 * t3;
467
} else if (2 * t3 < 1) {
468
val = t2;
469
} else if (3 * t3 < 2) {
470
val = t1 + (t2 - t1) * (2 / 3 - t3) * 6;
471
} else {
472
val = t1;
473
}
474
475
rgb[i] = val * 255;
476
}
477
478
return rgb;
479
};
480
481
convert.hsl.hsv = function (hsl) {
482
var h = hsl[0];
483
var s = hsl[1] / 100;
484
var l = hsl[2] / 100;
485
var smin = s;
486
var lmin = Math.max(l, 0.01);
487
var sv;
488
var v;
489
490
l *= 2;
491
s *= (l <= 1) ? l : 2 - l;
492
smin *= lmin <= 1 ? lmin : 2 - lmin;
493
v = (l + s) / 2;
494
sv = l === 0 ? (2 * smin) / (lmin + smin) : (2 * s) / (l + s);
495
496
return [h, sv * 100, v * 100];
497
};
498
499
convert.hsv.rgb = function (hsv) {
500
var h = hsv[0] / 60;
501
var s = hsv[1] / 100;
502
var v = hsv[2] / 100;
503
var hi = Math.floor(h) % 6;
504
505
var f = h - Math.floor(h);
506
var p = 255 * v * (1 - s);
507
var q = 255 * v * (1 - (s * f));
508
var t = 255 * v * (1 - (s * (1 - f)));
509
v *= 255;
510
511
switch (hi) {
512
case 0:
513
return [v, t, p];
514
case 1:
515
return [q, v, p];
516
case 2:
517
return [p, v, t];
518
case 3:
519
return [p, q, v];
520
case 4:
521
return [t, p, v];
522
case 5:
523
return [v, p, q];
524
}
525
};
526
527
convert.hsv.hsl = function (hsv) {
528
var h = hsv[0];
529
var s = hsv[1] / 100;
530
var v = hsv[2] / 100;
531
var vmin = Math.max(v, 0.01);
532
var lmin;
533
var sl;
534
var l;
535
536
l = (2 - s) * v;
537
lmin = (2 - s) * vmin;
538
sl = s * vmin;
539
sl /= (lmin <= 1) ? lmin : 2 - lmin;
540
sl = sl || 0;
541
l /= 2;
542
543
return [h, sl * 100, l * 100];
544
};
545
546
// http://dev.w3.org/csswg/css-color/#hwb-to-rgb
547
convert.hwb.rgb = function (hwb) {
548
var h = hwb[0] / 360;
549
var wh = hwb[1] / 100;
550
var bl = hwb[2] / 100;
551
var ratio = wh + bl;
552
var i;
553
var v;
554
var f;
555
var n;
556
557
// wh + bl cant be > 1
558
if (ratio > 1) {
559
wh /= ratio;
560
bl /= ratio;
561
}
562
563
i = Math.floor(6 * h);
564
v = 1 - bl;
565
f = 6 * h - i;
566
567
if ((i & 0x01) !== 0) {
568
f = 1 - f;
569
}
570
571
n = wh + f * (v - wh); // linear interpolation
572
573
var r;
574
var g;
575
var b;
576
switch (i) {
577
default:
578
case 6:
579
case 0: r = v; g = n; b = wh; break;
580
case 1: r = n; g = v; b = wh; break;
581
case 2: r = wh; g = v; b = n; break;
582
case 3: r = wh; g = n; b = v; break;
583
case 4: r = n; g = wh; b = v; break;
584
case 5: r = v; g = wh; b = n; break;
585
}
586
587
return [r * 255, g * 255, b * 255];
588
};
589
590
convert.cmyk.rgb = function (cmyk) {
591
var c = cmyk[0] / 100;
592
var m = cmyk[1] / 100;
593
var y = cmyk[2] / 100;
594
var k = cmyk[3] / 100;
595
var r;
596
var g;
597
var b;
598
599
r = 1 - Math.min(1, c * (1 - k) + k);
600
g = 1 - Math.min(1, m * (1 - k) + k);
601
b = 1 - Math.min(1, y * (1 - k) + k);
602
603
return [r * 255, g * 255, b * 255];
604
};
605
606
convert.xyz.rgb = function (xyz) {
607
var x = xyz[0] / 100;
608
var y = xyz[1] / 100;
609
var z = xyz[2] / 100;
610
var r;
611
var g;
612
var b;
613
614
r = (x * 3.2406) + (y * -1.5372) + (z * -0.4986);
615
g = (x * -0.9689) + (y * 1.8758) + (z * 0.0415);
616
b = (x * 0.0557) + (y * -0.2040) + (z * 1.0570);
617
618
// assume sRGB
619
r = r > 0.0031308
620
? ((1.055 * Math.pow(r, 1.0 / 2.4)) - 0.055)
621
: r * 12.92;
622
623
g = g > 0.0031308
624
? ((1.055 * Math.pow(g, 1.0 / 2.4)) - 0.055)
625
: g * 12.92;
626
627
b = b > 0.0031308
628
? ((1.055 * Math.pow(b, 1.0 / 2.4)) - 0.055)
629
: b * 12.92;
630
631
r = Math.min(Math.max(0, r), 1);
632
g = Math.min(Math.max(0, g), 1);
633
b = Math.min(Math.max(0, b), 1);
634
635
return [r * 255, g * 255, b * 255];
636
};
637
638
convert.xyz.lab = function (xyz) {
639
var x = xyz[0];
640
var y = xyz[1];
641
var z = xyz[2];
642
var l;
643
var a;
644
var b;
645
646
x /= 95.047;
647
y /= 100;
648
z /= 108.883;
649
650
x = x > 0.008856 ? Math.pow(x, 1 / 3) : (7.787 * x) + (16 / 116);
651
y = y > 0.008856 ? Math.pow(y, 1 / 3) : (7.787 * y) + (16 / 116);
652
z = z > 0.008856 ? Math.pow(z, 1 / 3) : (7.787 * z) + (16 / 116);
653
654
l = (116 * y) - 16;
655
a = 500 * (x - y);
656
b = 200 * (y - z);
657
658
return [l, a, b];
659
};
660
661
convert.lab.xyz = function (lab) {
662
var l = lab[0];
663
var a = lab[1];
664
var b = lab[2];
665
var x;
666
var y;
667
var z;
668
669
y = (l + 16) / 116;
670
x = a / 500 + y;
671
z = y - b / 200;
672
673
var y2 = Math.pow(y, 3);
674
var x2 = Math.pow(x, 3);
675
var z2 = Math.pow(z, 3);
676
y = y2 > 0.008856 ? y2 : (y - 16 / 116) / 7.787;
677
x = x2 > 0.008856 ? x2 : (x - 16 / 116) / 7.787;
678
z = z2 > 0.008856 ? z2 : (z - 16 / 116) / 7.787;
679
680
x *= 95.047;
681
y *= 100;
682
z *= 108.883;
683
684
return [x, y, z];
685
};
686
687
convert.lab.lch = function (lab) {
688
var l = lab[0];
689
var a = lab[1];
690
var b = lab[2];
691
var hr;
692
var h;
693
var c;
694
695
hr = Math.atan2(b, a);
696
h = hr * 360 / 2 / Math.PI;
697
698
if (h < 0) {
699
h += 360;
700
}
701
702
c = Math.sqrt(a * a + b * b);
703
704
return [l, c, h];
705
};
706
707
convert.lch.lab = function (lch) {
708
var l = lch[0];
709
var c = lch[1];
710
var h = lch[2];
711
var a;
712
var b;
713
var hr;
714
715
hr = h / 360 * 2 * Math.PI;
716
a = c * Math.cos(hr);
717
b = c * Math.sin(hr);
718
719
return [l, a, b];
720
};
721
722
convert.rgb.ansi16 = function (args) {
723
var r = args[0];
724
var g = args[1];
725
var b = args[2];
726
var value = 1 in arguments ? arguments[1] : convert.rgb.hsv(args)[2]; // hsv -> ansi16 optimization
727
728
value = Math.round(value / 50);
729
730
if (value === 0) {
731
return 30;
732
}
733
734
var ansi = 30
735
+ ((Math.round(b / 255) << 2)
736
| (Math.round(g / 255) << 1)
737
| Math.round(r / 255));
738
739
if (value === 2) {
740
ansi += 60;
741
}
742
743
return ansi;
744
};
745
746
convert.hsv.ansi16 = function (args) {
747
// optimization here; we already know the value and don't need to get
748
// it converted for us.
749
return convert.rgb.ansi16(convert.hsv.rgb(args), args[2]);
750
};
751
752
convert.rgb.ansi256 = function (args) {
753
var r = args[0];
754
var g = args[1];
755
var b = args[2];
756
757
// we use the extended greyscale palette here, with the exception of
758
// black and white. normal palette only has 4 greyscale shades.
759
if (r === g && g === b) {
760
if (r < 8) {
761
return 16;
762
}
763
764
if (r > 248) {
765
return 231;
766
}
767
768
return Math.round(((r - 8) / 247) * 24) + 232;
769
}
770
771
var ansi = 16
772
+ (36 * Math.round(r / 255 * 5))
773
+ (6 * Math.round(g / 255 * 5))
774
+ Math.round(b / 255 * 5);
775
776
return ansi;
777
};
778
779
convert.ansi16.rgb = function (args) {
780
var color = args % 10;
781
782
// handle greyscale
783
if (color === 0 || color === 7) {
784
if (args > 50) {
785
color += 3.5;
786
}
787
788
color = color / 10.5 * 255;
789
790
return [color, color, color];
791
}
792
793
var mult = (~~(args > 50) + 1) * 0.5;
794
var r = ((color & 1) * mult) * 255;
795
var g = (((color >> 1) & 1) * mult) * 255;
796
var b = (((color >> 2) & 1) * mult) * 255;
797
798
return [r, g, b];
799
};
800
801
convert.ansi256.rgb = function (args) {
802
// handle greyscale
803
if (args >= 232) {
804
var c = (args - 232) * 10 + 8;
805
return [c, c, c];
806
}
807
808
args -= 16;
809
810
var rem;
811
var r = Math.floor(args / 36) / 5 * 255;
812
var g = Math.floor((rem = args % 36) / 6) / 5 * 255;
813
var b = (rem % 6) / 5 * 255;
814
815
return [r, g, b];
816
};
817
818
convert.rgb.hex = function (args) {
819
var integer = ((Math.round(args[0]) & 0xFF) << 16)
820
+ ((Math.round(args[1]) & 0xFF) << 8)
821
+ (Math.round(args[2]) & 0xFF);
822
823
var string = integer.toString(16).toUpperCase();
824
return '000000'.substring(string.length) + string;
825
};
826
827
convert.hex.rgb = function (args) {
828
var match = args.toString(16).match(/[a-f0-9]{6}|[a-f0-9]{3}/i);
829
if (!match) {
830
return [0, 0, 0];
831
}
832
833
var colorString = match[0];
834
835
if (match[0].length === 3) {
836
colorString = colorString.split('').map(function (char) {
837
return char + char;
838
}).join('');
839
}
840
841
var integer = parseInt(colorString, 16);
842
var r = (integer >> 16) & 0xFF;
843
var g = (integer >> 8) & 0xFF;
844
var b = integer & 0xFF;
845
846
return [r, g, b];
847
};
848
849
convert.rgb.hcg = function (rgb) {
850
var r = rgb[0] / 255;
851
var g = rgb[1] / 255;
852
var b = rgb[2] / 255;
853
var max = Math.max(Math.max(r, g), b);
854
var min = Math.min(Math.min(r, g), b);
855
var chroma = (max - min);
856
var grayscale;
857
var hue;
858
859
if (chroma < 1) {
860
grayscale = min / (1 - chroma);
861
} else {
862
grayscale = 0;
863
}
864
865
if (chroma <= 0) {
866
hue = 0;
867
} else
868
if (max === r) {
869
hue = ((g - b) / chroma) % 6;
870
} else
871
if (max === g) {
872
hue = 2 + (b - r) / chroma;
873
} else {
874
hue = 4 + (r - g) / chroma + 4;
875
}
876
877
hue /= 6;
878
hue %= 1;
879
880
return [hue * 360, chroma * 100, grayscale * 100];
881
};
882
883
convert.hsl.hcg = function (hsl) {
884
var s = hsl[1] / 100;
885
var l = hsl[2] / 100;
886
var c = 1;
887
var f = 0;
888
889
if (l < 0.5) {
890
c = 2.0 * s * l;
891
} else {
892
c = 2.0 * s * (1.0 - l);
893
}
894
895
if (c < 1.0) {
896
f = (l - 0.5 * c) / (1.0 - c);
897
}
898
899
return [hsl[0], c * 100, f * 100];
900
};
901
902
convert.hsv.hcg = function (hsv) {
903
var s = hsv[1] / 100;
904
var v = hsv[2] / 100;
905
906
var c = s * v;
907
var f = 0;
908
909
if (c < 1.0) {
910
f = (v - c) / (1 - c);
911
}
912
913
return [hsv[0], c * 100, f * 100];
914
};
915
916
convert.hcg.rgb = function (hcg) {
917
var h = hcg[0] / 360;
918
var c = hcg[1] / 100;
919
var g = hcg[2] / 100;
920
921
if (c === 0.0) {
922
return [g * 255, g * 255, g * 255];
923
}
924
925
var pure = [0, 0, 0];
926
var hi = (h % 1) * 6;
927
var v = hi % 1;
928
var w = 1 - v;
929
var mg = 0;
930
931
switch (Math.floor(hi)) {
932
case 0:
933
pure[0] = 1; pure[1] = v; pure[2] = 0; break;
934
case 1:
935
pure[0] = w; pure[1] = 1; pure[2] = 0; break;
936
case 2:
937
pure[0] = 0; pure[1] = 1; pure[2] = v; break;
938
case 3:
939
pure[0] = 0; pure[1] = w; pure[2] = 1; break;
940
case 4:
941
pure[0] = v; pure[1] = 0; pure[2] = 1; break;
942
default:
943
pure[0] = 1; pure[1] = 0; pure[2] = w;
944
}
945
946
mg = (1.0 - c) * g;
947
948
return [
949
(c * pure[0] + mg) * 255,
950
(c * pure[1] + mg) * 255,
951
(c * pure[2] + mg) * 255
952
];
953
};
954
955
convert.hcg.hsv = function (hcg) {
956
var c = hcg[1] / 100;
957
var g = hcg[2] / 100;
958
959
var v = c + g * (1.0 - c);
960
var f = 0;
961
962
if (v > 0.0) {
963
f = c / v;
964
}
965
966
return [hcg[0], f * 100, v * 100];
967
};
968
969
convert.hcg.hsl = function (hcg) {
970
var c = hcg[1] / 100;
971
var g = hcg[2] / 100;
972
973
var l = g * (1.0 - c) + 0.5 * c;
974
var s = 0;
975
976
if (l > 0.0 && l < 0.5) {
977
s = c / (2 * l);
978
} else
979
if (l >= 0.5 && l < 1.0) {
980
s = c / (2 * (1 - l));
981
}
982
983
return [hcg[0], s * 100, l * 100];
984
};
985
986
convert.hcg.hwb = function (hcg) {
987
var c = hcg[1] / 100;
988
var g = hcg[2] / 100;
989
var v = c + g * (1.0 - c);
990
return [hcg[0], (v - c) * 100, (1 - v) * 100];
991
};
992
993
convert.hwb.hcg = function (hwb) {
994
var w = hwb[1] / 100;
995
var b = hwb[2] / 100;
996
var v = 1 - b;
997
var c = v - w;
998
var g = 0;
999
1000
if (c < 1) {
1001
g = (v - c) / (1 - c);
1002
}
1003
1004
return [hwb[0], c * 100, g * 100];
1005
};
1006
1007
convert.apple.rgb = function (apple) {
1008
return [(apple[0] / 65535) * 255, (apple[1] / 65535) * 255, (apple[2] / 65535) * 255];
1009
};
1010
1011
convert.rgb.apple = function (rgb) {
1012
return [(rgb[0] / 255) * 65535, (rgb[1] / 255) * 65535, (rgb[2] / 255) * 65535];
1013
};
1014
1015
convert.gray.rgb = function (args) {
1016
return [args[0] / 100 * 255, args[0] / 100 * 255, args[0] / 100 * 255];
1017
};
1018
1019
convert.gray.hsl = convert.gray.hsv = function (args) {
1020
return [0, 0, args[0]];
1021
};
1022
1023
convert.gray.hwb = function (gray) {
1024
return [0, 100, gray[0]];
1025
};
1026
1027
convert.gray.cmyk = function (gray) {
1028
return [0, 0, 0, gray[0]];
1029
};
1030
1031
convert.gray.lab = function (gray) {
1032
return [gray[0], 0, 0];
1033
};
1034
1035
convert.gray.hex = function (gray) {
1036
var val = Math.round(gray[0] / 100 * 255) & 0xFF;
1037
var integer = (val << 16) + (val << 8) + val;
1038
1039
var string = integer.toString(16).toUpperCase();
1040
return '000000'.substring(string.length) + string;
1041
};
1042
1043
convert.rgb.gray = function (rgb) {
1044
var val = (rgb[0] + rgb[1] + rgb[2]) / 3;
1045
return [val / 255 * 100];
1046
};
1047
});
1048
var conversions_1 = conversions.rgb;
1049
var conversions_2 = conversions.hsl;
1050
var conversions_3 = conversions.hsv;
1051
var conversions_4 = conversions.hwb;
1052
var conversions_5 = conversions.cmyk;
1053
var conversions_6 = conversions.xyz;
1054
var conversions_7 = conversions.lab;
1055
var conversions_8 = conversions.lch;
1056
var conversions_9 = conversions.hex;
1057
var conversions_10 = conversions.keyword;
1058
var conversions_11 = conversions.ansi16;
1059
var conversions_12 = conversions.ansi256;
1060
var conversions_13 = conversions.hcg;
1061
var conversions_14 = conversions.apple;
1062
var conversions_15 = conversions.gray;
1063
1064
/*
1065
this function routes a model to all other models.
1066
1067
all functions that are routed have a property `.conversion` attached
1068
to the returned synthetic function. This property is an array
1069
of strings, each with the steps in between the 'from' and 'to'
1070
color models (inclusive).
1071
1072
conversions that are not possible simply are not included.
1073
*/
1074
1075
function buildGraph() {
1076
var graph = {};
1077
// https://jsperf.com/object-keys-vs-for-in-with-closure/3
1078
var models = Object.keys(conversions);
1079
1080
for (var len = models.length, i = 0; i < len; i++) {
1081
graph[models[i]] = {
1082
// http://jsperf.com/1-vs-infinity
1083
// micro-opt, but this is simple.
1084
distance: -1,
1085
parent: null
1086
};
1087
}
1088
1089
return graph;
1090
}
1091
1092
// https://en.wikipedia.org/wiki/Breadth-first_search
1093
function deriveBFS(fromModel) {
1094
var graph = buildGraph();
1095
var queue = [fromModel]; // unshift -> queue -> pop
1096
1097
graph[fromModel].distance = 0;
1098
1099
while (queue.length) {
1100
var current = queue.pop();
1101
var adjacents = Object.keys(conversions[current]);
1102
1103
for (var len = adjacents.length, i = 0; i < len; i++) {
1104
var adjacent = adjacents[i];
1105
var node = graph[adjacent];
1106
1107
if (node.distance === -1) {
1108
node.distance = graph[current].distance + 1;
1109
node.parent = current;
1110
queue.unshift(adjacent);
1111
}
1112
}
1113
}
1114
1115
return graph;
1116
}
1117
1118
function link(from, to) {
1119
return function (args) {
1120
return to(from(args));
1121
};
1122
}
1123
1124
function wrapConversion(toModel, graph) {
1125
var path = [graph[toModel].parent, toModel];
1126
var fn = conversions[graph[toModel].parent][toModel];
1127
1128
var cur = graph[toModel].parent;
1129
while (graph[cur].parent) {
1130
path.unshift(graph[cur].parent);
1131
fn = link(conversions[graph[cur].parent][cur], fn);
1132
cur = graph[cur].parent;
1133
}
1134
1135
fn.conversion = path;
1136
return fn;
1137
}
1138
1139
var route = function (fromModel) {
1140
var graph = deriveBFS(fromModel);
1141
var conversion = {};
1142
1143
var models = Object.keys(graph);
1144
for (var len = models.length, i = 0; i < len; i++) {
1145
var toModel = models[i];
1146
var node = graph[toModel];
1147
1148
if (node.parent === null) {
1149
// no possible conversion, or this node is the source model.
1150
continue;
1151
}
1152
1153
conversion[toModel] = wrapConversion(toModel, graph);
1154
}
1155
1156
return conversion;
1157
};
1158
1159
var convert = {};
1160
1161
var models = Object.keys(conversions);
1162
1163
function wrapRaw(fn) {
1164
var wrappedFn = function (args) {
1165
if (args === undefined || args === null) {
1166
return args;
1167
}
1168
1169
if (arguments.length > 1) {
1170
args = Array.prototype.slice.call(arguments);
1171
}
1172
1173
return fn(args);
1174
};
1175
1176
// preserve .conversion property if there is one
1177
if ('conversion' in fn) {
1178
wrappedFn.conversion = fn.conversion;
1179
}
1180
1181
return wrappedFn;
1182
}
1183
1184
function wrapRounded(fn) {
1185
var wrappedFn = function (args) {
1186
if (args === undefined || args === null) {
1187
return args;
1188
}
1189
1190
if (arguments.length > 1) {
1191
args = Array.prototype.slice.call(arguments);
1192
}
1193
1194
var result = fn(args);
1195
1196
// we're assuming the result is an array here.
1197
// see notice in conversions.js; don't use box types
1198
// in conversion functions.
1199
if (typeof result === 'object') {
1200
for (var len = result.length, i = 0; i < len; i++) {
1201
result[i] = Math.round(result[i]);
1202
}
1203
}
1204
1205
return result;
1206
};
1207
1208
// preserve .conversion property if there is one
1209
if ('conversion' in fn) {
1210
wrappedFn.conversion = fn.conversion;
1211
}
1212
1213
return wrappedFn;
1214
}
1215
1216
models.forEach(function (fromModel) {
1217
convert[fromModel] = {};
1218
1219
Object.defineProperty(convert[fromModel], 'channels', {value: conversions[fromModel].channels});
1220
Object.defineProperty(convert[fromModel], 'labels', {value: conversions[fromModel].labels});
1221
1222
var routes = route(fromModel);
1223
var routeModels = Object.keys(routes);
1224
1225
routeModels.forEach(function (toModel) {
1226
var fn = routes[toModel];
1227
1228
convert[fromModel][toModel] = wrapRounded(fn);
1229
convert[fromModel][toModel].raw = wrapRaw(fn);
1230
});
1231
});
1232
1233
var colorConvert = convert;
1234
1235
var colorName$1 = {
1236
"aliceblue": [240, 248, 255],
1237
"antiquewhite": [250, 235, 215],
1238
"aqua": [0, 255, 255],
1239
"aquamarine": [127, 255, 212],
1240
"azure": [240, 255, 255],
1241
"beige": [245, 245, 220],
1242
"bisque": [255, 228, 196],
1243
"black": [0, 0, 0],
1244
"blanchedalmond": [255, 235, 205],
1245
"blue": [0, 0, 255],
1246
"blueviolet": [138, 43, 226],
1247
"brown": [165, 42, 42],
1248
"burlywood": [222, 184, 135],
1249
"cadetblue": [95, 158, 160],
1250
"chartreuse": [127, 255, 0],
1251
"chocolate": [210, 105, 30],
1252
"coral": [255, 127, 80],
1253
"cornflowerblue": [100, 149, 237],
1254
"cornsilk": [255, 248, 220],
1255
"crimson": [220, 20, 60],
1256
"cyan": [0, 255, 255],
1257
"darkblue": [0, 0, 139],
1258
"darkcyan": [0, 139, 139],
1259
"darkgoldenrod": [184, 134, 11],
1260
"darkgray": [169, 169, 169],
1261
"darkgreen": [0, 100, 0],
1262
"darkgrey": [169, 169, 169],
1263
"darkkhaki": [189, 183, 107],
1264
"darkmagenta": [139, 0, 139],
1265
"darkolivegreen": [85, 107, 47],
1266
"darkorange": [255, 140, 0],
1267
"darkorchid": [153, 50, 204],
1268
"darkred": [139, 0, 0],
1269
"darksalmon": [233, 150, 122],
1270
"darkseagreen": [143, 188, 143],
1271
"darkslateblue": [72, 61, 139],
1272
"darkslategray": [47, 79, 79],
1273
"darkslategrey": [47, 79, 79],
1274
"darkturquoise": [0, 206, 209],
1275
"darkviolet": [148, 0, 211],
1276
"deeppink": [255, 20, 147],
1277
"deepskyblue": [0, 191, 255],
1278
"dimgray": [105, 105, 105],
1279
"dimgrey": [105, 105, 105],
1280
"dodgerblue": [30, 144, 255],
1281
"firebrick": [178, 34, 34],
1282
"floralwhite": [255, 250, 240],
1283
"forestgreen": [34, 139, 34],
1284
"fuchsia": [255, 0, 255],
1285
"gainsboro": [220, 220, 220],
1286
"ghostwhite": [248, 248, 255],
1287
"gold": [255, 215, 0],
1288
"goldenrod": [218, 165, 32],
1289
"gray": [128, 128, 128],
1290
"green": [0, 128, 0],
1291
"greenyellow": [173, 255, 47],
1292
"grey": [128, 128, 128],
1293
"honeydew": [240, 255, 240],
1294
"hotpink": [255, 105, 180],
1295
"indianred": [205, 92, 92],
1296
"indigo": [75, 0, 130],
1297
"ivory": [255, 255, 240],
1298
"khaki": [240, 230, 140],
1299
"lavender": [230, 230, 250],
1300
"lavenderblush": [255, 240, 245],
1301
"lawngreen": [124, 252, 0],
1302
"lemonchiffon": [255, 250, 205],
1303
"lightblue": [173, 216, 230],
1304
"lightcoral": [240, 128, 128],
1305
"lightcyan": [224, 255, 255],
1306
"lightgoldenrodyellow": [250, 250, 210],
1307
"lightgray": [211, 211, 211],
1308
"lightgreen": [144, 238, 144],
1309
"lightgrey": [211, 211, 211],
1310
"lightpink": [255, 182, 193],
1311
"lightsalmon": [255, 160, 122],
1312
"lightseagreen": [32, 178, 170],
1313
"lightskyblue": [135, 206, 250],
1314
"lightslategray": [119, 136, 153],
1315
"lightslategrey": [119, 136, 153],
1316
"lightsteelblue": [176, 196, 222],
1317
"lightyellow": [255, 255, 224],
1318
"lime": [0, 255, 0],
1319
"limegreen": [50, 205, 50],
1320
"linen": [250, 240, 230],
1321
"magenta": [255, 0, 255],
1322
"maroon": [128, 0, 0],
1323
"mediumaquamarine": [102, 205, 170],
1324
"mediumblue": [0, 0, 205],
1325
"mediumorchid": [186, 85, 211],
1326
"mediumpurple": [147, 112, 219],
1327
"mediumseagreen": [60, 179, 113],
1328
"mediumslateblue": [123, 104, 238],
1329
"mediumspringgreen": [0, 250, 154],
1330
"mediumturquoise": [72, 209, 204],
1331
"mediumvioletred": [199, 21, 133],
1332
"midnightblue": [25, 25, 112],
1333
"mintcream": [245, 255, 250],
1334
"mistyrose": [255, 228, 225],
1335
"moccasin": [255, 228, 181],
1336
"navajowhite": [255, 222, 173],
1337
"navy": [0, 0, 128],
1338
"oldlace": [253, 245, 230],
1339
"olive": [128, 128, 0],
1340
"olivedrab": [107, 142, 35],
1341
"orange": [255, 165, 0],
1342
"orangered": [255, 69, 0],
1343
"orchid": [218, 112, 214],
1344
"palegoldenrod": [238, 232, 170],
1345
"palegreen": [152, 251, 152],
1346
"paleturquoise": [175, 238, 238],
1347
"palevioletred": [219, 112, 147],
1348
"papayawhip": [255, 239, 213],
1349
"peachpuff": [255, 218, 185],
1350
"peru": [205, 133, 63],
1351
"pink": [255, 192, 203],
1352
"plum": [221, 160, 221],
1353
"powderblue": [176, 224, 230],
1354
"purple": [128, 0, 128],
1355
"rebeccapurple": [102, 51, 153],
1356
"red": [255, 0, 0],
1357
"rosybrown": [188, 143, 143],
1358
"royalblue": [65, 105, 225],
1359
"saddlebrown": [139, 69, 19],
1360
"salmon": [250, 128, 114],
1361
"sandybrown": [244, 164, 96],
1362
"seagreen": [46, 139, 87],
1363
"seashell": [255, 245, 238],
1364
"sienna": [160, 82, 45],
1365
"silver": [192, 192, 192],
1366
"skyblue": [135, 206, 235],
1367
"slateblue": [106, 90, 205],
1368
"slategray": [112, 128, 144],
1369
"slategrey": [112, 128, 144],
1370
"snow": [255, 250, 250],
1371
"springgreen": [0, 255, 127],
1372
"steelblue": [70, 130, 180],
1373
"tan": [210, 180, 140],
1374
"teal": [0, 128, 128],
1375
"thistle": [216, 191, 216],
1376
"tomato": [255, 99, 71],
1377
"turquoise": [64, 224, 208],
1378
"violet": [238, 130, 238],
1379
"wheat": [245, 222, 179],
1380
"white": [255, 255, 255],
1381
"whitesmoke": [245, 245, 245],
1382
"yellow": [255, 255, 0],
1383
"yellowgreen": [154, 205, 50]
1384
};
1385
1386
/* MIT license */
1387
1388
1389
var colorString = {
1390
getRgba: getRgba,
1391
getHsla: getHsla,
1392
getRgb: getRgb,
1393
getHsl: getHsl,
1394
getHwb: getHwb,
1395
getAlpha: getAlpha,
1396
1397
hexString: hexString,
1398
rgbString: rgbString,
1399
rgbaString: rgbaString,
1400
percentString: percentString,
1401
percentaString: percentaString,
1402
hslString: hslString,
1403
hslaString: hslaString,
1404
hwbString: hwbString,
1405
keyword: keyword
1406
};
1407
1408
function getRgba(string) {
1409
if (!string) {
1410
return;
1411
}
1412
var abbr = /^#([a-fA-F0-9]{3,4})$/i,
1413
hex = /^#([a-fA-F0-9]{6}([a-fA-F0-9]{2})?)$/i,
1414
rgba = /^rgba?\(\s*([+-]?\d+)\s*,\s*([+-]?\d+)\s*,\s*([+-]?\d+)\s*(?:,\s*([+-]?[\d\.]+)\s*)?\)$/i,
1415
per = /^rgba?\(\s*([+-]?[\d\.]+)\%\s*,\s*([+-]?[\d\.]+)\%\s*,\s*([+-]?[\d\.]+)\%\s*(?:,\s*([+-]?[\d\.]+)\s*)?\)$/i,
1416
keyword = /(\w+)/;
1417
1418
var rgb = [0, 0, 0],
1419
a = 1,
1420
match = string.match(abbr),
1421
hexAlpha = "";
1422
if (match) {
1423
match = match[1];
1424
hexAlpha = match[3];
1425
for (var i = 0; i < rgb.length; i++) {
1426
rgb[i] = parseInt(match[i] + match[i], 16);
1427
}
1428
if (hexAlpha) {
1429
a = Math.round((parseInt(hexAlpha + hexAlpha, 16) / 255) * 100) / 100;
1430
}
1431
}
1432
else if (match = string.match(hex)) {
1433
hexAlpha = match[2];
1434
match = match[1];
1435
for (var i = 0; i < rgb.length; i++) {
1436
rgb[i] = parseInt(match.slice(i * 2, i * 2 + 2), 16);
1437
}
1438
if (hexAlpha) {
1439
a = Math.round((parseInt(hexAlpha, 16) / 255) * 100) / 100;
1440
}
1441
}
1442
else if (match = string.match(rgba)) {
1443
for (var i = 0; i < rgb.length; i++) {
1444
rgb[i] = parseInt(match[i + 1]);
1445
}
1446
a = parseFloat(match[4]);
1447
}
1448
else if (match = string.match(per)) {
1449
for (var i = 0; i < rgb.length; i++) {
1450
rgb[i] = Math.round(parseFloat(match[i + 1]) * 2.55);
1451
}
1452
a = parseFloat(match[4]);
1453
}
1454
else if (match = string.match(keyword)) {
1455
if (match[1] == "transparent") {
1456
return [0, 0, 0, 0];
1457
}
1458
rgb = colorName$1[match[1]];
1459
if (!rgb) {
1460
return;
1461
}
1462
}
1463
1464
for (var i = 0; i < rgb.length; i++) {
1465
rgb[i] = scale(rgb[i], 0, 255);
1466
}
1467
if (!a && a != 0) {
1468
a = 1;
1469
}
1470
else {
1471
a = scale(a, 0, 1);
1472
}
1473
rgb[3] = a;
1474
return rgb;
1475
}
1476
1477
function getHsla(string) {
1478
if (!string) {
1479
return;
1480
}
1481
var hsl = /^hsla?\(\s*([+-]?\d+)(?:deg)?\s*,\s*([+-]?[\d\.]+)%\s*,\s*([+-]?[\d\.]+)%\s*(?:,\s*([+-]?[\d\.]+)\s*)?\)/;
1482
var match = string.match(hsl);
1483
if (match) {
1484
var alpha = parseFloat(match[4]);
1485
var h = scale(parseInt(match[1]), 0, 360),
1486
s = scale(parseFloat(match[2]), 0, 100),
1487
l = scale(parseFloat(match[3]), 0, 100),
1488
a = scale(isNaN(alpha) ? 1 : alpha, 0, 1);
1489
return [h, s, l, a];
1490
}
1491
}
1492
1493
function getHwb(string) {
1494
if (!string) {
1495
return;
1496
}
1497
var hwb = /^hwb\(\s*([+-]?\d+)(?:deg)?\s*,\s*([+-]?[\d\.]+)%\s*,\s*([+-]?[\d\.]+)%\s*(?:,\s*([+-]?[\d\.]+)\s*)?\)/;
1498
var match = string.match(hwb);
1499
if (match) {
1500
var alpha = parseFloat(match[4]);
1501
var h = scale(parseInt(match[1]), 0, 360),
1502
w = scale(parseFloat(match[2]), 0, 100),
1503
b = scale(parseFloat(match[3]), 0, 100),
1504
a = scale(isNaN(alpha) ? 1 : alpha, 0, 1);
1505
return [h, w, b, a];
1506
}
1507
}
1508
1509
function getRgb(string) {
1510
var rgba = getRgba(string);
1511
return rgba && rgba.slice(0, 3);
1512
}
1513
1514
function getHsl(string) {
1515
var hsla = getHsla(string);
1516
return hsla && hsla.slice(0, 3);
1517
}
1518
1519
function getAlpha(string) {
1520
var vals = getRgba(string);
1521
if (vals) {
1522
return vals[3];
1523
}
1524
else if (vals = getHsla(string)) {
1525
return vals[3];
1526
}
1527
else if (vals = getHwb(string)) {
1528
return vals[3];
1529
}
1530
}
1531
1532
// generators
1533
function hexString(rgba, a) {
1534
var a = (a !== undefined && rgba.length === 3) ? a : rgba[3];
1535
return "#" + hexDouble(rgba[0])
1536
+ hexDouble(rgba[1])
1537
+ hexDouble(rgba[2])
1538
+ (
1539
(a >= 0 && a < 1)
1540
? hexDouble(Math.round(a * 255))
1541
: ""
1542
);
1543
}
1544
1545
function rgbString(rgba, alpha) {
1546
if (alpha < 1 || (rgba[3] && rgba[3] < 1)) {
1547
return rgbaString(rgba, alpha);
1548
}
1549
return "rgb(" + rgba[0] + ", " + rgba[1] + ", " + rgba[2] + ")";
1550
}
1551
1552
function rgbaString(rgba, alpha) {
1553
if (alpha === undefined) {
1554
alpha = (rgba[3] !== undefined ? rgba[3] : 1);
1555
}
1556
return "rgba(" + rgba[0] + ", " + rgba[1] + ", " + rgba[2]
1557
+ ", " + alpha + ")";
1558
}
1559
1560
function percentString(rgba, alpha) {
1561
if (alpha < 1 || (rgba[3] && rgba[3] < 1)) {
1562
return percentaString(rgba, alpha);
1563
}
1564
var r = Math.round(rgba[0]/255 * 100),
1565
g = Math.round(rgba[1]/255 * 100),
1566
b = Math.round(rgba[2]/255 * 100);
1567
1568
return "rgb(" + r + "%, " + g + "%, " + b + "%)";
1569
}
1570
1571
function percentaString(rgba, alpha) {
1572
var r = Math.round(rgba[0]/255 * 100),
1573
g = Math.round(rgba[1]/255 * 100),
1574
b = Math.round(rgba[2]/255 * 100);
1575
return "rgba(" + r + "%, " + g + "%, " + b + "%, " + (alpha || rgba[3] || 1) + ")";
1576
}
1577
1578
function hslString(hsla, alpha) {
1579
if (alpha < 1 || (hsla[3] && hsla[3] < 1)) {
1580
return hslaString(hsla, alpha);
1581
}
1582
return "hsl(" + hsla[0] + ", " + hsla[1] + "%, " + hsla[2] + "%)";
1583
}
1584
1585
function hslaString(hsla, alpha) {
1586
if (alpha === undefined) {
1587
alpha = (hsla[3] !== undefined ? hsla[3] : 1);
1588
}
1589
return "hsla(" + hsla[0] + ", " + hsla[1] + "%, " + hsla[2] + "%, "
1590
+ alpha + ")";
1591
}
1592
1593
// hwb is a bit different than rgb(a) & hsl(a) since there is no alpha specific syntax
1594
// (hwb have alpha optional & 1 is default value)
1595
function hwbString(hwb, alpha) {
1596
if (alpha === undefined) {
1597
alpha = (hwb[3] !== undefined ? hwb[3] : 1);
1598
}
1599
return "hwb(" + hwb[0] + ", " + hwb[1] + "%, " + hwb[2] + "%"
1600
+ (alpha !== undefined && alpha !== 1 ? ", " + alpha : "") + ")";
1601
}
1602
1603
function keyword(rgb) {
1604
return reverseNames[rgb.slice(0, 3)];
1605
}
1606
1607
// helpers
1608
function scale(num, min, max) {
1609
return Math.min(Math.max(min, num), max);
1610
}
1611
1612
function hexDouble(num) {
1613
var str = num.toString(16).toUpperCase();
1614
return (str.length < 2) ? "0" + str : str;
1615
}
1616
1617
1618
//create a list of reverse color names
1619
var reverseNames = {};
1620
for (var name in colorName$1) {
1621
reverseNames[colorName$1[name]] = name;
1622
}
1623
1624
/* MIT license */
1625
1626
1627
1628
var Color = function (obj) {
1629
if (obj instanceof Color) {
1630
return obj;
1631
}
1632
if (!(this instanceof Color)) {
1633
return new Color(obj);
1634
}
1635
1636
this.valid = false;
1637
this.values = {
1638
rgb: [0, 0, 0],
1639
hsl: [0, 0, 0],
1640
hsv: [0, 0, 0],
1641
hwb: [0, 0, 0],
1642
cmyk: [0, 0, 0, 0],
1643
alpha: 1
1644
};
1645
1646
// parse Color() argument
1647
var vals;
1648
if (typeof obj === 'string') {
1649
vals = colorString.getRgba(obj);
1650
if (vals) {
1651
this.setValues('rgb', vals);
1652
} else if (vals = colorString.getHsla(obj)) {
1653
this.setValues('hsl', vals);
1654
} else if (vals = colorString.getHwb(obj)) {
1655
this.setValues('hwb', vals);
1656
}
1657
} else if (typeof obj === 'object') {
1658
vals = obj;
1659
if (vals.r !== undefined || vals.red !== undefined) {
1660
this.setValues('rgb', vals);
1661
} else if (vals.l !== undefined || vals.lightness !== undefined) {
1662
this.setValues('hsl', vals);
1663
} else if (vals.v !== undefined || vals.value !== undefined) {
1664
this.setValues('hsv', vals);
1665
} else if (vals.w !== undefined || vals.whiteness !== undefined) {
1666
this.setValues('hwb', vals);
1667
} else if (vals.c !== undefined || vals.cyan !== undefined) {
1668
this.setValues('cmyk', vals);
1669
}
1670
}
1671
};
1672
1673
Color.prototype = {
1674
isValid: function () {
1675
return this.valid;
1676
},
1677
rgb: function () {
1678
return this.setSpace('rgb', arguments);
1679
},
1680
hsl: function () {
1681
return this.setSpace('hsl', arguments);
1682
},
1683
hsv: function () {
1684
return this.setSpace('hsv', arguments);
1685
},
1686
hwb: function () {
1687
return this.setSpace('hwb', arguments);
1688
},
1689
cmyk: function () {
1690
return this.setSpace('cmyk', arguments);
1691
},
1692
1693
rgbArray: function () {
1694
return this.values.rgb;
1695
},
1696
hslArray: function () {
1697
return this.values.hsl;
1698
},
1699
hsvArray: function () {
1700
return this.values.hsv;
1701
},
1702
hwbArray: function () {
1703
var values = this.values;
1704
if (values.alpha !== 1) {
1705
return values.hwb.concat([values.alpha]);
1706
}
1707
return values.hwb;
1708
},
1709
cmykArray: function () {
1710
return this.values.cmyk;
1711
},
1712
rgbaArray: function () {
1713
var values = this.values;
1714
return values.rgb.concat([values.alpha]);
1715
},
1716
hslaArray: function () {
1717
var values = this.values;
1718
return values.hsl.concat([values.alpha]);
1719
},
1720
alpha: function (val) {
1721
if (val === undefined) {
1722
return this.values.alpha;
1723
}
1724
this.setValues('alpha', val);
1725
return this;
1726
},
1727
1728
red: function (val) {
1729
return this.setChannel('rgb', 0, val);
1730
},
1731
green: function (val) {
1732
return this.setChannel('rgb', 1, val);
1733
},
1734
blue: function (val) {
1735
return this.setChannel('rgb', 2, val);
1736
},
1737
hue: function (val) {
1738
if (val) {
1739
val %= 360;
1740
val = val < 0 ? 360 + val : val;
1741
}
1742
return this.setChannel('hsl', 0, val);
1743
},
1744
saturation: function (val) {
1745
return this.setChannel('hsl', 1, val);
1746
},
1747
lightness: function (val) {
1748
return this.setChannel('hsl', 2, val);
1749
},
1750
saturationv: function (val) {
1751
return this.setChannel('hsv', 1, val);
1752
},
1753
whiteness: function (val) {
1754
return this.setChannel('hwb', 1, val);
1755
},
1756
blackness: function (val) {
1757
return this.setChannel('hwb', 2, val);
1758
},
1759
value: function (val) {
1760
return this.setChannel('hsv', 2, val);
1761
},
1762
cyan: function (val) {
1763
return this.setChannel('cmyk', 0, val);
1764
},
1765
magenta: function (val) {
1766
return this.setChannel('cmyk', 1, val);
1767
},
1768
yellow: function (val) {
1769
return this.setChannel('cmyk', 2, val);
1770
},
1771
black: function (val) {
1772
return this.setChannel('cmyk', 3, val);
1773
},
1774
1775
hexString: function () {
1776
return colorString.hexString(this.values.rgb);
1777
},
1778
rgbString: function () {
1779
return colorString.rgbString(this.values.rgb, this.values.alpha);
1780
},
1781
rgbaString: function () {
1782
return colorString.rgbaString(this.values.rgb, this.values.alpha);
1783
},
1784
percentString: function () {
1785
return colorString.percentString(this.values.rgb, this.values.alpha);
1786
},
1787
hslString: function () {
1788
return colorString.hslString(this.values.hsl, this.values.alpha);
1789
},
1790
hslaString: function () {
1791
return colorString.hslaString(this.values.hsl, this.values.alpha);
1792
},
1793
hwbString: function () {
1794
return colorString.hwbString(this.values.hwb, this.values.alpha);
1795
},
1796
keyword: function () {
1797
return colorString.keyword(this.values.rgb, this.values.alpha);
1798
},
1799
1800
rgbNumber: function () {
1801
var rgb = this.values.rgb;
1802
return (rgb[0] << 16) | (rgb[1] << 8) | rgb[2];
1803
},
1804
1805
luminosity: function () {
1806
// http://www.w3.org/TR/WCAG20/#relativeluminancedef
1807
var rgb = this.values.rgb;
1808
var lum = [];
1809
for (var i = 0; i < rgb.length; i++) {
1810
var chan = rgb[i] / 255;
1811
lum[i] = (chan <= 0.03928) ? chan / 12.92 : Math.pow(((chan + 0.055) / 1.055), 2.4);
1812
}
1813
return 0.2126 * lum[0] + 0.7152 * lum[1] + 0.0722 * lum[2];
1814
},
1815
1816
contrast: function (color2) {
1817
// http://www.w3.org/TR/WCAG20/#contrast-ratiodef
1818
var lum1 = this.luminosity();
1819
var lum2 = color2.luminosity();
1820
if (lum1 > lum2) {
1821
return (lum1 + 0.05) / (lum2 + 0.05);
1822
}
1823
return (lum2 + 0.05) / (lum1 + 0.05);
1824
},
1825
1826
level: function (color2) {
1827
var contrastRatio = this.contrast(color2);
1828
if (contrastRatio >= 7.1) {
1829
return 'AAA';
1830
}
1831
1832
return (contrastRatio >= 4.5) ? 'AA' : '';
1833
},
1834
1835
dark: function () {
1836
// YIQ equation from http://24ways.org/2010/calculating-color-contrast
1837
var rgb = this.values.rgb;
1838
var yiq = (rgb[0] * 299 + rgb[1] * 587 + rgb[2] * 114) / 1000;
1839
return yiq < 128;
1840
},
1841
1842
light: function () {
1843
return !this.dark();
1844
},
1845
1846
negate: function () {
1847
var rgb = [];
1848
for (var i = 0; i < 3; i++) {
1849
rgb[i] = 255 - this.values.rgb[i];
1850
}
1851
this.setValues('rgb', rgb);
1852
return this;
1853
},
1854
1855
lighten: function (ratio) {
1856
var hsl = this.values.hsl;
1857
hsl[2] += hsl[2] * ratio;
1858
this.setValues('hsl', hsl);
1859
return this;
1860
},
1861
1862
darken: function (ratio) {
1863
var hsl = this.values.hsl;
1864
hsl[2] -= hsl[2] * ratio;
1865
this.setValues('hsl', hsl);
1866
return this;
1867
},
1868
1869
saturate: function (ratio) {
1870
var hsl = this.values.hsl;
1871
hsl[1] += hsl[1] * ratio;
1872
this.setValues('hsl', hsl);
1873
return this;
1874
},
1875
1876
desaturate: function (ratio) {
1877
var hsl = this.values.hsl;
1878
hsl[1] -= hsl[1] * ratio;
1879
this.setValues('hsl', hsl);
1880
return this;
1881
},
1882
1883
whiten: function (ratio) {
1884
var hwb = this.values.hwb;
1885
hwb[1] += hwb[1] * ratio;
1886
this.setValues('hwb', hwb);
1887
return this;
1888
},
1889
1890
blacken: function (ratio) {
1891
var hwb = this.values.hwb;
1892
hwb[2] += hwb[2] * ratio;
1893
this.setValues('hwb', hwb);
1894
return this;
1895
},
1896
1897
greyscale: function () {
1898
var rgb = this.values.rgb;
1899
// http://en.wikipedia.org/wiki/Grayscale#Converting_color_to_grayscale
1900
var val = rgb[0] * 0.3 + rgb[1] * 0.59 + rgb[2] * 0.11;
1901
this.setValues('rgb', [val, val, val]);
1902
return this;
1903
},
1904
1905
clearer: function (ratio) {
1906
var alpha = this.values.alpha;
1907
this.setValues('alpha', alpha - (alpha * ratio));
1908
return this;
1909
},
1910
1911
opaquer: function (ratio) {
1912
var alpha = this.values.alpha;
1913
this.setValues('alpha', alpha + (alpha * ratio));
1914
return this;
1915
},
1916
1917
rotate: function (degrees) {
1918
var hsl = this.values.hsl;
1919
var hue = (hsl[0] + degrees) % 360;
1920
hsl[0] = hue < 0 ? 360 + hue : hue;
1921
this.setValues('hsl', hsl);
1922
return this;
1923
},
1924
1925
/**
1926
* Ported from sass implementation in C
1927
* https://github.com/sass/libsass/blob/0e6b4a2850092356aa3ece07c6b249f0221caced/functions.cpp#L209
1928
*/
1929
mix: function (mixinColor, weight) {
1930
var color1 = this;
1931
var color2 = mixinColor;
1932
var p = weight === undefined ? 0.5 : weight;
1933
1934
var w = 2 * p - 1;
1935
var a = color1.alpha() - color2.alpha();
1936
1937
var w1 = (((w * a === -1) ? w : (w + a) / (1 + w * a)) + 1) / 2.0;
1938
var w2 = 1 - w1;
1939
1940
return this
1941
.rgb(
1942
w1 * color1.red() + w2 * color2.red(),
1943
w1 * color1.green() + w2 * color2.green(),
1944
w1 * color1.blue() + w2 * color2.blue()
1945
)
1946
.alpha(color1.alpha() * p + color2.alpha() * (1 - p));
1947
},
1948
1949
toJSON: function () {
1950
return this.rgb();
1951
},
1952
1953
clone: function () {
1954
// NOTE(SB): using node-clone creates a dependency to Buffer when using browserify,
1955
// making the final build way to big to embed in Chart.js. So let's do it manually,
1956
// assuming that values to clone are 1 dimension arrays containing only numbers,
1957
// except 'alpha' which is a number.
1958
var result = new Color();
1959
var source = this.values;
1960
var target = result.values;
1961
var value, type;
1962
1963
for (var prop in source) {
1964
if (source.hasOwnProperty(prop)) {
1965
value = source[prop];
1966
type = ({}).toString.call(value);
1967
if (type === '[object Array]') {
1968
target[prop] = value.slice(0);
1969
} else if (type === '[object Number]') {
1970
target[prop] = value;
1971
} else {
1972
console.error('unexpected color value:', value);
1973
}
1974
}
1975
}
1976
1977
return result;
1978
}
1979
};
1980
1981
Color.prototype.spaces = {
1982
rgb: ['red', 'green', 'blue'],
1983
hsl: ['hue', 'saturation', 'lightness'],
1984
hsv: ['hue', 'saturation', 'value'],
1985
hwb: ['hue', 'whiteness', 'blackness'],
1986
cmyk: ['cyan', 'magenta', 'yellow', 'black']
1987
};
1988
1989
Color.prototype.maxes = {
1990
rgb: [255, 255, 255],
1991
hsl: [360, 100, 100],
1992
hsv: [360, 100, 100],
1993
hwb: [360, 100, 100],
1994
cmyk: [100, 100, 100, 100]
1995
};
1996
1997
Color.prototype.getValues = function (space) {
1998
var values = this.values;
1999
var vals = {};
2000
2001
for (var i = 0; i < space.length; i++) {
2002
vals[space.charAt(i)] = values[space][i];
2003
}
2004
2005
if (values.alpha !== 1) {
2006
vals.a = values.alpha;
2007
}
2008
2009
// {r: 255, g: 255, b: 255, a: 0.4}
2010
return vals;
2011
};
2012
2013
Color.prototype.setValues = function (space, vals) {
2014
var values = this.values;
2015
var spaces = this.spaces;
2016
var maxes = this.maxes;
2017
var alpha = 1;
2018
var i;
2019
2020
this.valid = true;
2021
2022
if (space === 'alpha') {
2023
alpha = vals;
2024
} else if (vals.length) {
2025
// [10, 10, 10]
2026
values[space] = vals.slice(0, space.length);
2027
alpha = vals[space.length];
2028
} else if (vals[space.charAt(0)] !== undefined) {
2029
// {r: 10, g: 10, b: 10}
2030
for (i = 0; i < space.length; i++) {
2031
values[space][i] = vals[space.charAt(i)];
2032
}
2033
2034
alpha = vals.a;
2035
} else if (vals[spaces[space][0]] !== undefined) {
2036
// {red: 10, green: 10, blue: 10}
2037
var chans = spaces[space];
2038
2039
for (i = 0; i < space.length; i++) {
2040
values[space][i] = vals[chans[i]];
2041
}
2042
2043
alpha = vals.alpha;
2044
}
2045
2046
values.alpha = Math.max(0, Math.min(1, (alpha === undefined ? values.alpha : alpha)));
2047
2048
if (space === 'alpha') {
2049
return false;
2050
}
2051
2052
var capped;
2053
2054
// cap values of the space prior converting all values
2055
for (i = 0; i < space.length; i++) {
2056
capped = Math.max(0, Math.min(maxes[space][i], values[space][i]));
2057
values[space][i] = Math.round(capped);
2058
}
2059
2060
// convert to all the other color spaces
2061
for (var sname in spaces) {
2062
if (sname !== space) {
2063
values[sname] = colorConvert[space][sname](values[space]);
2064
}
2065
}
2066
2067
return true;
2068
};
2069
2070
Color.prototype.setSpace = function (space, args) {
2071
var vals = args[0];
2072
2073
if (vals === undefined) {
2074
// color.rgb()
2075
return this.getValues(space);
2076
}
2077
2078
// color.rgb(10, 10, 10)
2079
if (typeof vals === 'number') {
2080
vals = Array.prototype.slice.call(args);
2081
}
2082
2083
this.setValues(space, vals);
2084
return this;
2085
};
2086
2087
Color.prototype.setChannel = function (space, index, val) {
2088
var svalues = this.values[space];
2089
if (val === undefined) {
2090
// color.red()
2091
return svalues[index];
2092
} else if (val === svalues[index]) {
2093
// color.red(color.red())
2094
return this;
2095
}
2096
2097
// color.red(100)
2098
svalues[index] = val;
2099
this.setValues(space, svalues);
2100
2101
return this;
2102
};
2103
2104
if (typeof window !== 'undefined') {
2105
window.Color = Color;
2106
}
2107
2108
var chartjsColor = Color;
2109
2110
/**
2111
* @namespace Chart.helpers
2112
*/
2113
var helpers = {
2114
/**
2115
* An empty function that can be used, for example, for optional callback.
2116
*/
2117
noop: function() {},
2118
2119
/**
2120
* Returns a unique id, sequentially generated from a global variable.
2121
* @returns {number}
2122
* @function
2123
*/
2124
uid: (function() {
2125
var id = 0;
2126
return function() {
2127
return id++;
2128
};
2129
}()),
2130
2131
/**
2132
* Returns true if `value` is neither null nor undefined, else returns false.
2133
* @param {*} value - The value to test.
2134
* @returns {boolean}
2135
* @since 2.7.0
2136
*/
2137
isNullOrUndef: function(value) {
2138
return value === null || typeof value === 'undefined';
2139
},
2140
2141
/**
2142
* Returns true if `value` is an array (including typed arrays), else returns false.
2143
* @param {*} value - The value to test.
2144
* @returns {boolean}
2145
* @function
2146
*/
2147
isArray: function(value) {
2148
if (Array.isArray && Array.isArray(value)) {
2149
return true;
2150
}
2151
var type = Object.prototype.toString.call(value);
2152
if (type.substr(0, 7) === '[object' && type.substr(-6) === 'Array]') {
2153
return true;
2154
}
2155
return false;
2156
},
2157
2158
/**
2159
* Returns true if `value` is an object (excluding null), else returns false.
2160
* @param {*} value - The value to test.
2161
* @returns {boolean}
2162
* @since 2.7.0
2163
*/
2164
isObject: function(value) {
2165
return value !== null && Object.prototype.toString.call(value) === '[object Object]';
2166
},
2167
2168
/**
2169
* Returns true if `value` is a finite number, else returns false
2170
* @param {*} value - The value to test.
2171
* @returns {boolean}
2172
*/
2173
isFinite: function(value) {
2174
return (typeof value === 'number' || value instanceof Number) && isFinite(value);
2175
},
2176
2177
/**
2178
* Returns `value` if defined, else returns `defaultValue`.
2179
* @param {*} value - The value to return if defined.
2180
* @param {*} defaultValue - The value to return if `value` is undefined.
2181
* @returns {*}
2182
*/
2183
valueOrDefault: function(value, defaultValue) {
2184
return typeof value === 'undefined' ? defaultValue : value;
2185
},
2186
2187
/**
2188
* Returns value at the given `index` in array if defined, else returns `defaultValue`.
2189
* @param {Array} value - The array to lookup for value at `index`.
2190
* @param {number} index - The index in `value` to lookup for value.
2191
* @param {*} defaultValue - The value to return if `value[index]` is undefined.
2192
* @returns {*}
2193
*/
2194
valueAtIndexOrDefault: function(value, index, defaultValue) {
2195
return helpers.valueOrDefault(helpers.isArray(value) ? value[index] : value, defaultValue);
2196
},
2197
2198
/**
2199
* Calls `fn` with the given `args` in the scope defined by `thisArg` and returns the
2200
* value returned by `fn`. If `fn` is not a function, this method returns undefined.
2201
* @param {function} fn - The function to call.
2202
* @param {Array|undefined|null} args - The arguments with which `fn` should be called.
2203
* @param {object} [thisArg] - The value of `this` provided for the call to `fn`.
2204
* @returns {*}
2205
*/
2206
callback: function(fn, args, thisArg) {
2207
if (fn && typeof fn.call === 'function') {
2208
return fn.apply(thisArg, args);
2209
}
2210
},
2211
2212
/**
2213
* Note(SB) for performance sake, this method should only be used when loopable type
2214
* is unknown or in none intensive code (not called often and small loopable). Else
2215
* it's preferable to use a regular for() loop and save extra function calls.
2216
* @param {object|Array} loopable - The object or array to be iterated.
2217
* @param {function} fn - The function to call for each item.
2218
* @param {object} [thisArg] - The value of `this` provided for the call to `fn`.
2219
* @param {boolean} [reverse] - If true, iterates backward on the loopable.
2220
*/
2221
each: function(loopable, fn, thisArg, reverse) {
2222
var i, len, keys;
2223
if (helpers.isArray(loopable)) {
2224
len = loopable.length;
2225
if (reverse) {
2226
for (i = len - 1; i >= 0; i--) {
2227
fn.call(thisArg, loopable[i], i);
2228
}
2229
} else {
2230
for (i = 0; i < len; i++) {
2231
fn.call(thisArg, loopable[i], i);
2232
}
2233
}
2234
} else if (helpers.isObject(loopable)) {
2235
keys = Object.keys(loopable);
2236
len = keys.length;
2237
for (i = 0; i < len; i++) {
2238
fn.call(thisArg, loopable[keys[i]], keys[i]);
2239
}
2240
}
2241
},
2242
2243
/**
2244
* Returns true if the `a0` and `a1` arrays have the same content, else returns false.
2245
* @see https://stackoverflow.com/a/14853974
2246
* @param {Array} a0 - The array to compare
2247
* @param {Array} a1 - The array to compare
2248
* @returns {boolean}
2249
*/
2250
arrayEquals: function(a0, a1) {
2251
var i, ilen, v0, v1;
2252
2253
if (!a0 || !a1 || a0.length !== a1.length) {
2254
return false;
2255
}
2256
2257
for (i = 0, ilen = a0.length; i < ilen; ++i) {
2258
v0 = a0[i];
2259
v1 = a1[i];
2260
2261
if (v0 instanceof Array && v1 instanceof Array) {
2262
if (!helpers.arrayEquals(v0, v1)) {
2263
return false;
2264
}
2265
} else if (v0 !== v1) {
2266
// NOTE: two different object instances will never be equal: {x:20} != {x:20}
2267
return false;
2268
}
2269
}
2270
2271
return true;
2272
},
2273
2274
/**
2275
* Returns a deep copy of `source` without keeping references on objects and arrays.
2276
* @param {*} source - The value to clone.
2277
* @returns {*}
2278
*/
2279
clone: function(source) {
2280
if (helpers.isArray(source)) {
2281
return source.map(helpers.clone);
2282
}
2283
2284
if (helpers.isObject(source)) {
2285
var target = {};
2286
var keys = Object.keys(source);
2287
var klen = keys.length;
2288
var k = 0;
2289
2290
for (; k < klen; ++k) {
2291
target[keys[k]] = helpers.clone(source[keys[k]]);
2292
}
2293
2294
return target;
2295
}
2296
2297
return source;
2298
},
2299
2300
/**
2301
* The default merger when Chart.helpers.merge is called without merger option.
2302
* Note(SB): also used by mergeConfig and mergeScaleConfig as fallback.
2303
* @private
2304
*/
2305
_merger: function(key, target, source, options) {
2306
var tval = target[key];
2307
var sval = source[key];
2308
2309
if (helpers.isObject(tval) && helpers.isObject(sval)) {
2310
helpers.merge(tval, sval, options);
2311
} else {
2312
target[key] = helpers.clone(sval);
2313
}
2314
},
2315
2316
/**
2317
* Merges source[key] in target[key] only if target[key] is undefined.
2318
* @private
2319
*/
2320
_mergerIf: function(key, target, source) {
2321
var tval = target[key];
2322
var sval = source[key];
2323
2324
if (helpers.isObject(tval) && helpers.isObject(sval)) {
2325
helpers.mergeIf(tval, sval);
2326
} else if (!target.hasOwnProperty(key)) {
2327
target[key] = helpers.clone(sval);
2328
}
2329
},
2330
2331
/**
2332
* Recursively deep copies `source` properties into `target` with the given `options`.
2333
* IMPORTANT: `target` is not cloned and will be updated with `source` properties.
2334
* @param {object} target - The target object in which all sources are merged into.
2335
* @param {object|object[]} source - Object(s) to merge into `target`.
2336
* @param {object} [options] - Merging options:
2337
* @param {function} [options.merger] - The merge method (key, target, source, options)
2338
* @returns {object} The `target` object.
2339
*/
2340
merge: function(target, source, options) {
2341
var sources = helpers.isArray(source) ? source : [source];
2342
var ilen = sources.length;
2343
var merge, i, keys, klen, k;
2344
2345
if (!helpers.isObject(target)) {
2346
return target;
2347
}
2348
2349
options = options || {};
2350
merge = options.merger || helpers._merger;
2351
2352
for (i = 0; i < ilen; ++i) {
2353
source = sources[i];
2354
if (!helpers.isObject(source)) {
2355
continue;
2356
}
2357
2358
keys = Object.keys(source);
2359
for (k = 0, klen = keys.length; k < klen; ++k) {
2360
merge(keys[k], target, source, options);
2361
}
2362
}
2363
2364
return target;
2365
},
2366
2367
/**
2368
* Recursively deep copies `source` properties into `target` *only* if not defined in target.
2369
* IMPORTANT: `target` is not cloned and will be updated with `source` properties.
2370
* @param {object} target - The target object in which all sources are merged into.
2371
* @param {object|object[]} source - Object(s) to merge into `target`.
2372
* @returns {object} The `target` object.
2373
*/
2374
mergeIf: function(target, source) {
2375
return helpers.merge(target, source, {merger: helpers._mergerIf});
2376
},
2377
2378
/**
2379
* Applies the contents of two or more objects together into the first object.
2380
* @param {object} target - The target object in which all objects are merged into.
2381
* @param {object} arg1 - Object containing additional properties to merge in target.
2382
* @param {object} argN - Additional objects containing properties to merge in target.
2383
* @returns {object} The `target` object.
2384
*/
2385
extend: Object.assign || function(target) {
2386
return helpers.merge(target, [].slice.call(arguments, 1), {
2387
merger: function(key, dst, src) {
2388
dst[key] = src[key];
2389
}
2390
});
2391
},
2392
2393
/**
2394
* Basic javascript inheritance based on the model created in Backbone.js
2395
*/
2396
inherits: function(extensions) {
2397
var me = this;
2398
var ChartElement = (extensions && extensions.hasOwnProperty('constructor')) ? extensions.constructor : function() {
2399
return me.apply(this, arguments);
2400
};
2401
2402
var Surrogate = function() {
2403
this.constructor = ChartElement;
2404
};
2405
2406
Surrogate.prototype = me.prototype;
2407
ChartElement.prototype = new Surrogate();
2408
ChartElement.extend = helpers.inherits;
2409
2410
if (extensions) {
2411
helpers.extend(ChartElement.prototype, extensions);
2412
}
2413
2414
ChartElement.__super__ = me.prototype;
2415
return ChartElement;
2416
},
2417
2418
_deprecated: function(scope, value, previous, current) {
2419
if (value !== undefined) {
2420
console.warn(scope + ': "' + previous +
2421
'" is deprecated. Please use "' + current + '" instead');
2422
}
2423
}
2424
};
2425
2426
var helpers_core = helpers;
2427
2428
// DEPRECATIONS
2429
2430
/**
2431
* Provided for backward compatibility, use Chart.helpers.callback instead.
2432
* @function Chart.helpers.callCallback
2433
* @deprecated since version 2.6.0
2434
* @todo remove at version 3
2435
* @private
2436
*/
2437
helpers.callCallback = helpers.callback;
2438
2439
/**
2440
* Provided for backward compatibility, use Array.prototype.indexOf instead.
2441
* Array.prototype.indexOf compatibility: Chrome, Opera, Safari, FF1.5+, IE9+
2442
* @function Chart.helpers.indexOf
2443
* @deprecated since version 2.7.0
2444
* @todo remove at version 3
2445
* @private
2446
*/
2447
helpers.indexOf = function(array, item, fromIndex) {
2448
return Array.prototype.indexOf.call(array, item, fromIndex);
2449
};
2450
2451
/**
2452
* Provided for backward compatibility, use Chart.helpers.valueOrDefault instead.
2453
* @function Chart.helpers.getValueOrDefault
2454
* @deprecated since version 2.7.0
2455
* @todo remove at version 3
2456
* @private
2457
*/
2458
helpers.getValueOrDefault = helpers.valueOrDefault;
2459
2460
/**
2461
* Provided for backward compatibility, use Chart.helpers.valueAtIndexOrDefault instead.
2462
* @function Chart.helpers.getValueAtIndexOrDefault
2463
* @deprecated since version 2.7.0
2464
* @todo remove at version 3
2465
* @private
2466
*/
2467
helpers.getValueAtIndexOrDefault = helpers.valueAtIndexOrDefault;
2468
2469
/**
2470
* Easing functions adapted from Robert Penner's easing equations.
2471
* @namespace Chart.helpers.easingEffects
2472
* @see http://www.robertpenner.com/easing/
2473
*/
2474
var effects = {
2475
linear: function(t) {
2476
return t;
2477
},
2478
2479
easeInQuad: function(t) {
2480
return t * t;
2481
},
2482
2483
easeOutQuad: function(t) {
2484
return -t * (t - 2);
2485
},
2486
2487
easeInOutQuad: function(t) {
2488
if ((t /= 0.5) < 1) {
2489
return 0.5 * t * t;
2490
}
2491
return -0.5 * ((--t) * (t - 2) - 1);
2492
},
2493
2494
easeInCubic: function(t) {
2495
return t * t * t;
2496
},
2497
2498
easeOutCubic: function(t) {
2499
return (t = t - 1) * t * t + 1;
2500
},
2501
2502
easeInOutCubic: function(t) {
2503
if ((t /= 0.5) < 1) {
2504
return 0.5 * t * t * t;
2505
}
2506
return 0.5 * ((t -= 2) * t * t + 2);
2507
},
2508
2509
easeInQuart: function(t) {
2510
return t * t * t * t;
2511
},
2512
2513
easeOutQuart: function(t) {
2514
return -((t = t - 1) * t * t * t - 1);
2515
},
2516
2517
easeInOutQuart: function(t) {
2518
if ((t /= 0.5) < 1) {
2519
return 0.5 * t * t * t * t;
2520
}
2521
return -0.5 * ((t -= 2) * t * t * t - 2);
2522
},
2523
2524
easeInQuint: function(t) {
2525
return t * t * t * t * t;
2526
},
2527
2528
easeOutQuint: function(t) {
2529
return (t = t - 1) * t * t * t * t + 1;
2530
},
2531
2532
easeInOutQuint: function(t) {
2533
if ((t /= 0.5) < 1) {
2534
return 0.5 * t * t * t * t * t;
2535
}
2536
return 0.5 * ((t -= 2) * t * t * t * t + 2);
2537
},
2538
2539
easeInSine: function(t) {
2540
return -Math.cos(t * (Math.PI / 2)) + 1;
2541
},
2542
2543
easeOutSine: function(t) {
2544
return Math.sin(t * (Math.PI / 2));
2545
},
2546
2547
easeInOutSine: function(t) {
2548
return -0.5 * (Math.cos(Math.PI * t) - 1);
2549
},
2550
2551
easeInExpo: function(t) {
2552
return (t === 0) ? 0 : Math.pow(2, 10 * (t - 1));
2553
},
2554
2555
easeOutExpo: function(t) {
2556
return (t === 1) ? 1 : -Math.pow(2, -10 * t) + 1;
2557
},
2558
2559
easeInOutExpo: function(t) {
2560
if (t === 0) {
2561
return 0;
2562
}
2563
if (t === 1) {
2564
return 1;
2565
}
2566
if ((t /= 0.5) < 1) {
2567
return 0.5 * Math.pow(2, 10 * (t - 1));
2568
}
2569
return 0.5 * (-Math.pow(2, -10 * --t) + 2);
2570
},
2571
2572
easeInCirc: function(t) {
2573
if (t >= 1) {
2574
return t;
2575
}
2576
return -(Math.sqrt(1 - t * t) - 1);
2577
},
2578
2579
easeOutCirc: function(t) {
2580
return Math.sqrt(1 - (t = t - 1) * t);
2581
},
2582
2583
easeInOutCirc: function(t) {
2584
if ((t /= 0.5) < 1) {
2585
return -0.5 * (Math.sqrt(1 - t * t) - 1);
2586
}
2587
return 0.5 * (Math.sqrt(1 - (t -= 2) * t) + 1);
2588
},
2589
2590
easeInElastic: function(t) {
2591
var s = 1.70158;
2592
var p = 0;
2593
var a = 1;
2594
if (t === 0) {
2595
return 0;
2596
}
2597
if (t === 1) {
2598
return 1;
2599
}
2600
if (!p) {
2601
p = 0.3;
2602
}
2603
if (a < 1) {
2604
a = 1;
2605
s = p / 4;
2606
} else {
2607
s = p / (2 * Math.PI) * Math.asin(1 / a);
2608
}
2609
return -(a * Math.pow(2, 10 * (t -= 1)) * Math.sin((t - s) * (2 * Math.PI) / p));
2610
},
2611
2612
easeOutElastic: function(t) {
2613
var s = 1.70158;
2614
var p = 0;
2615
var a = 1;
2616
if (t === 0) {
2617
return 0;
2618
}
2619
if (t === 1) {
2620
return 1;
2621
}
2622
if (!p) {
2623
p = 0.3;
2624
}
2625
if (a < 1) {
2626
a = 1;
2627
s = p / 4;
2628
} else {
2629
s = p / (2 * Math.PI) * Math.asin(1 / a);
2630
}
2631
return a * Math.pow(2, -10 * t) * Math.sin((t - s) * (2 * Math.PI) / p) + 1;
2632
},
2633
2634
easeInOutElastic: function(t) {
2635
var s = 1.70158;
2636
var p = 0;
2637
var a = 1;
2638
if (t === 0) {
2639
return 0;
2640
}
2641
if ((t /= 0.5) === 2) {
2642
return 1;
2643
}
2644
if (!p) {
2645
p = 0.45;
2646
}
2647
if (a < 1) {
2648
a = 1;
2649
s = p / 4;
2650
} else {
2651
s = p / (2 * Math.PI) * Math.asin(1 / a);
2652
}
2653
if (t < 1) {
2654
return -0.5 * (a * Math.pow(2, 10 * (t -= 1)) * Math.sin((t - s) * (2 * Math.PI) / p));
2655
}
2656
return a * Math.pow(2, -10 * (t -= 1)) * Math.sin((t - s) * (2 * Math.PI) / p) * 0.5 + 1;
2657
},
2658
easeInBack: function(t) {
2659
var s = 1.70158;
2660
return t * t * ((s + 1) * t - s);
2661
},
2662
2663
easeOutBack: function(t) {
2664
var s = 1.70158;
2665
return (t = t - 1) * t * ((s + 1) * t + s) + 1;
2666
},
2667
2668
easeInOutBack: function(t) {
2669
var s = 1.70158;
2670
if ((t /= 0.5) < 1) {
2671
return 0.5 * (t * t * (((s *= (1.525)) + 1) * t - s));
2672
}
2673
return 0.5 * ((t -= 2) * t * (((s *= (1.525)) + 1) * t + s) + 2);
2674
},
2675
2676
easeInBounce: function(t) {
2677
return 1 - effects.easeOutBounce(1 - t);
2678
},
2679
2680
easeOutBounce: function(t) {
2681
if (t < (1 / 2.75)) {
2682
return 7.5625 * t * t;
2683
}
2684
if (t < (2 / 2.75)) {
2685
return 7.5625 * (t -= (1.5 / 2.75)) * t + 0.75;
2686
}
2687
if (t < (2.5 / 2.75)) {
2688
return 7.5625 * (t -= (2.25 / 2.75)) * t + 0.9375;
2689
}
2690
return 7.5625 * (t -= (2.625 / 2.75)) * t + 0.984375;
2691
},
2692
2693
easeInOutBounce: function(t) {
2694
if (t < 0.5) {
2695
return effects.easeInBounce(t * 2) * 0.5;
2696
}
2697
return effects.easeOutBounce(t * 2 - 1) * 0.5 + 0.5;
2698
}
2699
};
2700
2701
var helpers_easing = {
2702
effects: effects
2703
};
2704
2705
// DEPRECATIONS
2706
2707
/**
2708
* Provided for backward compatibility, use Chart.helpers.easing.effects instead.
2709
* @function Chart.helpers.easingEffects
2710
* @deprecated since version 2.7.0
2711
* @todo remove at version 3
2712
* @private
2713
*/
2714
helpers_core.easingEffects = effects;
2715
2716
var PI = Math.PI;
2717
var RAD_PER_DEG = PI / 180;
2718
var DOUBLE_PI = PI * 2;
2719
var HALF_PI = PI / 2;
2720
var QUARTER_PI = PI / 4;
2721
var TWO_THIRDS_PI = PI * 2 / 3;
2722
2723
/**
2724
* @namespace Chart.helpers.canvas
2725
*/
2726
var exports$1 = {
2727
/**
2728
* Clears the entire canvas associated to the given `chart`.
2729
* @param {Chart} chart - The chart for which to clear the canvas.
2730
*/
2731
clear: function(chart) {
2732
chart.ctx.clearRect(0, 0, chart.width, chart.height);
2733
},
2734
2735
/**
2736
* Creates a "path" for a rectangle with rounded corners at position (x, y) with a
2737
* given size (width, height) and the same `radius` for all corners.
2738
* @param {CanvasRenderingContext2D} ctx - The canvas 2D Context.
2739
* @param {number} x - The x axis of the coordinate for the rectangle starting point.
2740
* @param {number} y - The y axis of the coordinate for the rectangle starting point.
2741
* @param {number} width - The rectangle's width.
2742
* @param {number} height - The rectangle's height.
2743
* @param {number} radius - The rounded amount (in pixels) for the four corners.
2744
* @todo handle `radius` as top-left, top-right, bottom-right, bottom-left array/object?
2745
*/
2746
roundedRect: function(ctx, x, y, width, height, radius) {
2747
if (radius) {
2748
var r = Math.min(radius, height / 2, width / 2);
2749
var left = x + r;
2750
var top = y + r;
2751
var right = x + width - r;
2752
var bottom = y + height - r;
2753
2754
ctx.moveTo(x, top);
2755
if (left < right && top < bottom) {
2756
ctx.arc(left, top, r, -PI, -HALF_PI);
2757
ctx.arc(right, top, r, -HALF_PI, 0);
2758
ctx.arc(right, bottom, r, 0, HALF_PI);
2759
ctx.arc(left, bottom, r, HALF_PI, PI);
2760
} else if (left < right) {
2761
ctx.moveTo(left, y);
2762
ctx.arc(right, top, r, -HALF_PI, HALF_PI);
2763
ctx.arc(left, top, r, HALF_PI, PI + HALF_PI);
2764
} else if (top < bottom) {
2765
ctx.arc(left, top, r, -PI, 0);
2766
ctx.arc(left, bottom, r, 0, PI);
2767
} else {
2768
ctx.arc(left, top, r, -PI, PI);
2769
}
2770
ctx.closePath();
2771
ctx.moveTo(x, y);
2772
} else {
2773
ctx.rect(x, y, width, height);
2774
}
2775
},
2776
2777
drawPoint: function(ctx, style, radius, x, y, rotation) {
2778
var type, xOffset, yOffset, size, cornerRadius;
2779
var rad = (rotation || 0) * RAD_PER_DEG;
2780
2781
if (style && typeof style === 'object') {
2782
type = style.toString();
2783
if (type === '[object HTMLImageElement]' || type === '[object HTMLCanvasElement]') {
2784
ctx.save();
2785
ctx.translate(x, y);
2786
ctx.rotate(rad);
2787
ctx.drawImage(style, -style.width / 2, -style.height / 2, style.width, style.height);
2788
ctx.restore();
2789
return;
2790
}
2791
}
2792
2793
if (isNaN(radius) || radius <= 0) {
2794
return;
2795
}
2796
2797
ctx.beginPath();
2798
2799
switch (style) {
2800
// Default includes circle
2801
default:
2802
ctx.arc(x, y, radius, 0, DOUBLE_PI);
2803
ctx.closePath();
2804
break;
2805
case 'triangle':
2806
ctx.moveTo(x + Math.sin(rad) * radius, y - Math.cos(rad) * radius);
2807
rad += TWO_THIRDS_PI;
2808
ctx.lineTo(x + Math.sin(rad) * radius, y - Math.cos(rad) * radius);
2809
rad += TWO_THIRDS_PI;
2810
ctx.lineTo(x + Math.sin(rad) * radius, y - Math.cos(rad) * radius);
2811
ctx.closePath();
2812
break;
2813
case 'rectRounded':
2814
// NOTE: the rounded rect implementation changed to use `arc` instead of
2815
// `quadraticCurveTo` since it generates better results when rect is
2816
// almost a circle. 0.516 (instead of 0.5) produces results with visually
2817
// closer proportion to the previous impl and it is inscribed in the
2818
// circle with `radius`. For more details, see the following PRs:
2819
// https://github.com/chartjs/Chart.js/issues/5597
2820
// https://github.com/chartjs/Chart.js/issues/5858
2821
cornerRadius = radius * 0.516;
2822
size = radius - cornerRadius;
2823
xOffset = Math.cos(rad + QUARTER_PI) * size;
2824
yOffset = Math.sin(rad + QUARTER_PI) * size;
2825
ctx.arc(x - xOffset, y - yOffset, cornerRadius, rad - PI, rad - HALF_PI);
2826
ctx.arc(x + yOffset, y - xOffset, cornerRadius, rad - HALF_PI, rad);
2827
ctx.arc(x + xOffset, y + yOffset, cornerRadius, rad, rad + HALF_PI);
2828
ctx.arc(x - yOffset, y + xOffset, cornerRadius, rad + HALF_PI, rad + PI);
2829
ctx.closePath();
2830
break;
2831
case 'rect':
2832
if (!rotation) {
2833
size = Math.SQRT1_2 * radius;
2834
ctx.rect(x - size, y - size, 2 * size, 2 * size);
2835
break;
2836
}
2837
rad += QUARTER_PI;
2838
/* falls through */
2839
case 'rectRot':
2840
xOffset = Math.cos(rad) * radius;
2841
yOffset = Math.sin(rad) * radius;
2842
ctx.moveTo(x - xOffset, y - yOffset);
2843
ctx.lineTo(x + yOffset, y - xOffset);
2844
ctx.lineTo(x + xOffset, y + yOffset);
2845
ctx.lineTo(x - yOffset, y + xOffset);
2846
ctx.closePath();
2847
break;
2848
case 'crossRot':
2849
rad += QUARTER_PI;
2850
/* falls through */
2851
case 'cross':
2852
xOffset = Math.cos(rad) * radius;
2853
yOffset = Math.sin(rad) * radius;
2854
ctx.moveTo(x - xOffset, y - yOffset);
2855
ctx.lineTo(x + xOffset, y + yOffset);
2856
ctx.moveTo(x + yOffset, y - xOffset);
2857
ctx.lineTo(x - yOffset, y + xOffset);
2858
break;
2859
case 'star':
2860
xOffset = Math.cos(rad) * radius;
2861
yOffset = Math.sin(rad) * radius;
2862
ctx.moveTo(x - xOffset, y - yOffset);
2863
ctx.lineTo(x + xOffset, y + yOffset);
2864
ctx.moveTo(x + yOffset, y - xOffset);
2865
ctx.lineTo(x - yOffset, y + xOffset);
2866
rad += QUARTER_PI;
2867
xOffset = Math.cos(rad) * radius;
2868
yOffset = Math.sin(rad) * radius;
2869
ctx.moveTo(x - xOffset, y - yOffset);
2870
ctx.lineTo(x + xOffset, y + yOffset);
2871
ctx.moveTo(x + yOffset, y - xOffset);
2872
ctx.lineTo(x - yOffset, y + xOffset);
2873
break;
2874
case 'line':
2875
xOffset = Math.cos(rad) * radius;
2876
yOffset = Math.sin(rad) * radius;
2877
ctx.moveTo(x - xOffset, y - yOffset);
2878
ctx.lineTo(x + xOffset, y + yOffset);
2879
break;
2880
case 'dash':
2881
ctx.moveTo(x, y);
2882
ctx.lineTo(x + Math.cos(rad) * radius, y + Math.sin(rad) * radius);
2883
break;
2884
}
2885
2886
ctx.fill();
2887
ctx.stroke();
2888
},
2889
2890
/**
2891
* Returns true if the point is inside the rectangle
2892
* @param {object} point - The point to test
2893
* @param {object} area - The rectangle
2894
* @returns {boolean}
2895
* @private
2896
*/
2897
_isPointInArea: function(point, area) {
2898
var epsilon = 1e-6; // 1e-6 is margin in pixels for accumulated error.
2899
2900
return point.x > area.left - epsilon && point.x < area.right + epsilon &&
2901
point.y > area.top - epsilon && point.y < area.bottom + epsilon;
2902
},
2903
2904
clipArea: function(ctx, area) {
2905
ctx.save();
2906
ctx.beginPath();
2907
ctx.rect(area.left, area.top, area.right - area.left, area.bottom - area.top);
2908
ctx.clip();
2909
},
2910
2911
unclipArea: function(ctx) {
2912
ctx.restore();
2913
},
2914
2915
lineTo: function(ctx, previous, target, flip) {
2916
var stepped = target.steppedLine;
2917
if (stepped) {
2918
if (stepped === 'middle') {
2919
var midpoint = (previous.x + target.x) / 2.0;
2920
ctx.lineTo(midpoint, flip ? target.y : previous.y);
2921
ctx.lineTo(midpoint, flip ? previous.y : target.y);
2922
} else if ((stepped === 'after' && !flip) || (stepped !== 'after' && flip)) {
2923
ctx.lineTo(previous.x, target.y);
2924
} else {
2925
ctx.lineTo(target.x, previous.y);
2926
}
2927
ctx.lineTo(target.x, target.y);
2928
return;
2929
}
2930
2931
if (!target.tension) {
2932
ctx.lineTo(target.x, target.y);
2933
return;
2934
}
2935
2936
ctx.bezierCurveTo(
2937
flip ? previous.controlPointPreviousX : previous.controlPointNextX,
2938
flip ? previous.controlPointPreviousY : previous.controlPointNextY,
2939
flip ? target.controlPointNextX : target.controlPointPreviousX,
2940
flip ? target.controlPointNextY : target.controlPointPreviousY,
2941
target.x,
2942
target.y);
2943
}
2944
};
2945
2946
var helpers_canvas = exports$1;
2947
2948
// DEPRECATIONS
2949
2950
/**
2951
* Provided for backward compatibility, use Chart.helpers.canvas.clear instead.
2952
* @namespace Chart.helpers.clear
2953
* @deprecated since version 2.7.0
2954
* @todo remove at version 3
2955
* @private
2956
*/
2957
helpers_core.clear = exports$1.clear;
2958
2959
/**
2960
* Provided for backward compatibility, use Chart.helpers.canvas.roundedRect instead.
2961
* @namespace Chart.helpers.drawRoundedRectangle
2962
* @deprecated since version 2.7.0
2963
* @todo remove at version 3
2964
* @private
2965
*/
2966
helpers_core.drawRoundedRectangle = function(ctx) {
2967
ctx.beginPath();
2968
exports$1.roundedRect.apply(exports$1, arguments);
2969
};
2970
2971
var defaults = {
2972
/**
2973
* @private
2974
*/
2975
_set: function(scope, values) {
2976
return helpers_core.merge(this[scope] || (this[scope] = {}), values);
2977
}
2978
};
2979
2980
// TODO(v3): remove 'global' from namespace. all default are global and
2981
// there's inconsistency around which options are under 'global'
2982
defaults._set('global', {
2983
defaultColor: 'rgba(0,0,0,0.1)',
2984
defaultFontColor: '#666',
2985
defaultFontFamily: "'Helvetica Neue', 'Helvetica', 'Arial', sans-serif",
2986
defaultFontSize: 12,
2987
defaultFontStyle: 'normal',
2988
defaultLineHeight: 1.2,
2989
showLines: true
2990
});
2991
2992
var core_defaults = defaults;
2993
2994
var valueOrDefault = helpers_core.valueOrDefault;
2995
2996
/**
2997
* Converts the given font object into a CSS font string.
2998
* @param {object} font - A font object.
2999
* @return {string} The CSS font string. See https://developer.mozilla.org/en-US/docs/Web/CSS/font
3000
* @private
3001
*/
3002
function toFontString(font) {
3003
if (!font || helpers_core.isNullOrUndef(font.size) || helpers_core.isNullOrUndef(font.family)) {
3004
return null;
3005
}
3006
3007
return (font.style ? font.style + ' ' : '')
3008
+ (font.weight ? font.weight + ' ' : '')
3009
+ font.size + 'px '
3010
+ font.family;
3011
}
3012
3013
/**
3014
* @alias Chart.helpers.options
3015
* @namespace
3016
*/
3017
var helpers_options = {
3018
/**
3019
* Converts the given line height `value` in pixels for a specific font `size`.
3020
* @param {number|string} value - The lineHeight to parse (eg. 1.6, '14px', '75%', '1.6em').
3021
* @param {number} size - The font size (in pixels) used to resolve relative `value`.
3022
* @returns {number} The effective line height in pixels (size * 1.2 if value is invalid).
3023
* @see https://developer.mozilla.org/en-US/docs/Web/CSS/line-height
3024
* @since 2.7.0
3025
*/
3026
toLineHeight: function(value, size) {
3027
var matches = ('' + value).match(/^(normal|(\d+(?:\.\d+)?)(px|em|%)?)$/);
3028
if (!matches || matches[1] === 'normal') {
3029
return size * 1.2;
3030
}
3031
3032
value = +matches[2];
3033
3034
switch (matches[3]) {
3035
case 'px':
3036
return value;
3037
case '%':
3038
value /= 100;
3039
break;
3040
}
3041
3042
return size * value;
3043
},
3044
3045
/**
3046
* Converts the given value into a padding object with pre-computed width/height.
3047
* @param {number|object} value - If a number, set the value to all TRBL component,
3048
* else, if and object, use defined properties and sets undefined ones to 0.
3049
* @returns {object} The padding values (top, right, bottom, left, width, height)
3050
* @since 2.7.0
3051
*/
3052
toPadding: function(value) {
3053
var t, r, b, l;
3054
3055
if (helpers_core.isObject(value)) {
3056
t = +value.top || 0;
3057
r = +value.right || 0;
3058
b = +value.bottom || 0;
3059
l = +value.left || 0;
3060
} else {
3061
t = r = b = l = +value || 0;
3062
}
3063
3064
return {
3065
top: t,
3066
right: r,
3067
bottom: b,
3068
left: l,
3069
height: t + b,
3070
width: l + r
3071
};
3072
},
3073
3074
/**
3075
* Parses font options and returns the font object.
3076
* @param {object} options - A object that contains font options to be parsed.
3077
* @return {object} The font object.
3078
* @todo Support font.* options and renamed to toFont().
3079
* @private
3080
*/
3081
_parseFont: function(options) {
3082
var globalDefaults = core_defaults.global;
3083
var size = valueOrDefault(options.fontSize, globalDefaults.defaultFontSize);
3084
var font = {
3085
family: valueOrDefault(options.fontFamily, globalDefaults.defaultFontFamily),
3086
lineHeight: helpers_core.options.toLineHeight(valueOrDefault(options.lineHeight, globalDefaults.defaultLineHeight), size),
3087
size: size,
3088
style: valueOrDefault(options.fontStyle, globalDefaults.defaultFontStyle),
3089
weight: null,
3090
string: ''
3091
};
3092
3093
font.string = toFontString(font);
3094
return font;
3095
},
3096
3097
/**
3098
* Evaluates the given `inputs` sequentially and returns the first defined value.
3099
* @param {Array} inputs - An array of values, falling back to the last value.
3100
* @param {object} [context] - If defined and the current value is a function, the value
3101
* is called with `context` as first argument and the result becomes the new input.
3102
* @param {number} [index] - If defined and the current value is an array, the value
3103
* at `index` become the new input.
3104
* @param {object} [info] - object to return information about resolution in
3105
* @param {boolean} [info.cacheable] - Will be set to `false` if option is not cacheable.
3106
* @since 2.7.0
3107
*/
3108
resolve: function(inputs, context, index, info) {
3109
var cacheable = true;
3110
var i, ilen, value;
3111
3112
for (i = 0, ilen = inputs.length; i < ilen; ++i) {
3113
value = inputs[i];
3114
if (value === undefined) {
3115
continue;
3116
}
3117
if (context !== undefined && typeof value === 'function') {
3118
value = value(context);
3119
cacheable = false;
3120
}
3121
if (index !== undefined && helpers_core.isArray(value)) {
3122
value = value[index];
3123
cacheable = false;
3124
}
3125
if (value !== undefined) {
3126
if (info && !cacheable) {
3127
info.cacheable = false;
3128
}
3129
return value;
3130
}
3131
}
3132
}
3133
};
3134
3135
/**
3136
* @alias Chart.helpers.math
3137
* @namespace
3138
*/
3139
var exports$2 = {
3140
/**
3141
* Returns an array of factors sorted from 1 to sqrt(value)
3142
* @private
3143
*/
3144
_factorize: function(value) {
3145
var result = [];
3146
var sqrt = Math.sqrt(value);
3147
var i;
3148
3149
for (i = 1; i < sqrt; i++) {
3150
if (value % i === 0) {
3151
result.push(i);
3152
result.push(value / i);
3153
}
3154
}
3155
if (sqrt === (sqrt | 0)) { // if value is a square number
3156
result.push(sqrt);
3157
}
3158
3159
result.sort(function(a, b) {
3160
return a - b;
3161
}).pop();
3162
return result;
3163
},
3164
3165
log10: Math.log10 || function(x) {
3166
var exponent = Math.log(x) * Math.LOG10E; // Math.LOG10E = 1 / Math.LN10.
3167
// Check for whole powers of 10,
3168
// which due to floating point rounding error should be corrected.
3169
var powerOf10 = Math.round(exponent);
3170
var isPowerOf10 = x === Math.pow(10, powerOf10);
3171
3172
return isPowerOf10 ? powerOf10 : exponent;
3173
}
3174
};
3175
3176
var helpers_math = exports$2;
3177
3178
// DEPRECATIONS
3179
3180
/**
3181
* Provided for backward compatibility, use Chart.helpers.math.log10 instead.
3182
* @namespace Chart.helpers.log10
3183
* @deprecated since version 2.9.0
3184
* @todo remove at version 3
3185
* @private
3186
*/
3187
helpers_core.log10 = exports$2.log10;
3188
3189
var getRtlAdapter = function(rectX, width) {
3190
return {
3191
x: function(x) {
3192
return rectX + rectX + width - x;
3193
},
3194
setWidth: function(w) {
3195
width = w;
3196
},
3197
textAlign: function(align) {
3198
if (align === 'center') {
3199
return align;
3200
}
3201
return align === 'right' ? 'left' : 'right';
3202
},
3203
xPlus: function(x, value) {
3204
return x - value;
3205
},
3206
leftForLtr: function(x, itemWidth) {
3207
return x - itemWidth;
3208
},
3209
};
3210
};
3211
3212
var getLtrAdapter = function() {
3213
return {
3214
x: function(x) {
3215
return x;
3216
},
3217
setWidth: function(w) { // eslint-disable-line no-unused-vars
3218
},
3219
textAlign: function(align) {
3220
return align;
3221
},
3222
xPlus: function(x, value) {
3223
return x + value;
3224
},
3225
leftForLtr: function(x, _itemWidth) { // eslint-disable-line no-unused-vars
3226
return x;
3227
},
3228
};
3229
};
3230
3231
var getAdapter = function(rtl, rectX, width) {
3232
return rtl ? getRtlAdapter(rectX, width) : getLtrAdapter();
3233
};
3234
3235
var overrideTextDirection = function(ctx, direction) {
3236
var style, original;
3237
if (direction === 'ltr' || direction === 'rtl') {
3238
style = ctx.canvas.style;
3239
original = [
3240
style.getPropertyValue('direction'),
3241
style.getPropertyPriority('direction'),
3242
];
3243
3244
style.setProperty('direction', direction, 'important');
3245
ctx.prevTextDirection = original;
3246
}
3247
};
3248
3249
var restoreTextDirection = function(ctx) {
3250
var original = ctx.prevTextDirection;
3251
if (original !== undefined) {
3252
delete ctx.prevTextDirection;
3253
ctx.canvas.style.setProperty('direction', original[0], original[1]);
3254
}
3255
};
3256
3257
var helpers_rtl = {
3258
getRtlAdapter: getAdapter,
3259
overrideTextDirection: overrideTextDirection,
3260
restoreTextDirection: restoreTextDirection,
3261
};
3262
3263
var helpers$1 = helpers_core;
3264
var easing = helpers_easing;
3265
var canvas = helpers_canvas;
3266
var options = helpers_options;
3267
var math = helpers_math;
3268
var rtl = helpers_rtl;
3269
helpers$1.easing = easing;
3270
helpers$1.canvas = canvas;
3271
helpers$1.options = options;
3272
helpers$1.math = math;
3273
helpers$1.rtl = rtl;
3274
3275
function interpolate(start, view, model, ease) {
3276
var keys = Object.keys(model);
3277
var i, ilen, key, actual, origin, target, type, c0, c1;
3278
3279
for (i = 0, ilen = keys.length; i < ilen; ++i) {
3280
key = keys[i];
3281
3282
target = model[key];
3283
3284
// if a value is added to the model after pivot() has been called, the view
3285
// doesn't contain it, so let's initialize the view to the target value.
3286
if (!view.hasOwnProperty(key)) {
3287
view[key] = target;
3288
}
3289
3290
actual = view[key];
3291
3292
if (actual === target || key[0] === '_') {
3293
continue;
3294
}
3295
3296
if (!start.hasOwnProperty(key)) {
3297
start[key] = actual;
3298
}
3299
3300
origin = start[key];
3301
3302
type = typeof target;
3303
3304
if (type === typeof origin) {
3305
if (type === 'string') {
3306
c0 = chartjsColor(origin);
3307
if (c0.valid) {
3308
c1 = chartjsColor(target);
3309
if (c1.valid) {
3310
view[key] = c1.mix(c0, ease).rgbString();
3311
continue;
3312
}
3313
}
3314
} else if (helpers$1.isFinite(origin) && helpers$1.isFinite(target)) {
3315
view[key] = origin + (target - origin) * ease;
3316
continue;
3317
}
3318
}
3319
3320
view[key] = target;
3321
}
3322
}
3323
3324
var Element = function(configuration) {
3325
helpers$1.extend(this, configuration);
3326
this.initialize.apply(this, arguments);
3327
};
3328
3329
helpers$1.extend(Element.prototype, {
3330
_type: undefined,
3331
3332
initialize: function() {
3333
this.hidden = false;
3334
},
3335
3336
pivot: function() {
3337
var me = this;
3338
if (!me._view) {
3339
me._view = helpers$1.extend({}, me._model);
3340
}
3341
me._start = {};
3342
return me;
3343
},
3344
3345
transition: function(ease) {
3346
var me = this;
3347
var model = me._model;
3348
var start = me._start;
3349
var view = me._view;
3350
3351
// No animation -> No Transition
3352
if (!model || ease === 1) {
3353
me._view = helpers$1.extend({}, model);
3354
me._start = null;
3355
return me;
3356
}
3357
3358
if (!view) {
3359
view = me._view = {};
3360
}
3361
3362
if (!start) {
3363
start = me._start = {};
3364
}
3365
3366
interpolate(start, view, model, ease);
3367
3368
return me;
3369
},
3370
3371
tooltipPosition: function() {
3372
return {
3373
x: this._model.x,
3374
y: this._model.y
3375
};
3376
},
3377
3378
hasValue: function() {
3379
return helpers$1.isNumber(this._model.x) && helpers$1.isNumber(this._model.y);
3380
}
3381
});
3382
3383
Element.extend = helpers$1.inherits;
3384
3385
var core_element = Element;
3386
3387
var exports$3 = core_element.extend({
3388
chart: null, // the animation associated chart instance
3389
currentStep: 0, // the current animation step
3390
numSteps: 60, // default number of steps
3391
easing: '', // the easing to use for this animation
3392
render: null, // render function used by the animation service
3393
3394
onAnimationProgress: null, // user specified callback to fire on each step of the animation
3395
onAnimationComplete: null, // user specified callback to fire when the animation finishes
3396
});
3397
3398
var core_animation = exports$3;
3399
3400
// DEPRECATIONS
3401
3402
/**
3403
* Provided for backward compatibility, use Chart.Animation instead
3404
* @prop Chart.Animation#animationObject
3405
* @deprecated since version 2.6.0
3406
* @todo remove at version 3
3407
*/
3408
Object.defineProperty(exports$3.prototype, 'animationObject', {
3409
get: function() {
3410
return this;
3411
}
3412
});
3413
3414
/**
3415
* Provided for backward compatibility, use Chart.Animation#chart instead
3416
* @prop Chart.Animation#chartInstance
3417
* @deprecated since version 2.6.0
3418
* @todo remove at version 3
3419
*/
3420
Object.defineProperty(exports$3.prototype, 'chartInstance', {
3421
get: function() {
3422
return this.chart;
3423
},
3424
set: function(value) {
3425
this.chart = value;
3426
}
3427
});
3428
3429
core_defaults._set('global', {
3430
animation: {
3431
duration: 1000,
3432
easing: 'easeOutQuart',
3433
onProgress: helpers$1.noop,
3434
onComplete: helpers$1.noop
3435
}
3436
});
3437
3438
var core_animations = {
3439
animations: [],
3440
request: null,
3441
3442
/**
3443
* @param {Chart} chart - The chart to animate.
3444
* @param {Chart.Animation} animation - The animation that we will animate.
3445
* @param {number} duration - The animation duration in ms.
3446
* @param {boolean} lazy - if true, the chart is not marked as animating to enable more responsive interactions
3447
*/
3448
addAnimation: function(chart, animation, duration, lazy) {
3449
var animations = this.animations;
3450
var i, ilen;
3451
3452
animation.chart = chart;
3453
animation.startTime = Date.now();
3454
animation.duration = duration;
3455
3456
if (!lazy) {
3457
chart.animating = true;
3458
}
3459
3460
for (i = 0, ilen = animations.length; i < ilen; ++i) {
3461
if (animations[i].chart === chart) {
3462
animations[i] = animation;
3463
return;
3464
}
3465
}
3466
3467
animations.push(animation);
3468
3469
// If there are no animations queued, manually kickstart a digest, for lack of a better word
3470
if (animations.length === 1) {
3471
this.requestAnimationFrame();
3472
}
3473
},
3474
3475
cancelAnimation: function(chart) {
3476
var index = helpers$1.findIndex(this.animations, function(animation) {
3477
return animation.chart === chart;
3478
});
3479
3480
if (index !== -1) {
3481
this.animations.splice(index, 1);
3482
chart.animating = false;
3483
}
3484
},
3485
3486
requestAnimationFrame: function() {
3487
var me = this;
3488
if (me.request === null) {
3489
// Skip animation frame requests until the active one is executed.
3490
// This can happen when processing mouse events, e.g. 'mousemove'
3491
// and 'mouseout' events will trigger multiple renders.
3492
me.request = helpers$1.requestAnimFrame.call(window, function() {
3493
me.request = null;
3494
me.startDigest();
3495
});
3496
}
3497
},
3498
3499
/**
3500
* @private
3501
*/
3502
startDigest: function() {
3503
var me = this;
3504
3505
me.advance();
3506
3507
// Do we have more stuff to animate?
3508
if (me.animations.length > 0) {
3509
me.requestAnimationFrame();
3510
}
3511
},
3512
3513
/**
3514
* @private
3515
*/
3516
advance: function() {
3517
var animations = this.animations;
3518
var animation, chart, numSteps, nextStep;
3519
var i = 0;
3520
3521
// 1 animation per chart, so we are looping charts here
3522
while (i < animations.length) {
3523
animation = animations[i];
3524
chart = animation.chart;
3525
numSteps = animation.numSteps;
3526
3527
// Make sure that currentStep starts at 1
3528
// https://github.com/chartjs/Chart.js/issues/6104
3529
nextStep = Math.floor((Date.now() - animation.startTime) / animation.duration * numSteps) + 1;
3530
animation.currentStep = Math.min(nextStep, numSteps);
3531
3532
helpers$1.callback(animation.render, [chart, animation], chart);
3533
helpers$1.callback(animation.onAnimationProgress, [animation], chart);
3534
3535
if (animation.currentStep >= numSteps) {
3536
helpers$1.callback(animation.onAnimationComplete, [animation], chart);
3537
chart.animating = false;
3538
animations.splice(i, 1);
3539
} else {
3540
++i;
3541
}
3542
}
3543
}
3544
};
3545
3546
var resolve = helpers$1.options.resolve;
3547
3548
var arrayEvents = ['push', 'pop', 'shift', 'splice', 'unshift'];
3549
3550
/**
3551
* Hooks the array methods that add or remove values ('push', pop', 'shift', 'splice',
3552
* 'unshift') and notify the listener AFTER the array has been altered. Listeners are
3553
* called on the 'onData*' callbacks (e.g. onDataPush, etc.) with same arguments.
3554
*/
3555
function listenArrayEvents(array, listener) {
3556
if (array._chartjs) {
3557
array._chartjs.listeners.push(listener);
3558
return;
3559
}
3560
3561
Object.defineProperty(array, '_chartjs', {
3562
configurable: true,
3563
enumerable: false,
3564
value: {
3565
listeners: [listener]
3566
}
3567
});
3568
3569
arrayEvents.forEach(function(key) {
3570
var method = 'onData' + key.charAt(0).toUpperCase() + key.slice(1);
3571
var base = array[key];
3572
3573
Object.defineProperty(array, key, {
3574
configurable: true,
3575
enumerable: false,
3576
value: function() {
3577
var args = Array.prototype.slice.call(arguments);
3578
var res = base.apply(this, args);
3579
3580
helpers$1.each(array._chartjs.listeners, function(object) {
3581
if (typeof object[method] === 'function') {
3582
object[method].apply(object, args);
3583
}
3584
});
3585
3586
return res;
3587
}
3588
});
3589
});
3590
}
3591
3592
/**
3593
* Removes the given array event listener and cleanup extra attached properties (such as
3594
* the _chartjs stub and overridden methods) if array doesn't have any more listeners.
3595
*/
3596
function unlistenArrayEvents(array, listener) {
3597
var stub = array._chartjs;
3598
if (!stub) {
3599
return;
3600
}
3601
3602
var listeners = stub.listeners;
3603
var index = listeners.indexOf(listener);
3604
if (index !== -1) {
3605
listeners.splice(index, 1);
3606
}
3607
3608
if (listeners.length > 0) {
3609
return;
3610
}
3611
3612
arrayEvents.forEach(function(key) {
3613
delete array[key];
3614
});
3615
3616
delete array._chartjs;
3617
}
3618
3619
// Base class for all dataset controllers (line, bar, etc)
3620
var DatasetController = function(chart, datasetIndex) {
3621
this.initialize(chart, datasetIndex);
3622
};
3623
3624
helpers$1.extend(DatasetController.prototype, {
3625
3626
/**
3627
* Element type used to generate a meta dataset (e.g. Chart.element.Line).
3628
* @type {Chart.core.element}
3629
*/
3630
datasetElementType: null,
3631
3632
/**
3633
* Element type used to generate a meta data (e.g. Chart.element.Point).
3634
* @type {Chart.core.element}
3635
*/
3636
dataElementType: null,
3637
3638
/**
3639
* Dataset element option keys to be resolved in _resolveDatasetElementOptions.
3640
* A derived controller may override this to resolve controller-specific options.
3641
* The keys defined here are for backward compatibility for legend styles.
3642
* @private
3643
*/
3644
_datasetElementOptions: [
3645
'backgroundColor',
3646
'borderCapStyle',
3647
'borderColor',
3648
'borderDash',
3649
'borderDashOffset',
3650
'borderJoinStyle',
3651
'borderWidth'
3652
],
3653
3654
/**
3655
* Data element option keys to be resolved in _resolveDataElementOptions.
3656
* A derived controller may override this to resolve controller-specific options.
3657
* The keys defined here are for backward compatibility for legend styles.
3658
* @private
3659
*/
3660
_dataElementOptions: [
3661
'backgroundColor',
3662
'borderColor',
3663
'borderWidth',
3664
'pointStyle'
3665
],
3666
3667
initialize: function(chart, datasetIndex) {
3668
var me = this;
3669
me.chart = chart;
3670
me.index = datasetIndex;
3671
me.linkScales();
3672
me.addElements();
3673
me._type = me.getMeta().type;
3674
},
3675
3676
updateIndex: function(datasetIndex) {
3677
this.index = datasetIndex;
3678
},
3679
3680
linkScales: function() {
3681
var me = this;
3682
var meta = me.getMeta();
3683
var chart = me.chart;
3684
var scales = chart.scales;
3685
var dataset = me.getDataset();
3686
var scalesOpts = chart.options.scales;
3687
3688
if (meta.xAxisID === null || !(meta.xAxisID in scales) || dataset.xAxisID) {
3689
meta.xAxisID = dataset.xAxisID || scalesOpts.xAxes[0].id;
3690
}
3691
if (meta.yAxisID === null || !(meta.yAxisID in scales) || dataset.yAxisID) {
3692
meta.yAxisID = dataset.yAxisID || scalesOpts.yAxes[0].id;
3693
}
3694
},
3695
3696
getDataset: function() {
3697
return this.chart.data.datasets[this.index];
3698
},
3699
3700
getMeta: function() {
3701
return this.chart.getDatasetMeta(this.index);
3702
},
3703
3704
getScaleForId: function(scaleID) {
3705
return this.chart.scales[scaleID];
3706
},
3707
3708
/**
3709
* @private
3710
*/
3711
_getValueScaleId: function() {
3712
return this.getMeta().yAxisID;
3713
},
3714
3715
/**
3716
* @private
3717
*/
3718
_getIndexScaleId: function() {
3719
return this.getMeta().xAxisID;
3720
},
3721
3722
/**
3723
* @private
3724
*/
3725
_getValueScale: function() {
3726
return this.getScaleForId(this._getValueScaleId());
3727
},
3728
3729
/**
3730
* @private
3731
*/
3732
_getIndexScale: function() {
3733
return this.getScaleForId(this._getIndexScaleId());
3734
},
3735
3736
reset: function() {
3737
this._update(true);
3738
},
3739
3740
/**
3741
* @private
3742
*/
3743
destroy: function() {
3744
if (this._data) {
3745
unlistenArrayEvents(this._data, this);
3746
}
3747
},
3748
3749
createMetaDataset: function() {
3750
var me = this;
3751
var type = me.datasetElementType;
3752
return type && new type({
3753
_chart: me.chart,
3754
_datasetIndex: me.index
3755
});
3756
},
3757
3758
createMetaData: function(index) {
3759
var me = this;
3760
var type = me.dataElementType;
3761
return type && new type({
3762
_chart: me.chart,
3763
_datasetIndex: me.index,
3764
_index: index
3765
});
3766
},
3767
3768
addElements: function() {
3769
var me = this;
3770
var meta = me.getMeta();
3771
var data = me.getDataset().data || [];
3772
var metaData = meta.data;
3773
var i, ilen;
3774
3775
for (i = 0, ilen = data.length; i < ilen; ++i) {
3776
metaData[i] = metaData[i] || me.createMetaData(i);
3777
}
3778
3779
meta.dataset = meta.dataset || me.createMetaDataset();
3780
},
3781
3782
addElementAndReset: function(index) {
3783
var element = this.createMetaData(index);
3784
this.getMeta().data.splice(index, 0, element);
3785
this.updateElement(element, index, true);
3786
},
3787
3788
buildOrUpdateElements: function() {
3789
var me = this;
3790
var dataset = me.getDataset();
3791
var data = dataset.data || (dataset.data = []);
3792
3793
// In order to correctly handle data addition/deletion animation (an thus simulate
3794
// real-time charts), we need to monitor these data modifications and synchronize
3795
// the internal meta data accordingly.
3796
if (me._data !== data) {
3797
if (me._data) {
3798
// This case happens when the user replaced the data array instance.
3799
unlistenArrayEvents(me._data, me);
3800
}
3801
3802
if (data && Object.isExtensible(data)) {
3803
listenArrayEvents(data, me);
3804
}
3805
me._data = data;
3806
}
3807
3808
// Re-sync meta data in case the user replaced the data array or if we missed
3809
// any updates and so make sure that we handle number of datapoints changing.
3810
me.resyncElements();
3811
},
3812
3813
/**
3814
* Returns the merged user-supplied and default dataset-level options
3815
* @private
3816
*/
3817
_configure: function() {
3818
var me = this;
3819
me._config = helpers$1.merge({}, [
3820
me.chart.options.datasets[me._type],
3821
me.getDataset(),
3822
], {
3823
merger: function(key, target, source) {
3824
if (key !== '_meta' && key !== 'data') {
3825
helpers$1._merger(key, target, source);
3826
}
3827
}
3828
});
3829
},
3830
3831
_update: function(reset) {
3832
var me = this;
3833
me._configure();
3834
me._cachedDataOpts = null;
3835
me.update(reset);
3836
},
3837
3838
update: helpers$1.noop,
3839
3840
transition: function(easingValue) {
3841
var meta = this.getMeta();
3842
var elements = meta.data || [];
3843
var ilen = elements.length;
3844
var i = 0;
3845
3846
for (; i < ilen; ++i) {
3847
elements[i].transition(easingValue);
3848
}
3849
3850
if (meta.dataset) {
3851
meta.dataset.transition(easingValue);
3852
}
3853
},
3854
3855
draw: function() {
3856
var meta = this.getMeta();
3857
var elements = meta.data || [];
3858
var ilen = elements.length;
3859
var i = 0;
3860
3861
if (meta.dataset) {
3862
meta.dataset.draw();
3863
}
3864
3865
for (; i < ilen; ++i) {
3866
elements[i].draw();
3867
}
3868
},
3869
3870
/**
3871
* Returns a set of predefined style properties that should be used to represent the dataset
3872
* or the data if the index is specified
3873
* @param {number} index - data index
3874
* @return {IStyleInterface} style object
3875
*/
3876
getStyle: function(index) {
3877
var me = this;
3878
var meta = me.getMeta();
3879
var dataset = meta.dataset;
3880
var style;
3881
3882
me._configure();
3883
if (dataset && index === undefined) {
3884
style = me._resolveDatasetElementOptions(dataset || {});
3885
} else {
3886
index = index || 0;
3887
style = me._resolveDataElementOptions(meta.data[index] || {}, index);
3888
}
3889
3890
if (style.fill === false || style.fill === null) {
3891
style.backgroundColor = style.borderColor;
3892
}
3893
3894
return style;
3895
},
3896
3897
/**
3898
* @private
3899
*/
3900
_resolveDatasetElementOptions: function(element, hover) {
3901
var me = this;
3902
var chart = me.chart;
3903
var datasetOpts = me._config;
3904
var custom = element.custom || {};
3905
var options = chart.options.elements[me.datasetElementType.prototype._type] || {};
3906
var elementOptions = me._datasetElementOptions;
3907
var values = {};
3908
var i, ilen, key, readKey;
3909
3910
// Scriptable options
3911
var context = {
3912
chart: chart,
3913
dataset: me.getDataset(),
3914
datasetIndex: me.index,
3915
hover: hover
3916
};
3917
3918
for (i = 0, ilen = elementOptions.length; i < ilen; ++i) {
3919
key = elementOptions[i];
3920
readKey = hover ? 'hover' + key.charAt(0).toUpperCase() + key.slice(1) : key;
3921
values[key] = resolve([
3922
custom[readKey],
3923
datasetOpts[readKey],
3924
options[readKey]
3925
], context);
3926
}
3927
3928
return values;
3929
},
3930
3931
/**
3932
* @private
3933
*/
3934
_resolveDataElementOptions: function(element, index) {
3935
var me = this;
3936
var custom = element && element.custom;
3937
var cached = me._cachedDataOpts;
3938
if (cached && !custom) {
3939
return cached;
3940
}
3941
var chart = me.chart;
3942
var datasetOpts = me._config;
3943
var options = chart.options.elements[me.dataElementType.prototype._type] || {};
3944
var elementOptions = me._dataElementOptions;
3945
var values = {};
3946
3947
// Scriptable options
3948
var context = {
3949
chart: chart,
3950
dataIndex: index,
3951
dataset: me.getDataset(),
3952
datasetIndex: me.index
3953
};
3954
3955
// `resolve` sets cacheable to `false` if any option is indexed or scripted
3956
var info = {cacheable: !custom};
3957
3958
var keys, i, ilen, key;
3959
3960
custom = custom || {};
3961
3962
if (helpers$1.isArray(elementOptions)) {
3963
for (i = 0, ilen = elementOptions.length; i < ilen; ++i) {
3964
key = elementOptions[i];
3965
values[key] = resolve([
3966
custom[key],
3967
datasetOpts[key],
3968
options[key]
3969
], context, index, info);
3970
}
3971
} else {
3972
keys = Object.keys(elementOptions);
3973
for (i = 0, ilen = keys.length; i < ilen; ++i) {
3974
key = keys[i];
3975
values[key] = resolve([
3976
custom[key],
3977
datasetOpts[elementOptions[key]],
3978
datasetOpts[key],
3979
options[key]
3980
], context, index, info);
3981
}
3982
}
3983
3984
if (info.cacheable) {
3985
me._cachedDataOpts = Object.freeze(values);
3986
}
3987
3988
return values;
3989
},
3990
3991
removeHoverStyle: function(element) {
3992
helpers$1.merge(element._model, element.$previousStyle || {});
3993
delete element.$previousStyle;
3994
},
3995
3996
setHoverStyle: function(element) {
3997
var dataset = this.chart.data.datasets[element._datasetIndex];
3998
var index = element._index;
3999
var custom = element.custom || {};
4000
var model = element._model;
4001
var getHoverColor = helpers$1.getHoverColor;
4002
4003
element.$previousStyle = {
4004
backgroundColor: model.backgroundColor,
4005
borderColor: model.borderColor,
4006
borderWidth: model.borderWidth
4007
};
4008
4009
model.backgroundColor = resolve([custom.hoverBackgroundColor, dataset.hoverBackgroundColor, getHoverColor(model.backgroundColor)], undefined, index);
4010
model.borderColor = resolve([custom.hoverBorderColor, dataset.hoverBorderColor, getHoverColor(model.borderColor)], undefined, index);
4011
model.borderWidth = resolve([custom.hoverBorderWidth, dataset.hoverBorderWidth, model.borderWidth], undefined, index);
4012
},
4013
4014
/**
4015
* @private
4016
*/
4017
_removeDatasetHoverStyle: function() {
4018
var element = this.getMeta().dataset;
4019
4020
if (element) {
4021
this.removeHoverStyle(element);
4022
}
4023
},
4024
4025
/**
4026
* @private
4027
*/
4028
_setDatasetHoverStyle: function() {
4029
var element = this.getMeta().dataset;
4030
var prev = {};
4031
var i, ilen, key, keys, hoverOptions, model;
4032
4033
if (!element) {
4034
return;
4035
}
4036
4037
model = element._model;
4038
hoverOptions = this._resolveDatasetElementOptions(element, true);
4039
4040
keys = Object.keys(hoverOptions);
4041
for (i = 0, ilen = keys.length; i < ilen; ++i) {
4042
key = keys[i];
4043
prev[key] = model[key];
4044
model[key] = hoverOptions[key];
4045
}
4046
4047
element.$previousStyle = prev;
4048
},
4049
4050
/**
4051
* @private
4052
*/
4053
resyncElements: function() {
4054
var me = this;
4055
var meta = me.getMeta();
4056
var data = me.getDataset().data;
4057
var numMeta = meta.data.length;
4058
var numData = data.length;
4059
4060
if (numData < numMeta) {
4061
meta.data.splice(numData, numMeta - numData);
4062
} else if (numData > numMeta) {
4063
me.insertElements(numMeta, numData - numMeta);
4064
}
4065
},
4066
4067
/**
4068
* @private
4069
*/
4070
insertElements: function(start, count) {
4071
for (var i = 0; i < count; ++i) {
4072
this.addElementAndReset(start + i);
4073
}
4074
},
4075
4076
/**
4077
* @private
4078
*/
4079
onDataPush: function() {
4080
var count = arguments.length;
4081
this.insertElements(this.getDataset().data.length - count, count);
4082
},
4083
4084
/**
4085
* @private
4086
*/
4087
onDataPop: function() {
4088
this.getMeta().data.pop();
4089
},
4090
4091
/**
4092
* @private
4093
*/
4094
onDataShift: function() {
4095
this.getMeta().data.shift();
4096
},
4097
4098
/**
4099
* @private
4100
*/
4101
onDataSplice: function(start, count) {
4102
this.getMeta().data.splice(start, count);
4103
this.insertElements(start, arguments.length - 2);
4104
},
4105
4106
/**
4107
* @private
4108
*/
4109
onDataUnshift: function() {
4110
this.insertElements(0, arguments.length);
4111
}
4112
});
4113
4114
DatasetController.extend = helpers$1.inherits;
4115
4116
var core_datasetController = DatasetController;
4117
4118
var TAU = Math.PI * 2;
4119
4120
core_defaults._set('global', {
4121
elements: {
4122
arc: {
4123
backgroundColor: core_defaults.global.defaultColor,
4124
borderColor: '#fff',
4125
borderWidth: 2,
4126
borderAlign: 'center'
4127
}
4128
}
4129
});
4130
4131
function clipArc(ctx, arc) {
4132
var startAngle = arc.startAngle;
4133
var endAngle = arc.endAngle;
4134
var pixelMargin = arc.pixelMargin;
4135
var angleMargin = pixelMargin / arc.outerRadius;
4136
var x = arc.x;
4137
var y = arc.y;
4138
4139
// Draw an inner border by cliping the arc and drawing a double-width border
4140
// Enlarge the clipping arc by 0.33 pixels to eliminate glitches between borders
4141
ctx.beginPath();
4142
ctx.arc(x, y, arc.outerRadius, startAngle - angleMargin, endAngle + angleMargin);
4143
if (arc.innerRadius > pixelMargin) {
4144
angleMargin = pixelMargin / arc.innerRadius;
4145
ctx.arc(x, y, arc.innerRadius - pixelMargin, endAngle + angleMargin, startAngle - angleMargin, true);
4146
} else {
4147
ctx.arc(x, y, pixelMargin, endAngle + Math.PI / 2, startAngle - Math.PI / 2);
4148
}
4149
ctx.closePath();
4150
ctx.clip();
4151
}
4152
4153
function drawFullCircleBorders(ctx, vm, arc, inner) {
4154
var endAngle = arc.endAngle;
4155
var i;
4156
4157
if (inner) {
4158
arc.endAngle = arc.startAngle + TAU;
4159
clipArc(ctx, arc);
4160
arc.endAngle = endAngle;
4161
if (arc.endAngle === arc.startAngle && arc.fullCircles) {
4162
arc.endAngle += TAU;
4163
arc.fullCircles--;
4164
}
4165
}
4166
4167
ctx.beginPath();
4168
ctx.arc(arc.x, arc.y, arc.innerRadius, arc.startAngle + TAU, arc.startAngle, true);
4169
for (i = 0; i < arc.fullCircles; ++i) {
4170
ctx.stroke();
4171
}
4172
4173
ctx.beginPath();
4174
ctx.arc(arc.x, arc.y, vm.outerRadius, arc.startAngle, arc.startAngle + TAU);
4175
for (i = 0; i < arc.fullCircles; ++i) {
4176
ctx.stroke();
4177
}
4178
}
4179
4180
function drawBorder(ctx, vm, arc) {
4181
var inner = vm.borderAlign === 'inner';
4182
4183
if (inner) {
4184
ctx.lineWidth = vm.borderWidth * 2;
4185
ctx.lineJoin = 'round';
4186
} else {
4187
ctx.lineWidth = vm.borderWidth;
4188
ctx.lineJoin = 'bevel';
4189
}
4190
4191
if (arc.fullCircles) {
4192
drawFullCircleBorders(ctx, vm, arc, inner);
4193
}
4194
4195
if (inner) {
4196
clipArc(ctx, arc);
4197
}
4198
4199
ctx.beginPath();
4200
ctx.arc(arc.x, arc.y, vm.outerRadius, arc.startAngle, arc.endAngle);
4201
ctx.arc(arc.x, arc.y, arc.innerRadius, arc.endAngle, arc.startAngle, true);
4202
ctx.closePath();
4203
ctx.stroke();
4204
}
4205
4206
var element_arc = core_element.extend({
4207
_type: 'arc',
4208
4209
inLabelRange: function(mouseX) {
4210
var vm = this._view;
4211
4212
if (vm) {
4213
return (Math.pow(mouseX - vm.x, 2) < Math.pow(vm.radius + vm.hoverRadius, 2));
4214
}
4215
return false;
4216
},
4217
4218
inRange: function(chartX, chartY) {
4219
var vm = this._view;
4220
4221
if (vm) {
4222
var pointRelativePosition = helpers$1.getAngleFromPoint(vm, {x: chartX, y: chartY});
4223
var angle = pointRelativePosition.angle;
4224
var distance = pointRelativePosition.distance;
4225
4226
// Sanitise angle range
4227
var startAngle = vm.startAngle;
4228
var endAngle = vm.endAngle;
4229
while (endAngle < startAngle) {
4230
endAngle += TAU;
4231
}
4232
while (angle > endAngle) {
4233
angle -= TAU;
4234
}
4235
while (angle < startAngle) {
4236
angle += TAU;
4237
}
4238
4239
// Check if within the range of the open/close angle
4240
var betweenAngles = (angle >= startAngle && angle <= endAngle);
4241
var withinRadius = (distance >= vm.innerRadius && distance <= vm.outerRadius);
4242
4243
return (betweenAngles && withinRadius);
4244
}
4245
return false;
4246
},
4247
4248
getCenterPoint: function() {
4249
var vm = this._view;
4250
var halfAngle = (vm.startAngle + vm.endAngle) / 2;
4251
var halfRadius = (vm.innerRadius + vm.outerRadius) / 2;
4252
return {
4253
x: vm.x + Math.cos(halfAngle) * halfRadius,
4254
y: vm.y + Math.sin(halfAngle) * halfRadius
4255
};
4256
},
4257
4258
getArea: function() {
4259
var vm = this._view;
4260
return Math.PI * ((vm.endAngle - vm.startAngle) / (2 * Math.PI)) * (Math.pow(vm.outerRadius, 2) - Math.pow(vm.innerRadius, 2));
4261
},
4262
4263
tooltipPosition: function() {
4264
var vm = this._view;
4265
var centreAngle = vm.startAngle + ((vm.endAngle - vm.startAngle) / 2);
4266
var rangeFromCentre = (vm.outerRadius - vm.innerRadius) / 2 + vm.innerRadius;
4267
4268
return {
4269
x: vm.x + (Math.cos(centreAngle) * rangeFromCentre),
4270
y: vm.y + (Math.sin(centreAngle) * rangeFromCentre)
4271
};
4272
},
4273
4274
draw: function() {
4275
var ctx = this._chart.ctx;
4276
var vm = this._view;
4277
var pixelMargin = (vm.borderAlign === 'inner') ? 0.33 : 0;
4278
var arc = {
4279
x: vm.x,
4280
y: vm.y,
4281
innerRadius: vm.innerRadius,
4282
outerRadius: Math.max(vm.outerRadius - pixelMargin, 0),
4283
pixelMargin: pixelMargin,
4284
startAngle: vm.startAngle,
4285
endAngle: vm.endAngle,
4286
fullCircles: Math.floor(vm.circumference / TAU)
4287
};
4288
var i;
4289
4290
ctx.save();
4291
4292
ctx.fillStyle = vm.backgroundColor;
4293
ctx.strokeStyle = vm.borderColor;
4294
4295
if (arc.fullCircles) {
4296
arc.endAngle = arc.startAngle + TAU;
4297
ctx.beginPath();
4298
ctx.arc(arc.x, arc.y, arc.outerRadius, arc.startAngle, arc.endAngle);
4299
ctx.arc(arc.x, arc.y, arc.innerRadius, arc.endAngle, arc.startAngle, true);
4300
ctx.closePath();
4301
for (i = 0; i < arc.fullCircles; ++i) {
4302
ctx.fill();
4303
}
4304
arc.endAngle = arc.startAngle + vm.circumference % TAU;
4305
}
4306
4307
ctx.beginPath();
4308
ctx.arc(arc.x, arc.y, arc.outerRadius, arc.startAngle, arc.endAngle);
4309
ctx.arc(arc.x, arc.y, arc.innerRadius, arc.endAngle, arc.startAngle, true);
4310
ctx.closePath();
4311
ctx.fill();
4312
4313
if (vm.borderWidth) {
4314
drawBorder(ctx, vm, arc);
4315
}
4316
4317
ctx.restore();
4318
}
4319
});
4320
4321
var valueOrDefault$1 = helpers$1.valueOrDefault;
4322
4323
var defaultColor = core_defaults.global.defaultColor;
4324
4325
core_defaults._set('global', {
4326
elements: {
4327
line: {
4328
tension: 0.4,
4329
backgroundColor: defaultColor,
4330
borderWidth: 3,
4331
borderColor: defaultColor,
4332
borderCapStyle: 'butt',
4333
borderDash: [],
4334
borderDashOffset: 0.0,
4335
borderJoinStyle: 'miter',
4336
capBezierPoints: true,
4337
fill: true, // do we fill in the area between the line and its base axis
4338
}
4339
}
4340
});
4341
4342
var element_line = core_element.extend({
4343
_type: 'line',
4344
4345
draw: function() {
4346
var me = this;
4347
var vm = me._view;
4348
var ctx = me._chart.ctx;
4349
var spanGaps = vm.spanGaps;
4350
var points = me._children.slice(); // clone array
4351
var globalDefaults = core_defaults.global;
4352
var globalOptionLineElements = globalDefaults.elements.line;
4353
var lastDrawnIndex = -1;
4354
var closePath = me._loop;
4355
var index, previous, currentVM;
4356
4357
if (!points.length) {
4358
return;
4359
}
4360
4361
if (me._loop) {
4362
for (index = 0; index < points.length; ++index) {
4363
previous = helpers$1.previousItem(points, index);
4364
// If the line has an open path, shift the point array
4365
if (!points[index]._view.skip && previous._view.skip) {
4366
points = points.slice(index).concat(points.slice(0, index));
4367
closePath = spanGaps;
4368
break;
4369
}
4370
}
4371
// If the line has a close path, add the first point again
4372
if (closePath) {
4373
points.push(points[0]);
4374
}
4375
}
4376
4377
ctx.save();
4378
4379
// Stroke Line Options
4380
ctx.lineCap = vm.borderCapStyle || globalOptionLineElements.borderCapStyle;
4381
4382
// IE 9 and 10 do not support line dash
4383
if (ctx.setLineDash) {
4384
ctx.setLineDash(vm.borderDash || globalOptionLineElements.borderDash);
4385
}
4386
4387
ctx.lineDashOffset = valueOrDefault$1(vm.borderDashOffset, globalOptionLineElements.borderDashOffset);
4388
ctx.lineJoin = vm.borderJoinStyle || globalOptionLineElements.borderJoinStyle;
4389
ctx.lineWidth = valueOrDefault$1(vm.borderWidth, globalOptionLineElements.borderWidth);
4390
ctx.strokeStyle = vm.borderColor || globalDefaults.defaultColor;
4391
4392
// Stroke Line
4393
ctx.beginPath();
4394
4395
// First point moves to it's starting position no matter what
4396
currentVM = points[0]._view;
4397
if (!currentVM.skip) {
4398
ctx.moveTo(currentVM.x, currentVM.y);
4399
lastDrawnIndex = 0;
4400
}
4401
4402
for (index = 1; index < points.length; ++index) {
4403
currentVM = points[index]._view;
4404
previous = lastDrawnIndex === -1 ? helpers$1.previousItem(points, index) : points[lastDrawnIndex];
4405
4406
if (!currentVM.skip) {
4407
if ((lastDrawnIndex !== (index - 1) && !spanGaps) || lastDrawnIndex === -1) {
4408
// There was a gap and this is the first point after the gap
4409
ctx.moveTo(currentVM.x, currentVM.y);
4410
} else {
4411
// Line to next point
4412
helpers$1.canvas.lineTo(ctx, previous._view, currentVM);
4413
}
4414
lastDrawnIndex = index;
4415
}
4416
}
4417
4418
if (closePath) {
4419
ctx.closePath();
4420
}
4421
4422
ctx.stroke();
4423
ctx.restore();
4424
}
4425
});
4426
4427
var valueOrDefault$2 = helpers$1.valueOrDefault;
4428
4429
var defaultColor$1 = core_defaults.global.defaultColor;
4430
4431
core_defaults._set('global', {
4432
elements: {
4433
point: {
4434
radius: 3,
4435
pointStyle: 'circle',
4436
backgroundColor: defaultColor$1,
4437
borderColor: defaultColor$1,
4438
borderWidth: 1,
4439
// Hover
4440
hitRadius: 1,
4441
hoverRadius: 4,
4442
hoverBorderWidth: 1
4443
}
4444
}
4445
});
4446
4447
function xRange(mouseX) {
4448
var vm = this._view;
4449
return vm ? (Math.abs(mouseX - vm.x) < vm.radius + vm.hitRadius) : false;
4450
}
4451
4452
function yRange(mouseY) {
4453
var vm = this._view;
4454
return vm ? (Math.abs(mouseY - vm.y) < vm.radius + vm.hitRadius) : false;
4455
}
4456
4457
var element_point = core_element.extend({
4458
_type: 'point',
4459
4460
inRange: function(mouseX, mouseY) {
4461
var vm = this._view;
4462
return vm ? ((Math.pow(mouseX - vm.x, 2) + Math.pow(mouseY - vm.y, 2)) < Math.pow(vm.hitRadius + vm.radius, 2)) : false;
4463
},
4464
4465
inLabelRange: xRange,
4466
inXRange: xRange,
4467
inYRange: yRange,
4468
4469
getCenterPoint: function() {
4470
var vm = this._view;
4471
return {
4472
x: vm.x,
4473
y: vm.y
4474
};
4475
},
4476
4477
getArea: function() {
4478
return Math.PI * Math.pow(this._view.radius, 2);
4479
},
4480
4481
tooltipPosition: function() {
4482
var vm = this._view;
4483
return {
4484
x: vm.x,
4485
y: vm.y,
4486
padding: vm.radius + vm.borderWidth
4487
};
4488
},
4489
4490
draw: function(chartArea) {
4491
var vm = this._view;
4492
var ctx = this._chart.ctx;
4493
var pointStyle = vm.pointStyle;
4494
var rotation = vm.rotation;
4495
var radius = vm.radius;
4496
var x = vm.x;
4497
var y = vm.y;
4498
var globalDefaults = core_defaults.global;
4499
var defaultColor = globalDefaults.defaultColor; // eslint-disable-line no-shadow
4500
4501
if (vm.skip) {
4502
return;
4503
}
4504
4505
// Clipping for Points.
4506
if (chartArea === undefined || helpers$1.canvas._isPointInArea(vm, chartArea)) {
4507
ctx.strokeStyle = vm.borderColor || defaultColor;
4508
ctx.lineWidth = valueOrDefault$2(vm.borderWidth, globalDefaults.elements.point.borderWidth);
4509
ctx.fillStyle = vm.backgroundColor || defaultColor;
4510
helpers$1.canvas.drawPoint(ctx, pointStyle, radius, x, y, rotation);
4511
}
4512
}
4513
});
4514
4515
var defaultColor$2 = core_defaults.global.defaultColor;
4516
4517
core_defaults._set('global', {
4518
elements: {
4519
rectangle: {
4520
backgroundColor: defaultColor$2,
4521
borderColor: defaultColor$2,
4522
borderSkipped: 'bottom',
4523
borderWidth: 0
4524
}
4525
}
4526
});
4527
4528
function isVertical(vm) {
4529
return vm && vm.width !== undefined;
4530
}
4531
4532
/**
4533
* Helper function to get the bounds of the bar regardless of the orientation
4534
* @param bar {Chart.Element.Rectangle} the bar
4535
* @return {Bounds} bounds of the bar
4536
* @private
4537
*/
4538
function getBarBounds(vm) {
4539
var x1, x2, y1, y2, half;
4540
4541
if (isVertical(vm)) {
4542
half = vm.width / 2;
4543
x1 = vm.x - half;
4544
x2 = vm.x + half;
4545
y1 = Math.min(vm.y, vm.base);
4546
y2 = Math.max(vm.y, vm.base);
4547
} else {
4548
half = vm.height / 2;
4549
x1 = Math.min(vm.x, vm.base);
4550
x2 = Math.max(vm.x, vm.base);
4551
y1 = vm.y - half;
4552
y2 = vm.y + half;
4553
}
4554
4555
return {
4556
left: x1,
4557
top: y1,
4558
right: x2,
4559
bottom: y2
4560
};
4561
}
4562
4563
function swap(orig, v1, v2) {
4564
return orig === v1 ? v2 : orig === v2 ? v1 : orig;
4565
}
4566
4567
function parseBorderSkipped(vm) {
4568
var edge = vm.borderSkipped;
4569
var res = {};
4570
4571
if (!edge) {
4572
return res;
4573
}
4574
4575
if (vm.horizontal) {
4576
if (vm.base > vm.x) {
4577
edge = swap(edge, 'left', 'right');
4578
}
4579
} else if (vm.base < vm.y) {
4580
edge = swap(edge, 'bottom', 'top');
4581
}
4582
4583
res[edge] = true;
4584
return res;
4585
}
4586
4587
function parseBorderWidth(vm, maxW, maxH) {
4588
var value = vm.borderWidth;
4589
var skip = parseBorderSkipped(vm);
4590
var t, r, b, l;
4591
4592
if (helpers$1.isObject(value)) {
4593
t = +value.top || 0;
4594
r = +value.right || 0;
4595
b = +value.bottom || 0;
4596
l = +value.left || 0;
4597
} else {
4598
t = r = b = l = +value || 0;
4599
}
4600
4601
return {
4602
t: skip.top || (t < 0) ? 0 : t > maxH ? maxH : t,
4603
r: skip.right || (r < 0) ? 0 : r > maxW ? maxW : r,
4604
b: skip.bottom || (b < 0) ? 0 : b > maxH ? maxH : b,
4605
l: skip.left || (l < 0) ? 0 : l > maxW ? maxW : l
4606
};
4607
}
4608
4609
function boundingRects(vm) {
4610
var bounds = getBarBounds(vm);
4611
var width = bounds.right - bounds.left;
4612
var height = bounds.bottom - bounds.top;
4613
var border = parseBorderWidth(vm, width / 2, height / 2);
4614
4615
return {
4616
outer: {
4617
x: bounds.left,
4618
y: bounds.top,
4619
w: width,
4620
h: height
4621
},
4622
inner: {
4623
x: bounds.left + border.l,
4624
y: bounds.top + border.t,
4625
w: width - border.l - border.r,
4626
h: height - border.t - border.b
4627
}
4628
};
4629
}
4630
4631
function inRange(vm, x, y) {
4632
var skipX = x === null;
4633
var skipY = y === null;
4634
var bounds = !vm || (skipX && skipY) ? false : getBarBounds(vm);
4635
4636
return bounds
4637
&& (skipX || x >= bounds.left && x <= bounds.right)
4638
&& (skipY || y >= bounds.top && y <= bounds.bottom);
4639
}
4640
4641
var element_rectangle = core_element.extend({
4642
_type: 'rectangle',
4643
4644
draw: function() {
4645
var ctx = this._chart.ctx;
4646
var vm = this._view;
4647
var rects = boundingRects(vm);
4648
var outer = rects.outer;
4649
var inner = rects.inner;
4650
4651
ctx.fillStyle = vm.backgroundColor;
4652
ctx.fillRect(outer.x, outer.y, outer.w, outer.h);
4653
4654
if (outer.w === inner.w && outer.h === inner.h) {
4655
return;
4656
}
4657
4658
ctx.save();
4659
ctx.beginPath();
4660
ctx.rect(outer.x, outer.y, outer.w, outer.h);
4661
ctx.clip();
4662
ctx.fillStyle = vm.borderColor;
4663
ctx.rect(inner.x, inner.y, inner.w, inner.h);
4664
ctx.fill('evenodd');
4665
ctx.restore();
4666
},
4667
4668
height: function() {
4669
var vm = this._view;
4670
return vm.base - vm.y;
4671
},
4672
4673
inRange: function(mouseX, mouseY) {
4674
return inRange(this._view, mouseX, mouseY);
4675
},
4676
4677
inLabelRange: function(mouseX, mouseY) {
4678
var vm = this._view;
4679
return isVertical(vm)
4680
? inRange(vm, mouseX, null)
4681
: inRange(vm, null, mouseY);
4682
},
4683
4684
inXRange: function(mouseX) {
4685
return inRange(this._view, mouseX, null);
4686
},
4687
4688
inYRange: function(mouseY) {
4689
return inRange(this._view, null, mouseY);
4690
},
4691
4692
getCenterPoint: function() {
4693
var vm = this._view;
4694
var x, y;
4695
if (isVertical(vm)) {
4696
x = vm.x;
4697
y = (vm.y + vm.base) / 2;
4698
} else {
4699
x = (vm.x + vm.base) / 2;
4700
y = vm.y;
4701
}
4702
4703
return {x: x, y: y};
4704
},
4705
4706
getArea: function() {
4707
var vm = this._view;
4708
4709
return isVertical(vm)
4710
? vm.width * Math.abs(vm.y - vm.base)
4711
: vm.height * Math.abs(vm.x - vm.base);
4712
},
4713
4714
tooltipPosition: function() {
4715
var vm = this._view;
4716
return {
4717
x: vm.x,
4718
y: vm.y
4719
};
4720
}
4721
});
4722
4723
var elements = {};
4724
var Arc = element_arc;
4725
var Line = element_line;
4726
var Point = element_point;
4727
var Rectangle = element_rectangle;
4728
elements.Arc = Arc;
4729
elements.Line = Line;
4730
elements.Point = Point;
4731
elements.Rectangle = Rectangle;
4732
4733
var deprecated = helpers$1._deprecated;
4734
var valueOrDefault$3 = helpers$1.valueOrDefault;
4735
4736
core_defaults._set('bar', {
4737
hover: {
4738
mode: 'label'
4739
},
4740
4741
scales: {
4742
xAxes: [{
4743
type: 'category',
4744
offset: true,
4745
gridLines: {
4746
offsetGridLines: true
4747
}
4748
}],
4749
4750
yAxes: [{
4751
type: 'linear'
4752
}]
4753
}
4754
});
4755
4756
core_defaults._set('global', {
4757
datasets: {
4758
bar: {
4759
categoryPercentage: 0.8,
4760
barPercentage: 0.9
4761
}
4762
}
4763
});
4764
4765
/**
4766
* Computes the "optimal" sample size to maintain bars equally sized while preventing overlap.
4767
* @private
4768
*/
4769
function computeMinSampleSize(scale, pixels) {
4770
var min = scale._length;
4771
var prev, curr, i, ilen;
4772
4773
for (i = 1, ilen = pixels.length; i < ilen; ++i) {
4774
min = Math.min(min, Math.abs(pixels[i] - pixels[i - 1]));
4775
}
4776
4777
for (i = 0, ilen = scale.getTicks().length; i < ilen; ++i) {
4778
curr = scale.getPixelForTick(i);
4779
min = i > 0 ? Math.min(min, Math.abs(curr - prev)) : min;
4780
prev = curr;
4781
}
4782
4783
return min;
4784
}
4785
4786
/**
4787
* Computes an "ideal" category based on the absolute bar thickness or, if undefined or null,
4788
* uses the smallest interval (see computeMinSampleSize) that prevents bar overlapping. This
4789
* mode currently always generates bars equally sized (until we introduce scriptable options?).
4790
* @private
4791
*/
4792
function computeFitCategoryTraits(index, ruler, options) {
4793
var thickness = options.barThickness;
4794
var count = ruler.stackCount;
4795
var curr = ruler.pixels[index];
4796
var min = helpers$1.isNullOrUndef(thickness)
4797
? computeMinSampleSize(ruler.scale, ruler.pixels)
4798
: -1;
4799
var size, ratio;
4800
4801
if (helpers$1.isNullOrUndef(thickness)) {
4802
size = min * options.categoryPercentage;
4803
ratio = options.barPercentage;
4804
} else {
4805
// When bar thickness is enforced, category and bar percentages are ignored.
4806
// Note(SB): we could add support for relative bar thickness (e.g. barThickness: '50%')
4807
// and deprecate barPercentage since this value is ignored when thickness is absolute.
4808
size = thickness * count;
4809
ratio = 1;
4810
}
4811
4812
return {
4813
chunk: size / count,
4814
ratio: ratio,
4815
start: curr - (size / 2)
4816
};
4817
}
4818
4819
/**
4820
* Computes an "optimal" category that globally arranges bars side by side (no gap when
4821
* percentage options are 1), based on the previous and following categories. This mode
4822
* generates bars with different widths when data are not evenly spaced.
4823
* @private
4824
*/
4825
function computeFlexCategoryTraits(index, ruler, options) {
4826
var pixels = ruler.pixels;
4827
var curr = pixels[index];
4828
var prev = index > 0 ? pixels[index - 1] : null;
4829
var next = index < pixels.length - 1 ? pixels[index + 1] : null;
4830
var percent = options.categoryPercentage;
4831
var start, size;
4832
4833
if (prev === null) {
4834
// first data: its size is double based on the next point or,
4835
// if it's also the last data, we use the scale size.
4836
prev = curr - (next === null ? ruler.end - ruler.start : next - curr);
4837
}
4838
4839
if (next === null) {
4840
// last data: its size is also double based on the previous point.
4841
next = curr + curr - prev;
4842
}
4843
4844
start = curr - (curr - Math.min(prev, next)) / 2 * percent;
4845
size = Math.abs(next - prev) / 2 * percent;
4846
4847
return {
4848
chunk: size / ruler.stackCount,
4849
ratio: options.barPercentage,
4850
start: start
4851
};
4852
}
4853
4854
var controller_bar = core_datasetController.extend({
4855
4856
dataElementType: elements.Rectangle,
4857
4858
/**
4859
* @private
4860
*/
4861
_dataElementOptions: [
4862
'backgroundColor',
4863
'borderColor',
4864
'borderSkipped',
4865
'borderWidth',
4866
'barPercentage',
4867
'barThickness',
4868
'categoryPercentage',
4869
'maxBarThickness',
4870
'minBarLength'
4871
],
4872
4873
initialize: function() {
4874
var me = this;
4875
var meta, scaleOpts;
4876
4877
core_datasetController.prototype.initialize.apply(me, arguments);
4878
4879
meta = me.getMeta();
4880
meta.stack = me.getDataset().stack;
4881
meta.bar = true;
4882
4883
scaleOpts = me._getIndexScale().options;
4884
deprecated('bar chart', scaleOpts.barPercentage, 'scales.[x/y]Axes.barPercentage', 'dataset.barPercentage');
4885
deprecated('bar chart', scaleOpts.barThickness, 'scales.[x/y]Axes.barThickness', 'dataset.barThickness');
4886
deprecated('bar chart', scaleOpts.categoryPercentage, 'scales.[x/y]Axes.categoryPercentage', 'dataset.categoryPercentage');
4887
deprecated('bar chart', me._getValueScale().options.minBarLength, 'scales.[x/y]Axes.minBarLength', 'dataset.minBarLength');
4888
deprecated('bar chart', scaleOpts.maxBarThickness, 'scales.[x/y]Axes.maxBarThickness', 'dataset.maxBarThickness');
4889
},
4890
4891
update: function(reset) {
4892
var me = this;
4893
var rects = me.getMeta().data;
4894
var i, ilen;
4895
4896
me._ruler = me.getRuler();
4897
4898
for (i = 0, ilen = rects.length; i < ilen; ++i) {
4899
me.updateElement(rects[i], i, reset);
4900
}
4901
},
4902
4903
updateElement: function(rectangle, index, reset) {
4904
var me = this;
4905
var meta = me.getMeta();
4906
var dataset = me.getDataset();
4907
var options = me._resolveDataElementOptions(rectangle, index);
4908
4909
rectangle._xScale = me.getScaleForId(meta.xAxisID);
4910
rectangle._yScale = me.getScaleForId(meta.yAxisID);
4911
rectangle._datasetIndex = me.index;
4912
rectangle._index = index;
4913
rectangle._model = {
4914
backgroundColor: options.backgroundColor,
4915
borderColor: options.borderColor,
4916
borderSkipped: options.borderSkipped,
4917
borderWidth: options.borderWidth,
4918
datasetLabel: dataset.label,
4919
label: me.chart.data.labels[index]
4920
};
4921
4922
if (helpers$1.isArray(dataset.data[index])) {
4923
rectangle._model.borderSkipped = null;
4924
}
4925
4926
me._updateElementGeometry(rectangle, index, reset, options);
4927
4928
rectangle.pivot();
4929
},
4930
4931
/**
4932
* @private
4933
*/
4934
_updateElementGeometry: function(rectangle, index, reset, options) {
4935
var me = this;
4936
var model = rectangle._model;
4937
var vscale = me._getValueScale();
4938
var base = vscale.getBasePixel();
4939
var horizontal = vscale.isHorizontal();
4940
var ruler = me._ruler || me.getRuler();
4941
var vpixels = me.calculateBarValuePixels(me.index, index, options);
4942
var ipixels = me.calculateBarIndexPixels(me.index, index, ruler, options);
4943
4944
model.horizontal = horizontal;
4945
model.base = reset ? base : vpixels.base;
4946
model.x = horizontal ? reset ? base : vpixels.head : ipixels.center;
4947
model.y = horizontal ? ipixels.center : reset ? base : vpixels.head;
4948
model.height = horizontal ? ipixels.size : undefined;
4949
model.width = horizontal ? undefined : ipixels.size;
4950
},
4951
4952
/**
4953
* Returns the stacks based on groups and bar visibility.
4954
* @param {number} [last] - The dataset index
4955
* @returns {string[]} The list of stack IDs
4956
* @private
4957
*/
4958
_getStacks: function(last) {
4959
var me = this;
4960
var scale = me._getIndexScale();
4961
var metasets = scale._getMatchingVisibleMetas(me._type);
4962
var stacked = scale.options.stacked;
4963
var ilen = metasets.length;
4964
var stacks = [];
4965
var i, meta;
4966
4967
for (i = 0; i < ilen; ++i) {
4968
meta = metasets[i];
4969
// stacked | meta.stack
4970
// | found | not found | undefined
4971
// false | x | x | x
4972
// true | | x |
4973
// undefined | | x | x
4974
if (stacked === false || stacks.indexOf(meta.stack) === -1 ||
4975
(stacked === undefined && meta.stack === undefined)) {
4976
stacks.push(meta.stack);
4977
}
4978
if (meta.index === last) {
4979
break;
4980
}
4981
}
4982
4983
return stacks;
4984
},
4985
4986
/**
4987
* Returns the effective number of stacks based on groups and bar visibility.
4988
* @private
4989
*/
4990
getStackCount: function() {
4991
return this._getStacks().length;
4992
},
4993
4994
/**
4995
* Returns the stack index for the given dataset based on groups and bar visibility.
4996
* @param {number} [datasetIndex] - The dataset index
4997
* @param {string} [name] - The stack name to find
4998
* @returns {number} The stack index
4999
* @private
5000
*/
5001
getStackIndex: function(datasetIndex, name) {
5002
var stacks = this._getStacks(datasetIndex);
5003
var index = (name !== undefined)
5004
? stacks.indexOf(name)
5005
: -1; // indexOf returns -1 if element is not present
5006
5007
return (index === -1)
5008
? stacks.length - 1
5009
: index;
5010
},
5011
5012
/**
5013
* @private
5014
*/
5015
getRuler: function() {
5016
var me = this;
5017
var scale = me._getIndexScale();
5018
var pixels = [];
5019
var i, ilen;
5020
5021
for (i = 0, ilen = me.getMeta().data.length; i < ilen; ++i) {
5022
pixels.push(scale.getPixelForValue(null, i, me.index));
5023
}
5024
5025
return {
5026
pixels: pixels,
5027
start: scale._startPixel,
5028
end: scale._endPixel,
5029
stackCount: me.getStackCount(),
5030
scale: scale
5031
};
5032
},
5033
5034
/**
5035
* Note: pixel values are not clamped to the scale area.
5036
* @private
5037
*/
5038
calculateBarValuePixels: function(datasetIndex, index, options) {
5039
var me = this;
5040
var chart = me.chart;
5041
var scale = me._getValueScale();
5042
var isHorizontal = scale.isHorizontal();
5043
var datasets = chart.data.datasets;
5044
var metasets = scale._getMatchingVisibleMetas(me._type);
5045
var value = scale._parseValue(datasets[datasetIndex].data[index]);
5046
var minBarLength = options.minBarLength;
5047
var stacked = scale.options.stacked;
5048
var stack = me.getMeta().stack;
5049
var start = value.start === undefined ? 0 : value.max >= 0 && value.min >= 0 ? value.min : value.max;
5050
var length = value.start === undefined ? value.end : value.max >= 0 && value.min >= 0 ? value.max - value.min : value.min - value.max;
5051
var ilen = metasets.length;
5052
var i, imeta, ivalue, base, head, size, stackLength;
5053
5054
if (stacked || (stacked === undefined && stack !== undefined)) {
5055
for (i = 0; i < ilen; ++i) {
5056
imeta = metasets[i];
5057
5058
if (imeta.index === datasetIndex) {
5059
break;
5060
}
5061
5062
if (imeta.stack === stack) {
5063
stackLength = scale._parseValue(datasets[imeta.index].data[index]);
5064
ivalue = stackLength.start === undefined ? stackLength.end : stackLength.min >= 0 && stackLength.max >= 0 ? stackLength.max : stackLength.min;
5065
5066
if ((value.min < 0 && ivalue < 0) || (value.max >= 0 && ivalue > 0)) {
5067
start += ivalue;
5068
}
5069
}
5070
}
5071
}
5072
5073
base = scale.getPixelForValue(start);
5074
head = scale.getPixelForValue(start + length);
5075
size = head - base;
5076
5077
if (minBarLength !== undefined && Math.abs(size) < minBarLength) {
5078
size = minBarLength;
5079
if (length >= 0 && !isHorizontal || length < 0 && isHorizontal) {
5080
head = base - minBarLength;
5081
} else {
5082
head = base + minBarLength;
5083
}
5084
}
5085
5086
return {
5087
size: size,
5088
base: base,
5089
head: head,
5090
center: head + size / 2
5091
};
5092
},
5093
5094
/**
5095
* @private
5096
*/
5097
calculateBarIndexPixels: function(datasetIndex, index, ruler, options) {
5098
var me = this;
5099
var range = options.barThickness === 'flex'
5100
? computeFlexCategoryTraits(index, ruler, options)
5101
: computeFitCategoryTraits(index, ruler, options);
5102
5103
var stackIndex = me.getStackIndex(datasetIndex, me.getMeta().stack);
5104
var center = range.start + (range.chunk * stackIndex) + (range.chunk / 2);
5105
var size = Math.min(
5106
valueOrDefault$3(options.maxBarThickness, Infinity),
5107
range.chunk * range.ratio);
5108
5109
return {
5110
base: center - size / 2,
5111
head: center + size / 2,
5112
center: center,
5113
size: size
5114
};
5115
},
5116
5117
draw: function() {
5118
var me = this;
5119
var chart = me.chart;
5120
var scale = me._getValueScale();
5121
var rects = me.getMeta().data;
5122
var dataset = me.getDataset();
5123
var ilen = rects.length;
5124
var i = 0;
5125
5126
helpers$1.canvas.clipArea(chart.ctx, chart.chartArea);
5127
5128
for (; i < ilen; ++i) {
5129
var val = scale._parseValue(dataset.data[i]);
5130
if (!isNaN(val.min) && !isNaN(val.max)) {
5131
rects[i].draw();
5132
}
5133
}
5134
5135
helpers$1.canvas.unclipArea(chart.ctx);
5136
},
5137
5138
/**
5139
* @private
5140
*/
5141
_resolveDataElementOptions: function() {
5142
var me = this;
5143
var values = helpers$1.extend({}, core_datasetController.prototype._resolveDataElementOptions.apply(me, arguments));
5144
var indexOpts = me._getIndexScale().options;
5145
var valueOpts = me._getValueScale().options;
5146
5147
values.barPercentage = valueOrDefault$3(indexOpts.barPercentage, values.barPercentage);
5148
values.barThickness = valueOrDefault$3(indexOpts.barThickness, values.barThickness);
5149
values.categoryPercentage = valueOrDefault$3(indexOpts.categoryPercentage, values.categoryPercentage);
5150
values.maxBarThickness = valueOrDefault$3(indexOpts.maxBarThickness, values.maxBarThickness);
5151
values.minBarLength = valueOrDefault$3(valueOpts.minBarLength, values.minBarLength);
5152
5153
return values;
5154
}
5155
5156
});
5157
5158
var valueOrDefault$4 = helpers$1.valueOrDefault;
5159
var resolve$1 = helpers$1.options.resolve;
5160
5161
core_defaults._set('bubble', {
5162
hover: {
5163
mode: 'single'
5164
},
5165
5166
scales: {
5167
xAxes: [{
5168
type: 'linear', // bubble should probably use a linear scale by default
5169
position: 'bottom',
5170
id: 'x-axis-0' // need an ID so datasets can reference the scale
5171
}],
5172
yAxes: [{
5173
type: 'linear',
5174
position: 'left',
5175
id: 'y-axis-0'
5176
}]
5177
},
5178
5179
tooltips: {
5180
callbacks: {
5181
title: function() {
5182
// Title doesn't make sense for scatter since we format the data as a point
5183
return '';
5184
},
5185
label: function(item, data) {
5186
var datasetLabel = data.datasets[item.datasetIndex].label || '';
5187
var dataPoint = data.datasets[item.datasetIndex].data[item.index];
5188
return datasetLabel + ': (' + item.xLabel + ', ' + item.yLabel + ', ' + dataPoint.r + ')';
5189
}
5190
}
5191
}
5192
});
5193
5194
var controller_bubble = core_datasetController.extend({
5195
/**
5196
* @protected
5197
*/
5198
dataElementType: elements.Point,
5199
5200
/**
5201
* @private
5202
*/
5203
_dataElementOptions: [
5204
'backgroundColor',
5205
'borderColor',
5206
'borderWidth',
5207
'hoverBackgroundColor',
5208
'hoverBorderColor',
5209
'hoverBorderWidth',
5210
'hoverRadius',
5211
'hitRadius',
5212
'pointStyle',
5213
'rotation'
5214
],
5215
5216
/**
5217
* @protected
5218
*/
5219
update: function(reset) {
5220
var me = this;
5221
var meta = me.getMeta();
5222
var points = meta.data;
5223
5224
// Update Points
5225
helpers$1.each(points, function(point, index) {
5226
me.updateElement(point, index, reset);
5227
});
5228
},
5229
5230
/**
5231
* @protected
5232
*/
5233
updateElement: function(point, index, reset) {
5234
var me = this;
5235
var meta = me.getMeta();
5236
var custom = point.custom || {};
5237
var xScale = me.getScaleForId(meta.xAxisID);
5238
var yScale = me.getScaleForId(meta.yAxisID);
5239
var options = me._resolveDataElementOptions(point, index);
5240
var data = me.getDataset().data[index];
5241
var dsIndex = me.index;
5242
5243
var x = reset ? xScale.getPixelForDecimal(0.5) : xScale.getPixelForValue(typeof data === 'object' ? data : NaN, index, dsIndex);
5244
var y = reset ? yScale.getBasePixel() : yScale.getPixelForValue(data, index, dsIndex);
5245
5246
point._xScale = xScale;
5247
point._yScale = yScale;
5248
point._options = options;
5249
point._datasetIndex = dsIndex;
5250
point._index = index;
5251
point._model = {
5252
backgroundColor: options.backgroundColor,
5253
borderColor: options.borderColor,
5254
borderWidth: options.borderWidth,
5255
hitRadius: options.hitRadius,
5256
pointStyle: options.pointStyle,
5257
rotation: options.rotation,
5258
radius: reset ? 0 : options.radius,
5259
skip: custom.skip || isNaN(x) || isNaN(y),
5260
x: x,
5261
y: y,
5262
};
5263
5264
point.pivot();
5265
},
5266
5267
/**
5268
* @protected
5269
*/
5270
setHoverStyle: function(point) {
5271
var model = point._model;
5272
var options = point._options;
5273
var getHoverColor = helpers$1.getHoverColor;
5274
5275
point.$previousStyle = {
5276
backgroundColor: model.backgroundColor,
5277
borderColor: model.borderColor,
5278
borderWidth: model.borderWidth,
5279
radius: model.radius
5280
};
5281
5282
model.backgroundColor = valueOrDefault$4(options.hoverBackgroundColor, getHoverColor(options.backgroundColor));
5283
model.borderColor = valueOrDefault$4(options.hoverBorderColor, getHoverColor(options.borderColor));
5284
model.borderWidth = valueOrDefault$4(options.hoverBorderWidth, options.borderWidth);
5285
model.radius = options.radius + options.hoverRadius;
5286
},
5287
5288
/**
5289
* @private
5290
*/
5291
_resolveDataElementOptions: function(point, index) {
5292
var me = this;
5293
var chart = me.chart;
5294
var dataset = me.getDataset();
5295
var custom = point.custom || {};
5296
var data = dataset.data[index] || {};
5297
var values = core_datasetController.prototype._resolveDataElementOptions.apply(me, arguments);
5298
5299
// Scriptable options
5300
var context = {
5301
chart: chart,
5302
dataIndex: index,
5303
dataset: dataset,
5304
datasetIndex: me.index
5305
};
5306
5307
// In case values were cached (and thus frozen), we need to clone the values
5308
if (me._cachedDataOpts === values) {
5309
values = helpers$1.extend({}, values);
5310
}
5311
5312
// Custom radius resolution
5313
values.radius = resolve$1([
5314
custom.radius,
5315
data.r,
5316
me._config.radius,
5317
chart.options.elements.point.radius
5318
], context, index);
5319
5320
return values;
5321
}
5322
});
5323
5324
var valueOrDefault$5 = helpers$1.valueOrDefault;
5325
5326
var PI$1 = Math.PI;
5327
var DOUBLE_PI$1 = PI$1 * 2;
5328
var HALF_PI$1 = PI$1 / 2;
5329
5330
core_defaults._set('doughnut', {
5331
animation: {
5332
// Boolean - Whether we animate the rotation of the Doughnut
5333
animateRotate: true,
5334
// Boolean - Whether we animate scaling the Doughnut from the centre
5335
animateScale: false
5336
},
5337
hover: {
5338
mode: 'single'
5339
},
5340
legendCallback: function(chart) {
5341
var list = document.createElement('ul');
5342
var data = chart.data;
5343
var datasets = data.datasets;
5344
var labels = data.labels;
5345
var i, ilen, listItem, listItemSpan;
5346
5347
list.setAttribute('class', chart.id + '-legend');
5348
if (datasets.length) {
5349
for (i = 0, ilen = datasets[0].data.length; i < ilen; ++i) {
5350
listItem = list.appendChild(document.createElement('li'));
5351
listItemSpan = listItem.appendChild(document.createElement('span'));
5352
listItemSpan.style.backgroundColor = datasets[0].backgroundColor[i];
5353
if (labels[i]) {
5354
listItem.appendChild(document.createTextNode(labels[i]));
5355
}
5356
}
5357
}
5358
5359
return list.outerHTML;
5360
},
5361
legend: {
5362
labels: {
5363
generateLabels: function(chart) {
5364
var data = chart.data;
5365
if (data.labels.length && data.datasets.length) {
5366
return data.labels.map(function(label, i) {
5367
var meta = chart.getDatasetMeta(0);
5368
var style = meta.controller.getStyle(i);
5369
5370
return {
5371
text: label,
5372
fillStyle: style.backgroundColor,
5373
strokeStyle: style.borderColor,
5374
lineWidth: style.borderWidth,
5375
hidden: isNaN(data.datasets[0].data[i]) || meta.data[i].hidden,
5376
5377
// Extra data used for toggling the correct item
5378
index: i
5379
};
5380
});
5381
}
5382
return [];
5383
}
5384
},
5385
5386
onClick: function(e, legendItem) {
5387
var index = legendItem.index;
5388
var chart = this.chart;
5389
var i, ilen, meta;
5390
5391
for (i = 0, ilen = (chart.data.datasets || []).length; i < ilen; ++i) {
5392
meta = chart.getDatasetMeta(i);
5393
// toggle visibility of index if exists
5394
if (meta.data[index]) {
5395
meta.data[index].hidden = !meta.data[index].hidden;
5396
}
5397
}
5398
5399
chart.update();
5400
}
5401
},
5402
5403
// The percentage of the chart that we cut out of the middle.
5404
cutoutPercentage: 50,
5405
5406
// The rotation of the chart, where the first data arc begins.
5407
rotation: -HALF_PI$1,
5408
5409
// The total circumference of the chart.
5410
circumference: DOUBLE_PI$1,
5411
5412
// Need to override these to give a nice default
5413
tooltips: {
5414
callbacks: {
5415
title: function() {
5416
return '';
5417
},
5418
label: function(tooltipItem, data) {
5419
var dataLabel = data.labels[tooltipItem.index];
5420
var value = ': ' + data.datasets[tooltipItem.datasetIndex].data[tooltipItem.index];
5421
5422
if (helpers$1.isArray(dataLabel)) {
5423
// show value on first line of multiline label
5424
// need to clone because we are changing the value
5425
dataLabel = dataLabel.slice();
5426
dataLabel[0] += value;
5427
} else {
5428
dataLabel += value;
5429
}
5430
5431
return dataLabel;
5432
}
5433
}
5434
}
5435
});
5436
5437
var controller_doughnut = core_datasetController.extend({
5438
5439
dataElementType: elements.Arc,
5440
5441
linkScales: helpers$1.noop,
5442
5443
/**
5444
* @private
5445
*/
5446
_dataElementOptions: [
5447
'backgroundColor',
5448
'borderColor',
5449
'borderWidth',
5450
'borderAlign',
5451
'hoverBackgroundColor',
5452
'hoverBorderColor',
5453
'hoverBorderWidth',
5454
],
5455
5456
// Get index of the dataset in relation to the visible datasets. This allows determining the inner and outer radius correctly
5457
getRingIndex: function(datasetIndex) {
5458
var ringIndex = 0;
5459
5460
for (var j = 0; j < datasetIndex; ++j) {
5461
if (this.chart.isDatasetVisible(j)) {
5462
++ringIndex;
5463
}
5464
}
5465
5466
return ringIndex;
5467
},
5468
5469
update: function(reset) {
5470
var me = this;
5471
var chart = me.chart;
5472
var chartArea = chart.chartArea;
5473
var opts = chart.options;
5474
var ratioX = 1;
5475
var ratioY = 1;
5476
var offsetX = 0;
5477
var offsetY = 0;
5478
var meta = me.getMeta();
5479
var arcs = meta.data;
5480
var cutout = opts.cutoutPercentage / 100 || 0;
5481
var circumference = opts.circumference;
5482
var chartWeight = me._getRingWeight(me.index);
5483
var maxWidth, maxHeight, i, ilen;
5484
5485
// If the chart's circumference isn't a full circle, calculate size as a ratio of the width/height of the arc
5486
if (circumference < DOUBLE_PI$1) {
5487
var startAngle = opts.rotation % DOUBLE_PI$1;
5488
startAngle += startAngle >= PI$1 ? -DOUBLE_PI$1 : startAngle < -PI$1 ? DOUBLE_PI$1 : 0;
5489
var endAngle = startAngle + circumference;
5490
var startX = Math.cos(startAngle);
5491
var startY = Math.sin(startAngle);
5492
var endX = Math.cos(endAngle);
5493
var endY = Math.sin(endAngle);
5494
var contains0 = (startAngle <= 0 && endAngle >= 0) || endAngle >= DOUBLE_PI$1;
5495
var contains90 = (startAngle <= HALF_PI$1 && endAngle >= HALF_PI$1) || endAngle >= DOUBLE_PI$1 + HALF_PI$1;
5496
var contains180 = startAngle === -PI$1 || endAngle >= PI$1;
5497
var contains270 = (startAngle <= -HALF_PI$1 && endAngle >= -HALF_PI$1) || endAngle >= PI$1 + HALF_PI$1;
5498
var minX = contains180 ? -1 : Math.min(startX, startX * cutout, endX, endX * cutout);
5499
var minY = contains270 ? -1 : Math.min(startY, startY * cutout, endY, endY * cutout);
5500
var maxX = contains0 ? 1 : Math.max(startX, startX * cutout, endX, endX * cutout);
5501
var maxY = contains90 ? 1 : Math.max(startY, startY * cutout, endY, endY * cutout);
5502
ratioX = (maxX - minX) / 2;
5503
ratioY = (maxY - minY) / 2;
5504
offsetX = -(maxX + minX) / 2;
5505
offsetY = -(maxY + minY) / 2;
5506
}
5507
5508
for (i = 0, ilen = arcs.length; i < ilen; ++i) {
5509
arcs[i]._options = me._resolveDataElementOptions(arcs[i], i);
5510
}
5511
5512
chart.borderWidth = me.getMaxBorderWidth();
5513
maxWidth = (chartArea.right - chartArea.left - chart.borderWidth) / ratioX;
5514
maxHeight = (chartArea.bottom - chartArea.top - chart.borderWidth) / ratioY;
5515
chart.outerRadius = Math.max(Math.min(maxWidth, maxHeight) / 2, 0);
5516
chart.innerRadius = Math.max(chart.outerRadius * cutout, 0);
5517
chart.radiusLength = (chart.outerRadius - chart.innerRadius) / (me._getVisibleDatasetWeightTotal() || 1);
5518
chart.offsetX = offsetX * chart.outerRadius;
5519
chart.offsetY = offsetY * chart.outerRadius;
5520
5521
meta.total = me.calculateTotal();
5522
5523
me.outerRadius = chart.outerRadius - chart.radiusLength * me._getRingWeightOffset(me.index);
5524
me.innerRadius = Math.max(me.outerRadius - chart.radiusLength * chartWeight, 0);
5525
5526
for (i = 0, ilen = arcs.length; i < ilen; ++i) {
5527
me.updateElement(arcs[i], i, reset);
5528
}
5529
},
5530
5531
updateElement: function(arc, index, reset) {
5532
var me = this;
5533
var chart = me.chart;
5534
var chartArea = chart.chartArea;
5535
var opts = chart.options;
5536
var animationOpts = opts.animation;
5537
var centerX = (chartArea.left + chartArea.right) / 2;
5538
var centerY = (chartArea.top + chartArea.bottom) / 2;
5539
var startAngle = opts.rotation; // non reset case handled later
5540
var endAngle = opts.rotation; // non reset case handled later
5541
var dataset = me.getDataset();
5542
var circumference = reset && animationOpts.animateRotate ? 0 : arc.hidden ? 0 : me.calculateCircumference(dataset.data[index]) * (opts.circumference / DOUBLE_PI$1);
5543
var innerRadius = reset && animationOpts.animateScale ? 0 : me.innerRadius;
5544
var outerRadius = reset && animationOpts.animateScale ? 0 : me.outerRadius;
5545
var options = arc._options || {};
5546
5547
helpers$1.extend(arc, {
5548
// Utility
5549
_datasetIndex: me.index,
5550
_index: index,
5551
5552
// Desired view properties
5553
_model: {
5554
backgroundColor: options.backgroundColor,
5555
borderColor: options.borderColor,
5556
borderWidth: options.borderWidth,
5557
borderAlign: options.borderAlign,
5558
x: centerX + chart.offsetX,
5559
y: centerY + chart.offsetY,
5560
startAngle: startAngle,
5561
endAngle: endAngle,
5562
circumference: circumference,
5563
outerRadius: outerRadius,
5564
innerRadius: innerRadius,
5565
label: helpers$1.valueAtIndexOrDefault(dataset.label, index, chart.data.labels[index])
5566
}
5567
});
5568
5569
var model = arc._model;
5570
5571
// Set correct angles if not resetting
5572
if (!reset || !animationOpts.animateRotate) {
5573
if (index === 0) {
5574
model.startAngle = opts.rotation;
5575
} else {
5576
model.startAngle = me.getMeta().data[index - 1]._model.endAngle;
5577
}
5578
5579
model.endAngle = model.startAngle + model.circumference;
5580
}
5581
5582
arc.pivot();
5583
},
5584
5585
calculateTotal: function() {
5586
var dataset = this.getDataset();
5587
var meta = this.getMeta();
5588
var total = 0;
5589
var value;
5590
5591
helpers$1.each(meta.data, function(element, index) {
5592
value = dataset.data[index];
5593
if (!isNaN(value) && !element.hidden) {
5594
total += Math.abs(value);
5595
}
5596
});
5597
5598
/* if (total === 0) {
5599
total = NaN;
5600
}*/
5601
5602
return total;
5603
},
5604
5605
calculateCircumference: function(value) {
5606
var total = this.getMeta().total;
5607
if (total > 0 && !isNaN(value)) {
5608
return DOUBLE_PI$1 * (Math.abs(value) / total);
5609
}
5610
return 0;
5611
},
5612
5613
// gets the max border or hover width to properly scale pie charts
5614
getMaxBorderWidth: function(arcs) {
5615
var me = this;
5616
var max = 0;
5617
var chart = me.chart;
5618
var i, ilen, meta, arc, controller, options, borderWidth, hoverWidth;
5619
5620
if (!arcs) {
5621
// Find the outmost visible dataset
5622
for (i = 0, ilen = chart.data.datasets.length; i < ilen; ++i) {
5623
if (chart.isDatasetVisible(i)) {
5624
meta = chart.getDatasetMeta(i);
5625
arcs = meta.data;
5626
if (i !== me.index) {
5627
controller = meta.controller;
5628
}
5629
break;
5630
}
5631
}
5632
}
5633
5634
if (!arcs) {
5635
return 0;
5636
}
5637
5638
for (i = 0, ilen = arcs.length; i < ilen; ++i) {
5639
arc = arcs[i];
5640
if (controller) {
5641
controller._configure();
5642
options = controller._resolveDataElementOptions(arc, i);
5643
} else {
5644
options = arc._options;
5645
}
5646
if (options.borderAlign !== 'inner') {
5647
borderWidth = options.borderWidth;
5648
hoverWidth = options.hoverBorderWidth;
5649
5650
max = borderWidth > max ? borderWidth : max;
5651
max = hoverWidth > max ? hoverWidth : max;
5652
}
5653
}
5654
return max;
5655
},
5656
5657
/**
5658
* @protected
5659
*/
5660
setHoverStyle: function(arc) {
5661
var model = arc._model;
5662
var options = arc._options;
5663
var getHoverColor = helpers$1.getHoverColor;
5664
5665
arc.$previousStyle = {
5666
backgroundColor: model.backgroundColor,
5667
borderColor: model.borderColor,
5668
borderWidth: model.borderWidth,
5669
};
5670
5671
model.backgroundColor = valueOrDefault$5(options.hoverBackgroundColor, getHoverColor(options.backgroundColor));
5672
model.borderColor = valueOrDefault$5(options.hoverBorderColor, getHoverColor(options.borderColor));
5673
model.borderWidth = valueOrDefault$5(options.hoverBorderWidth, options.borderWidth);
5674
},
5675
5676
/**
5677
* Get radius length offset of the dataset in relation to the visible datasets weights. This allows determining the inner and outer radius correctly
5678
* @private
5679
*/
5680
_getRingWeightOffset: function(datasetIndex) {
5681
var ringWeightOffset = 0;
5682
5683
for (var i = 0; i < datasetIndex; ++i) {
5684
if (this.chart.isDatasetVisible(i)) {
5685
ringWeightOffset += this._getRingWeight(i);
5686
}
5687
}
5688
5689
return ringWeightOffset;
5690
},
5691
5692
/**
5693
* @private
5694
*/
5695
_getRingWeight: function(dataSetIndex) {
5696
return Math.max(valueOrDefault$5(this.chart.data.datasets[dataSetIndex].weight, 1), 0);
5697
},
5698
5699
/**
5700
* Returns the sum of all visibile data set weights. This value can be 0.
5701
* @private
5702
*/
5703
_getVisibleDatasetWeightTotal: function() {
5704
return this._getRingWeightOffset(this.chart.data.datasets.length);
5705
}
5706
});
5707
5708
core_defaults._set('horizontalBar', {
5709
hover: {
5710
mode: 'index',
5711
axis: 'y'
5712
},
5713
5714
scales: {
5715
xAxes: [{
5716
type: 'linear',
5717
position: 'bottom'
5718
}],
5719
5720
yAxes: [{
5721
type: 'category',
5722
position: 'left',
5723
offset: true,
5724
gridLines: {
5725
offsetGridLines: true
5726
}
5727
}]
5728
},
5729
5730
elements: {
5731
rectangle: {
5732
borderSkipped: 'left'
5733
}
5734
},
5735
5736
tooltips: {
5737
mode: 'index',
5738
axis: 'y'
5739
}
5740
});
5741
5742
core_defaults._set('global', {
5743
datasets: {
5744
horizontalBar: {
5745
categoryPercentage: 0.8,
5746
barPercentage: 0.9
5747
}
5748
}
5749
});
5750
5751
var controller_horizontalBar = controller_bar.extend({
5752
/**
5753
* @private
5754
*/
5755
_getValueScaleId: function() {
5756
return this.getMeta().xAxisID;
5757
},
5758
5759
/**
5760
* @private
5761
*/
5762
_getIndexScaleId: function() {
5763
return this.getMeta().yAxisID;
5764
}
5765
});
5766
5767
var valueOrDefault$6 = helpers$1.valueOrDefault;
5768
var resolve$2 = helpers$1.options.resolve;
5769
var isPointInArea = helpers$1.canvas._isPointInArea;
5770
5771
core_defaults._set('line', {
5772
showLines: true,
5773
spanGaps: false,
5774
5775
hover: {
5776
mode: 'label'
5777
},
5778
5779
scales: {
5780
xAxes: [{
5781
type: 'category',
5782
id: 'x-axis-0'
5783
}],
5784
yAxes: [{
5785
type: 'linear',
5786
id: 'y-axis-0'
5787
}]
5788
}
5789
});
5790
5791
function scaleClip(scale, halfBorderWidth) {
5792
var tickOpts = scale && scale.options.ticks || {};
5793
var reverse = tickOpts.reverse;
5794
var min = tickOpts.min === undefined ? halfBorderWidth : 0;
5795
var max = tickOpts.max === undefined ? halfBorderWidth : 0;
5796
return {
5797
start: reverse ? max : min,
5798
end: reverse ? min : max
5799
};
5800
}
5801
5802
function defaultClip(xScale, yScale, borderWidth) {
5803
var halfBorderWidth = borderWidth / 2;
5804
var x = scaleClip(xScale, halfBorderWidth);
5805
var y = scaleClip(yScale, halfBorderWidth);
5806
5807
return {
5808
top: y.end,
5809
right: x.end,
5810
bottom: y.start,
5811
left: x.start
5812
};
5813
}
5814
5815
function toClip(value) {
5816
var t, r, b, l;
5817
5818
if (helpers$1.isObject(value)) {
5819
t = value.top;
5820
r = value.right;
5821
b = value.bottom;
5822
l = value.left;
5823
} else {
5824
t = r = b = l = value;
5825
}
5826
5827
return {
5828
top: t,
5829
right: r,
5830
bottom: b,
5831
left: l
5832
};
5833
}
5834
5835
5836
var controller_line = core_datasetController.extend({
5837
5838
datasetElementType: elements.Line,
5839
5840
dataElementType: elements.Point,
5841
5842
/**
5843
* @private
5844
*/
5845
_datasetElementOptions: [
5846
'backgroundColor',
5847
'borderCapStyle',
5848
'borderColor',
5849
'borderDash',
5850
'borderDashOffset',
5851
'borderJoinStyle',
5852
'borderWidth',
5853
'cubicInterpolationMode',
5854
'fill'
5855
],
5856
5857
/**
5858
* @private
5859
*/
5860
_dataElementOptions: {
5861
backgroundColor: 'pointBackgroundColor',
5862
borderColor: 'pointBorderColor',
5863
borderWidth: 'pointBorderWidth',
5864
hitRadius: 'pointHitRadius',
5865
hoverBackgroundColor: 'pointHoverBackgroundColor',
5866
hoverBorderColor: 'pointHoverBorderColor',
5867
hoverBorderWidth: 'pointHoverBorderWidth',
5868
hoverRadius: 'pointHoverRadius',
5869
pointStyle: 'pointStyle',
5870
radius: 'pointRadius',
5871
rotation: 'pointRotation'
5872
},
5873
5874
update: function(reset) {
5875
var me = this;
5876
var meta = me.getMeta();
5877
var line = meta.dataset;
5878
var points = meta.data || [];
5879
var options = me.chart.options;
5880
var config = me._config;
5881
var showLine = me._showLine = valueOrDefault$6(config.showLine, options.showLines);
5882
var i, ilen;
5883
5884
me._xScale = me.getScaleForId(meta.xAxisID);
5885
me._yScale = me.getScaleForId(meta.yAxisID);
5886
5887
// Update Line
5888
if (showLine) {
5889
// Compatibility: If the properties are defined with only the old name, use those values
5890
if (config.tension !== undefined && config.lineTension === undefined) {
5891
config.lineTension = config.tension;
5892
}
5893
5894
// Utility
5895
line._scale = me._yScale;
5896
line._datasetIndex = me.index;
5897
// Data
5898
line._children = points;
5899
// Model
5900
line._model = me._resolveDatasetElementOptions(line);
5901
5902
line.pivot();
5903
}
5904
5905
// Update Points
5906
for (i = 0, ilen = points.length; i < ilen; ++i) {
5907
me.updateElement(points[i], i, reset);
5908
}
5909
5910
if (showLine && line._model.tension !== 0) {
5911
me.updateBezierControlPoints();
5912
}
5913
5914
// Now pivot the point for animation
5915
for (i = 0, ilen = points.length; i < ilen; ++i) {
5916
points[i].pivot();
5917
}
5918
},
5919
5920
updateElement: function(point, index, reset) {
5921
var me = this;
5922
var meta = me.getMeta();
5923
var custom = point.custom || {};
5924
var dataset = me.getDataset();
5925
var datasetIndex = me.index;
5926
var value = dataset.data[index];
5927
var xScale = me._xScale;
5928
var yScale = me._yScale;
5929
var lineModel = meta.dataset._model;
5930
var x, y;
5931
5932
var options = me._resolveDataElementOptions(point, index);
5933
5934
x = xScale.getPixelForValue(typeof value === 'object' ? value : NaN, index, datasetIndex);
5935
y = reset ? yScale.getBasePixel() : me.calculatePointY(value, index, datasetIndex);
5936
5937
// Utility
5938
point._xScale = xScale;
5939
point._yScale = yScale;
5940
point._options = options;
5941
point._datasetIndex = datasetIndex;
5942
point._index = index;
5943
5944
// Desired view properties
5945
point._model = {
5946
x: x,
5947
y: y,
5948
skip: custom.skip || isNaN(x) || isNaN(y),
5949
// Appearance
5950
radius: options.radius,
5951
pointStyle: options.pointStyle,
5952
rotation: options.rotation,
5953
backgroundColor: options.backgroundColor,
5954
borderColor: options.borderColor,
5955
borderWidth: options.borderWidth,
5956
tension: valueOrDefault$6(custom.tension, lineModel ? lineModel.tension : 0),
5957
steppedLine: lineModel ? lineModel.steppedLine : false,
5958
// Tooltip
5959
hitRadius: options.hitRadius
5960
};
5961
},
5962
5963
/**
5964
* @private
5965
*/
5966
_resolveDatasetElementOptions: function(element) {
5967
var me = this;
5968
var config = me._config;
5969
var custom = element.custom || {};
5970
var options = me.chart.options;
5971
var lineOptions = options.elements.line;
5972
var values = core_datasetController.prototype._resolveDatasetElementOptions.apply(me, arguments);
5973
5974
// The default behavior of lines is to break at null values, according
5975
// to https://github.com/chartjs/Chart.js/issues/2435#issuecomment-216718158
5976
// This option gives lines the ability to span gaps
5977
values.spanGaps = valueOrDefault$6(config.spanGaps, options.spanGaps);
5978
values.tension = valueOrDefault$6(config.lineTension, lineOptions.tension);
5979
values.steppedLine = resolve$2([custom.steppedLine, config.steppedLine, lineOptions.stepped]);
5980
values.clip = toClip(valueOrDefault$6(config.clip, defaultClip(me._xScale, me._yScale, values.borderWidth)));
5981
5982
return values;
5983
},
5984
5985
calculatePointY: function(value, index, datasetIndex) {
5986
var me = this;
5987
var chart = me.chart;
5988
var yScale = me._yScale;
5989
var sumPos = 0;
5990
var sumNeg = 0;
5991
var i, ds, dsMeta, stackedRightValue, rightValue, metasets, ilen;
5992
5993
if (yScale.options.stacked) {
5994
rightValue = +yScale.getRightValue(value);
5995
metasets = chart._getSortedVisibleDatasetMetas();
5996
ilen = metasets.length;
5997
5998
for (i = 0; i < ilen; ++i) {
5999
dsMeta = metasets[i];
6000
if (dsMeta.index === datasetIndex) {
6001
break;
6002
}
6003
6004
ds = chart.data.datasets[dsMeta.index];
6005
if (dsMeta.type === 'line' && dsMeta.yAxisID === yScale.id) {
6006
stackedRightValue = +yScale.getRightValue(ds.data[index]);
6007
if (stackedRightValue < 0) {
6008
sumNeg += stackedRightValue || 0;
6009
} else {
6010
sumPos += stackedRightValue || 0;
6011
}
6012
}
6013
}
6014
6015
if (rightValue < 0) {
6016
return yScale.getPixelForValue(sumNeg + rightValue);
6017
}
6018
return yScale.getPixelForValue(sumPos + rightValue);
6019
}
6020
return yScale.getPixelForValue(value);
6021
},
6022
6023
updateBezierControlPoints: function() {
6024
var me = this;
6025
var chart = me.chart;
6026
var meta = me.getMeta();
6027
var lineModel = meta.dataset._model;
6028
var area = chart.chartArea;
6029
var points = meta.data || [];
6030
var i, ilen, model, controlPoints;
6031
6032
// Only consider points that are drawn in case the spanGaps option is used
6033
if (lineModel.spanGaps) {
6034
points = points.filter(function(pt) {
6035
return !pt._model.skip;
6036
});
6037
}
6038
6039
function capControlPoint(pt, min, max) {
6040
return Math.max(Math.min(pt, max), min);
6041
}
6042
6043
if (lineModel.cubicInterpolationMode === 'monotone') {
6044
helpers$1.splineCurveMonotone(points);
6045
} else {
6046
for (i = 0, ilen = points.length; i < ilen; ++i) {
6047
model = points[i]._model;
6048
controlPoints = helpers$1.splineCurve(
6049
helpers$1.previousItem(points, i)._model,
6050
model,
6051
helpers$1.nextItem(points, i)._model,
6052
lineModel.tension
6053
);
6054
model.controlPointPreviousX = controlPoints.previous.x;
6055
model.controlPointPreviousY = controlPoints.previous.y;
6056
model.controlPointNextX = controlPoints.next.x;
6057
model.controlPointNextY = controlPoints.next.y;
6058
}
6059
}
6060
6061
if (chart.options.elements.line.capBezierPoints) {
6062
for (i = 0, ilen = points.length; i < ilen; ++i) {
6063
model = points[i]._model;
6064
if (isPointInArea(model, area)) {
6065
if (i > 0 && isPointInArea(points[i - 1]._model, area)) {
6066
model.controlPointPreviousX = capControlPoint(model.controlPointPreviousX, area.left, area.right);
6067
model.controlPointPreviousY = capControlPoint(model.controlPointPreviousY, area.top, area.bottom);
6068
}
6069
if (i < points.length - 1 && isPointInArea(points[i + 1]._model, area)) {
6070
model.controlPointNextX = capControlPoint(model.controlPointNextX, area.left, area.right);
6071
model.controlPointNextY = capControlPoint(model.controlPointNextY, area.top, area.bottom);
6072
}
6073
}
6074
}
6075
}
6076
},
6077
6078
draw: function() {
6079
var me = this;
6080
var chart = me.chart;
6081
var meta = me.getMeta();
6082
var points = meta.data || [];
6083
var area = chart.chartArea;
6084
var canvas = chart.canvas;
6085
var i = 0;
6086
var ilen = points.length;
6087
var clip;
6088
6089
if (me._showLine) {
6090
clip = meta.dataset._model.clip;
6091
6092
helpers$1.canvas.clipArea(chart.ctx, {
6093
left: clip.left === false ? 0 : area.left - clip.left,
6094
right: clip.right === false ? canvas.width : area.right + clip.right,
6095
top: clip.top === false ? 0 : area.top - clip.top,
6096
bottom: clip.bottom === false ? canvas.height : area.bottom + clip.bottom
6097
});
6098
6099
meta.dataset.draw();
6100
6101
helpers$1.canvas.unclipArea(chart.ctx);
6102
}
6103
6104
// Draw the points
6105
for (; i < ilen; ++i) {
6106
points[i].draw(area);
6107
}
6108
},
6109
6110
/**
6111
* @protected
6112
*/
6113
setHoverStyle: function(point) {
6114
var model = point._model;
6115
var options = point._options;
6116
var getHoverColor = helpers$1.getHoverColor;
6117
6118
point.$previousStyle = {
6119
backgroundColor: model.backgroundColor,
6120
borderColor: model.borderColor,
6121
borderWidth: model.borderWidth,
6122
radius: model.radius
6123
};
6124
6125
model.backgroundColor = valueOrDefault$6(options.hoverBackgroundColor, getHoverColor(options.backgroundColor));
6126
model.borderColor = valueOrDefault$6(options.hoverBorderColor, getHoverColor(options.borderColor));
6127
model.borderWidth = valueOrDefault$6(options.hoverBorderWidth, options.borderWidth);
6128
model.radius = valueOrDefault$6(options.hoverRadius, options.radius);
6129
},
6130
});
6131
6132
var resolve$3 = helpers$1.options.resolve;
6133
6134
core_defaults._set('polarArea', {
6135
scale: {
6136
type: 'radialLinear',
6137
angleLines: {
6138
display: false
6139
},
6140
gridLines: {
6141
circular: true
6142
},
6143
pointLabels: {
6144
display: false
6145
},
6146
ticks: {
6147
beginAtZero: true
6148
}
6149
},
6150
6151
// Boolean - Whether to animate the rotation of the chart
6152
animation: {
6153
animateRotate: true,
6154
animateScale: true
6155
},
6156
6157
startAngle: -0.5 * Math.PI,
6158
legendCallback: function(chart) {
6159
var list = document.createElement('ul');
6160
var data = chart.data;
6161
var datasets = data.datasets;
6162
var labels = data.labels;
6163
var i, ilen, listItem, listItemSpan;
6164
6165
list.setAttribute('class', chart.id + '-legend');
6166
if (datasets.length) {
6167
for (i = 0, ilen = datasets[0].data.length; i < ilen; ++i) {
6168
listItem = list.appendChild(document.createElement('li'));
6169
listItemSpan = listItem.appendChild(document.createElement('span'));
6170
listItemSpan.style.backgroundColor = datasets[0].backgroundColor[i];
6171
if (labels[i]) {
6172
listItem.appendChild(document.createTextNode(labels[i]));
6173
}
6174
}
6175
}
6176
6177
return list.outerHTML;
6178
},
6179
legend: {
6180
labels: {
6181
generateLabels: function(chart) {
6182
var data = chart.data;
6183
if (data.labels.length && data.datasets.length) {
6184
return data.labels.map(function(label, i) {
6185
var meta = chart.getDatasetMeta(0);
6186
var style = meta.controller.getStyle(i);
6187
6188
return {
6189
text: label,
6190
fillStyle: style.backgroundColor,
6191
strokeStyle: style.borderColor,
6192
lineWidth: style.borderWidth,
6193
hidden: isNaN(data.datasets[0].data[i]) || meta.data[i].hidden,
6194
6195
// Extra data used for toggling the correct item
6196
index: i
6197
};
6198
});
6199
}
6200
return [];
6201
}
6202
},
6203
6204
onClick: function(e, legendItem) {
6205
var index = legendItem.index;
6206
var chart = this.chart;
6207
var i, ilen, meta;
6208
6209
for (i = 0, ilen = (chart.data.datasets || []).length; i < ilen; ++i) {
6210
meta = chart.getDatasetMeta(i);
6211
meta.data[index].hidden = !meta.data[index].hidden;
6212
}
6213
6214
chart.update();
6215
}
6216
},
6217
6218
// Need to override these to give a nice default
6219
tooltips: {
6220
callbacks: {
6221
title: function() {
6222
return '';
6223
},
6224
label: function(item, data) {
6225
return data.labels[item.index] + ': ' + item.yLabel;
6226
}
6227
}
6228
}
6229
});
6230
6231
var controller_polarArea = core_datasetController.extend({
6232
6233
dataElementType: elements.Arc,
6234
6235
linkScales: helpers$1.noop,
6236
6237
/**
6238
* @private
6239
*/
6240
_dataElementOptions: [
6241
'backgroundColor',
6242
'borderColor',
6243
'borderWidth',
6244
'borderAlign',
6245
'hoverBackgroundColor',
6246
'hoverBorderColor',
6247
'hoverBorderWidth',
6248
],
6249
6250
/**
6251
* @private
6252
*/
6253
_getIndexScaleId: function() {
6254
return this.chart.scale.id;
6255
},
6256
6257
/**
6258
* @private
6259
*/
6260
_getValueScaleId: function() {
6261
return this.chart.scale.id;
6262
},
6263
6264
update: function(reset) {
6265
var me = this;
6266
var dataset = me.getDataset();
6267
var meta = me.getMeta();
6268
var start = me.chart.options.startAngle || 0;
6269
var starts = me._starts = [];
6270
var angles = me._angles = [];
6271
var arcs = meta.data;
6272
var i, ilen, angle;
6273
6274
me._updateRadius();
6275
6276
meta.count = me.countVisibleElements();
6277
6278
for (i = 0, ilen = dataset.data.length; i < ilen; i++) {
6279
starts[i] = start;
6280
angle = me._computeAngle(i);
6281
angles[i] = angle;
6282
start += angle;
6283
}
6284
6285
for (i = 0, ilen = arcs.length; i < ilen; ++i) {
6286
arcs[i]._options = me._resolveDataElementOptions(arcs[i], i);
6287
me.updateElement(arcs[i], i, reset);
6288
}
6289
},
6290
6291
/**
6292
* @private
6293
*/
6294
_updateRadius: function() {
6295
var me = this;
6296
var chart = me.chart;
6297
var chartArea = chart.chartArea;
6298
var opts = chart.options;
6299
var minSize = Math.min(chartArea.right - chartArea.left, chartArea.bottom - chartArea.top);
6300
6301
chart.outerRadius = Math.max(minSize / 2, 0);
6302
chart.innerRadius = Math.max(opts.cutoutPercentage ? (chart.outerRadius / 100) * (opts.cutoutPercentage) : 1, 0);
6303
chart.radiusLength = (chart.outerRadius - chart.innerRadius) / chart.getVisibleDatasetCount();
6304
6305
me.outerRadius = chart.outerRadius - (chart.radiusLength * me.index);
6306
me.innerRadius = me.outerRadius - chart.radiusLength;
6307
},
6308
6309
updateElement: function(arc, index, reset) {
6310
var me = this;
6311
var chart = me.chart;
6312
var dataset = me.getDataset();
6313
var opts = chart.options;
6314
var animationOpts = opts.animation;
6315
var scale = chart.scale;
6316
var labels = chart.data.labels;
6317
6318
var centerX = scale.xCenter;
6319
var centerY = scale.yCenter;
6320
6321
// var negHalfPI = -0.5 * Math.PI;
6322
var datasetStartAngle = opts.startAngle;
6323
var distance = arc.hidden ? 0 : scale.getDistanceFromCenterForValue(dataset.data[index]);
6324
var startAngle = me._starts[index];
6325
var endAngle = startAngle + (arc.hidden ? 0 : me._angles[index]);
6326
6327
var resetRadius = animationOpts.animateScale ? 0 : scale.getDistanceFromCenterForValue(dataset.data[index]);
6328
var options = arc._options || {};
6329
6330
helpers$1.extend(arc, {
6331
// Utility
6332
_datasetIndex: me.index,
6333
_index: index,
6334
_scale: scale,
6335
6336
// Desired view properties
6337
_model: {
6338
backgroundColor: options.backgroundColor,
6339
borderColor: options.borderColor,
6340
borderWidth: options.borderWidth,
6341
borderAlign: options.borderAlign,
6342
x: centerX,
6343
y: centerY,
6344
innerRadius: 0,
6345
outerRadius: reset ? resetRadius : distance,
6346
startAngle: reset && animationOpts.animateRotate ? datasetStartAngle : startAngle,
6347
endAngle: reset && animationOpts.animateRotate ? datasetStartAngle : endAngle,
6348
label: helpers$1.valueAtIndexOrDefault(labels, index, labels[index])
6349
}
6350
});
6351
6352
arc.pivot();
6353
},
6354
6355
countVisibleElements: function() {
6356
var dataset = this.getDataset();
6357
var meta = this.getMeta();
6358
var count = 0;
6359
6360
helpers$1.each(meta.data, function(element, index) {
6361
if (!isNaN(dataset.data[index]) && !element.hidden) {
6362
count++;
6363
}
6364
});
6365
6366
return count;
6367
},
6368
6369
/**
6370
* @protected
6371
*/
6372
setHoverStyle: function(arc) {
6373
var model = arc._model;
6374
var options = arc._options;
6375
var getHoverColor = helpers$1.getHoverColor;
6376
var valueOrDefault = helpers$1.valueOrDefault;
6377
6378
arc.$previousStyle = {
6379
backgroundColor: model.backgroundColor,
6380
borderColor: model.borderColor,
6381
borderWidth: model.borderWidth,
6382
};
6383
6384
model.backgroundColor = valueOrDefault(options.hoverBackgroundColor, getHoverColor(options.backgroundColor));
6385
model.borderColor = valueOrDefault(options.hoverBorderColor, getHoverColor(options.borderColor));
6386
model.borderWidth = valueOrDefault(options.hoverBorderWidth, options.borderWidth);
6387
},
6388
6389
/**
6390
* @private
6391
*/
6392
_computeAngle: function(index) {
6393
var me = this;
6394
var count = this.getMeta().count;
6395
var dataset = me.getDataset();
6396
var meta = me.getMeta();
6397
6398
if (isNaN(dataset.data[index]) || meta.data[index].hidden) {
6399
return 0;
6400
}
6401
6402
// Scriptable options
6403
var context = {
6404
chart: me.chart,
6405
dataIndex: index,
6406
dataset: dataset,
6407
datasetIndex: me.index
6408
};
6409
6410
return resolve$3([
6411
me.chart.options.elements.arc.angle,
6412
(2 * Math.PI) / count
6413
], context, index);
6414
}
6415
});
6416
6417
core_defaults._set('pie', helpers$1.clone(core_defaults.doughnut));
6418
core_defaults._set('pie', {
6419
cutoutPercentage: 0
6420
});
6421
6422
// Pie charts are Doughnut chart with different defaults
6423
var controller_pie = controller_doughnut;
6424
6425
var valueOrDefault$7 = helpers$1.valueOrDefault;
6426
6427
core_defaults._set('radar', {
6428
spanGaps: false,
6429
scale: {
6430
type: 'radialLinear'
6431
},
6432
elements: {
6433
line: {
6434
fill: 'start',
6435
tension: 0 // no bezier in radar
6436
}
6437
}
6438
});
6439
6440
var controller_radar = core_datasetController.extend({
6441
datasetElementType: elements.Line,
6442
6443
dataElementType: elements.Point,
6444
6445
linkScales: helpers$1.noop,
6446
6447
/**
6448
* @private
6449
*/
6450
_datasetElementOptions: [
6451
'backgroundColor',
6452
'borderWidth',
6453
'borderColor',
6454
'borderCapStyle',
6455
'borderDash',
6456
'borderDashOffset',
6457
'borderJoinStyle',
6458
'fill'
6459
],
6460
6461
/**
6462
* @private
6463
*/
6464
_dataElementOptions: {
6465
backgroundColor: 'pointBackgroundColor',
6466
borderColor: 'pointBorderColor',
6467
borderWidth: 'pointBorderWidth',
6468
hitRadius: 'pointHitRadius',
6469
hoverBackgroundColor: 'pointHoverBackgroundColor',
6470
hoverBorderColor: 'pointHoverBorderColor',
6471
hoverBorderWidth: 'pointHoverBorderWidth',
6472
hoverRadius: 'pointHoverRadius',
6473
pointStyle: 'pointStyle',
6474
radius: 'pointRadius',
6475
rotation: 'pointRotation'
6476
},
6477
6478
/**
6479
* @private
6480
*/
6481
_getIndexScaleId: function() {
6482
return this.chart.scale.id;
6483
},
6484
6485
/**
6486
* @private
6487
*/
6488
_getValueScaleId: function() {
6489
return this.chart.scale.id;
6490
},
6491
6492
update: function(reset) {
6493
var me = this;
6494
var meta = me.getMeta();
6495
var line = meta.dataset;
6496
var points = meta.data || [];
6497
var scale = me.chart.scale;
6498
var config = me._config;
6499
var i, ilen;
6500
6501
// Compatibility: If the properties are defined with only the old name, use those values
6502
if (config.tension !== undefined && config.lineTension === undefined) {
6503
config.lineTension = config.tension;
6504
}
6505
6506
// Utility
6507
line._scale = scale;
6508
line._datasetIndex = me.index;
6509
// Data
6510
line._children = points;
6511
line._loop = true;
6512
// Model
6513
line._model = me._resolveDatasetElementOptions(line);
6514
6515
line.pivot();
6516
6517
// Update Points
6518
for (i = 0, ilen = points.length; i < ilen; ++i) {
6519
me.updateElement(points[i], i, reset);
6520
}
6521
6522
// Update bezier control points
6523
me.updateBezierControlPoints();
6524
6525
// Now pivot the point for animation
6526
for (i = 0, ilen = points.length; i < ilen; ++i) {
6527
points[i].pivot();
6528
}
6529
},
6530
6531
updateElement: function(point, index, reset) {
6532
var me = this;
6533
var custom = point.custom || {};
6534
var dataset = me.getDataset();
6535
var scale = me.chart.scale;
6536
var pointPosition = scale.getPointPositionForValue(index, dataset.data[index]);
6537
var options = me._resolveDataElementOptions(point, index);
6538
var lineModel = me.getMeta().dataset._model;
6539
var x = reset ? scale.xCenter : pointPosition.x;
6540
var y = reset ? scale.yCenter : pointPosition.y;
6541
6542
// Utility
6543
point._scale = scale;
6544
point._options = options;
6545
point._datasetIndex = me.index;
6546
point._index = index;
6547
6548
// Desired view properties
6549
point._model = {
6550
x: x, // value not used in dataset scale, but we want a consistent API between scales
6551
y: y,
6552
skip: custom.skip || isNaN(x) || isNaN(y),
6553
// Appearance
6554
radius: options.radius,
6555
pointStyle: options.pointStyle,
6556
rotation: options.rotation,
6557
backgroundColor: options.backgroundColor,
6558
borderColor: options.borderColor,
6559
borderWidth: options.borderWidth,
6560
tension: valueOrDefault$7(custom.tension, lineModel ? lineModel.tension : 0),
6561
6562
// Tooltip
6563
hitRadius: options.hitRadius
6564
};
6565
},
6566
6567
/**
6568
* @private
6569
*/
6570
_resolveDatasetElementOptions: function() {
6571
var me = this;
6572
var config = me._config;
6573
var options = me.chart.options;
6574
var values = core_datasetController.prototype._resolveDatasetElementOptions.apply(me, arguments);
6575
6576
values.spanGaps = valueOrDefault$7(config.spanGaps, options.spanGaps);
6577
values.tension = valueOrDefault$7(config.lineTension, options.elements.line.tension);
6578
6579
return values;
6580
},
6581
6582
updateBezierControlPoints: function() {
6583
var me = this;
6584
var meta = me.getMeta();
6585
var area = me.chart.chartArea;
6586
var points = meta.data || [];
6587
var i, ilen, model, controlPoints;
6588
6589
// Only consider points that are drawn in case the spanGaps option is used
6590
if (meta.dataset._model.spanGaps) {
6591
points = points.filter(function(pt) {
6592
return !pt._model.skip;
6593
});
6594
}
6595
6596
function capControlPoint(pt, min, max) {
6597
return Math.max(Math.min(pt, max), min);
6598
}
6599
6600
for (i = 0, ilen = points.length; i < ilen; ++i) {
6601
model = points[i]._model;
6602
controlPoints = helpers$1.splineCurve(
6603
helpers$1.previousItem(points, i, true)._model,
6604
model,
6605
helpers$1.nextItem(points, i, true)._model,
6606
model.tension
6607
);
6608
6609
// Prevent the bezier going outside of the bounds of the graph
6610
model.controlPointPreviousX = capControlPoint(controlPoints.previous.x, area.left, area.right);
6611
model.controlPointPreviousY = capControlPoint(controlPoints.previous.y, area.top, area.bottom);
6612
model.controlPointNextX = capControlPoint(controlPoints.next.x, area.left, area.right);
6613
model.controlPointNextY = capControlPoint(controlPoints.next.y, area.top, area.bottom);
6614
}
6615
},
6616
6617
setHoverStyle: function(point) {
6618
var model = point._model;
6619
var options = point._options;
6620
var getHoverColor = helpers$1.getHoverColor;
6621
6622
point.$previousStyle = {
6623
backgroundColor: model.backgroundColor,
6624
borderColor: model.borderColor,
6625
borderWidth: model.borderWidth,
6626
radius: model.radius
6627
};
6628
6629
model.backgroundColor = valueOrDefault$7(options.hoverBackgroundColor, getHoverColor(options.backgroundColor));
6630
model.borderColor = valueOrDefault$7(options.hoverBorderColor, getHoverColor(options.borderColor));
6631
model.borderWidth = valueOrDefault$7(options.hoverBorderWidth, options.borderWidth);
6632
model.radius = valueOrDefault$7(options.hoverRadius, options.radius);
6633
}
6634
});
6635
6636
core_defaults._set('scatter', {
6637
hover: {
6638
mode: 'single'
6639
},
6640
6641
scales: {
6642
xAxes: [{
6643
id: 'x-axis-1', // need an ID so datasets can reference the scale
6644
type: 'linear', // scatter should not use a category axis
6645
position: 'bottom'
6646
}],
6647
yAxes: [{
6648
id: 'y-axis-1',
6649
type: 'linear',
6650
position: 'left'
6651
}]
6652
},
6653
6654
tooltips: {
6655
callbacks: {
6656
title: function() {
6657
return ''; // doesn't make sense for scatter since data are formatted as a point
6658
},
6659
label: function(item) {
6660
return '(' + item.xLabel + ', ' + item.yLabel + ')';
6661
}
6662
}
6663
}
6664
});
6665
6666
core_defaults._set('global', {
6667
datasets: {
6668
scatter: {
6669
showLine: false
6670
}
6671
}
6672
});
6673
6674
// Scatter charts use line controllers
6675
var controller_scatter = controller_line;
6676
6677
// NOTE export a map in which the key represents the controller type, not
6678
// the class, and so must be CamelCase in order to be correctly retrieved
6679
// by the controller in core.controller.js (`controllers[meta.type]`).
6680
6681
var controllers = {
6682
bar: controller_bar,
6683
bubble: controller_bubble,
6684
doughnut: controller_doughnut,
6685
horizontalBar: controller_horizontalBar,
6686
line: controller_line,
6687
polarArea: controller_polarArea,
6688
pie: controller_pie,
6689
radar: controller_radar,
6690
scatter: controller_scatter
6691
};
6692
6693
/**
6694
* Helper function to get relative position for an event
6695
* @param {Event|IEvent} event - The event to get the position for
6696
* @param {Chart} chart - The chart
6697
* @returns {object} the event position
6698
*/
6699
function getRelativePosition(e, chart) {
6700
if (e.native) {
6701
return {
6702
x: e.x,
6703
y: e.y
6704
};
6705
}
6706
6707
return helpers$1.getRelativePosition(e, chart);
6708
}
6709
6710
/**
6711
* Helper function to traverse all of the visible elements in the chart
6712
* @param {Chart} chart - the chart
6713
* @param {function} handler - the callback to execute for each visible item
6714
*/
6715
function parseVisibleItems(chart, handler) {
6716
var metasets = chart._getSortedVisibleDatasetMetas();
6717
var metadata, i, j, ilen, jlen, element;
6718
6719
for (i = 0, ilen = metasets.length; i < ilen; ++i) {
6720
metadata = metasets[i].data;
6721
for (j = 0, jlen = metadata.length; j < jlen; ++j) {
6722
element = metadata[j];
6723
if (!element._view.skip) {
6724
handler(element);
6725
}
6726
}
6727
}
6728
}
6729
6730
/**
6731
* Helper function to get the items that intersect the event position
6732
* @param {ChartElement[]} items - elements to filter
6733
* @param {object} position - the point to be nearest to
6734
* @return {ChartElement[]} the nearest items
6735
*/
6736
function getIntersectItems(chart, position) {
6737
var elements = [];
6738
6739
parseVisibleItems(chart, function(element) {
6740
if (element.inRange(position.x, position.y)) {
6741
elements.push(element);
6742
}
6743
});
6744
6745
return elements;
6746
}
6747
6748
/**
6749
* Helper function to get the items nearest to the event position considering all visible items in teh chart
6750
* @param {Chart} chart - the chart to look at elements from
6751
* @param {object} position - the point to be nearest to
6752
* @param {boolean} intersect - if true, only consider items that intersect the position
6753
* @param {function} distanceMetric - function to provide the distance between points
6754
* @return {ChartElement[]} the nearest items
6755
*/
6756
function getNearestItems(chart, position, intersect, distanceMetric) {
6757
var minDistance = Number.POSITIVE_INFINITY;
6758
var nearestItems = [];
6759
6760
parseVisibleItems(chart, function(element) {
6761
if (intersect && !element.inRange(position.x, position.y)) {
6762
return;
6763
}
6764
6765
var center = element.getCenterPoint();
6766
var distance = distanceMetric(position, center);
6767
if (distance < minDistance) {
6768
nearestItems = [element];
6769
minDistance = distance;
6770
} else if (distance === minDistance) {
6771
// Can have multiple items at the same distance in which case we sort by size
6772
nearestItems.push(element);
6773
}
6774
});
6775
6776
return nearestItems;
6777
}
6778
6779
/**
6780
* Get a distance metric function for two points based on the
6781
* axis mode setting
6782
* @param {string} axis - the axis mode. x|y|xy
6783
*/
6784
function getDistanceMetricForAxis(axis) {
6785
var useX = axis.indexOf('x') !== -1;
6786
var useY = axis.indexOf('y') !== -1;
6787
6788
return function(pt1, pt2) {
6789
var deltaX = useX ? Math.abs(pt1.x - pt2.x) : 0;
6790
var deltaY = useY ? Math.abs(pt1.y - pt2.y) : 0;
6791
return Math.sqrt(Math.pow(deltaX, 2) + Math.pow(deltaY, 2));
6792
};
6793
}
6794
6795
function indexMode(chart, e, options) {
6796
var position = getRelativePosition(e, chart);
6797
// Default axis for index mode is 'x' to match old behaviour
6798
options.axis = options.axis || 'x';
6799
var distanceMetric = getDistanceMetricForAxis(options.axis);
6800
var items = options.intersect ? getIntersectItems(chart, position) : getNearestItems(chart, position, false, distanceMetric);
6801
var elements = [];
6802
6803
if (!items.length) {
6804
return [];
6805
}
6806
6807
chart._getSortedVisibleDatasetMetas().forEach(function(meta) {
6808
var element = meta.data[items[0]._index];
6809
6810
// don't count items that are skipped (null data)
6811
if (element && !element._view.skip) {
6812
elements.push(element);
6813
}
6814
});
6815
6816
return elements;
6817
}
6818
6819
/**
6820
* @interface IInteractionOptions
6821
*/
6822
/**
6823
* If true, only consider items that intersect the point
6824
* @name IInterfaceOptions#boolean
6825
* @type Boolean
6826
*/
6827
6828
/**
6829
* Contains interaction related functions
6830
* @namespace Chart.Interaction
6831
*/
6832
var core_interaction = {
6833
// Helper function for different modes
6834
modes: {
6835
single: function(chart, e) {
6836
var position = getRelativePosition(e, chart);
6837
var elements = [];
6838
6839
parseVisibleItems(chart, function(element) {
6840
if (element.inRange(position.x, position.y)) {
6841
elements.push(element);
6842
return elements;
6843
}
6844
});
6845
6846
return elements.slice(0, 1);
6847
},
6848
6849
/**
6850
* @function Chart.Interaction.modes.label
6851
* @deprecated since version 2.4.0
6852
* @todo remove at version 3
6853
* @private
6854
*/
6855
label: indexMode,
6856
6857
/**
6858
* Returns items at the same index. If the options.intersect parameter is true, we only return items if we intersect something
6859
* If the options.intersect mode is false, we find the nearest item and return the items at the same index as that item
6860
* @function Chart.Interaction.modes.index
6861
* @since v2.4.0
6862
* @param {Chart} chart - the chart we are returning items from
6863
* @param {Event} e - the event we are find things at
6864
* @param {IInteractionOptions} options - options to use during interaction
6865
* @return {Chart.Element[]} Array of elements that are under the point. If none are found, an empty array is returned
6866
*/
6867
index: indexMode,
6868
6869
/**
6870
* Returns items in the same dataset. If the options.intersect parameter is true, we only return items if we intersect something
6871
* If the options.intersect is false, we find the nearest item and return the items in that dataset
6872
* @function Chart.Interaction.modes.dataset
6873
* @param {Chart} chart - the chart we are returning items from
6874
* @param {Event} e - the event we are find things at
6875
* @param {IInteractionOptions} options - options to use during interaction
6876
* @return {Chart.Element[]} Array of elements that are under the point. If none are found, an empty array is returned
6877
*/
6878
dataset: function(chart, e, options) {
6879
var position = getRelativePosition(e, chart);
6880
options.axis = options.axis || 'xy';
6881
var distanceMetric = getDistanceMetricForAxis(options.axis);
6882
var items = options.intersect ? getIntersectItems(chart, position) : getNearestItems(chart, position, false, distanceMetric);
6883
6884
if (items.length > 0) {
6885
items = chart.getDatasetMeta(items[0]._datasetIndex).data;
6886
}
6887
6888
return items;
6889
},
6890
6891
/**
6892
* @function Chart.Interaction.modes.x-axis
6893
* @deprecated since version 2.4.0. Use index mode and intersect == true
6894
* @todo remove at version 3
6895
* @private
6896
*/
6897
'x-axis': function(chart, e) {
6898
return indexMode(chart, e, {intersect: false});
6899
},
6900
6901
/**
6902
* Point mode returns all elements that hit test based on the event position
6903
* of the event
6904
* @function Chart.Interaction.modes.intersect
6905
* @param {Chart} chart - the chart we are returning items from
6906
* @param {Event} e - the event we are find things at
6907
* @return {Chart.Element[]} Array of elements that are under the point. If none are found, an empty array is returned
6908
*/
6909
point: function(chart, e) {
6910
var position = getRelativePosition(e, chart);
6911
return getIntersectItems(chart, position);
6912
},
6913
6914
/**
6915
* nearest mode returns the element closest to the point
6916
* @function Chart.Interaction.modes.intersect
6917
* @param {Chart} chart - the chart we are returning items from
6918
* @param {Event} e - the event we are find things at
6919
* @param {IInteractionOptions} options - options to use
6920
* @return {Chart.Element[]} Array of elements that are under the point. If none are found, an empty array is returned
6921
*/
6922
nearest: function(chart, e, options) {
6923
var position = getRelativePosition(e, chart);
6924
options.axis = options.axis || 'xy';
6925
var distanceMetric = getDistanceMetricForAxis(options.axis);
6926
return getNearestItems(chart, position, options.intersect, distanceMetric);
6927
},
6928
6929
/**
6930
* x mode returns the elements that hit-test at the current x coordinate
6931
* @function Chart.Interaction.modes.x
6932
* @param {Chart} chart - the chart we are returning items from
6933
* @param {Event} e - the event we are find things at
6934
* @param {IInteractionOptions} options - options to use
6935
* @return {Chart.Element[]} Array of elements that are under the point. If none are found, an empty array is returned
6936
*/
6937
x: function(chart, e, options) {
6938
var position = getRelativePosition(e, chart);
6939
var items = [];
6940
var intersectsItem = false;
6941
6942
parseVisibleItems(chart, function(element) {
6943
if (element.inXRange(position.x)) {
6944
items.push(element);
6945
}
6946
6947
if (element.inRange(position.x, position.y)) {
6948
intersectsItem = true;
6949
}
6950
});
6951
6952
// If we want to trigger on an intersect and we don't have any items
6953
// that intersect the position, return nothing
6954
if (options.intersect && !intersectsItem) {
6955
items = [];
6956
}
6957
return items;
6958
},
6959
6960
/**
6961
* y mode returns the elements that hit-test at the current y coordinate
6962
* @function Chart.Interaction.modes.y
6963
* @param {Chart} chart - the chart we are returning items from
6964
* @param {Event} e - the event we are find things at
6965
* @param {IInteractionOptions} options - options to use
6966
* @return {Chart.Element[]} Array of elements that are under the point. If none are found, an empty array is returned
6967
*/
6968
y: function(chart, e, options) {
6969
var position = getRelativePosition(e, chart);
6970
var items = [];
6971
var intersectsItem = false;
6972
6973
parseVisibleItems(chart, function(element) {
6974
if (element.inYRange(position.y)) {
6975
items.push(element);
6976
}
6977
6978
if (element.inRange(position.x, position.y)) {
6979
intersectsItem = true;
6980
}
6981
});
6982
6983
// If we want to trigger on an intersect and we don't have any items
6984
// that intersect the position, return nothing
6985
if (options.intersect && !intersectsItem) {
6986
items = [];
6987
}
6988
return items;
6989
}
6990
}
6991
};
6992
6993
var extend = helpers$1.extend;
6994
6995
function filterByPosition(array, position) {
6996
return helpers$1.where(array, function(v) {
6997
return v.pos === position;
6998
});
6999
}
7000
7001
function sortByWeight(array, reverse) {
7002
return array.sort(function(a, b) {
7003
var v0 = reverse ? b : a;
7004
var v1 = reverse ? a : b;
7005
return v0.weight === v1.weight ?
7006
v0.index - v1.index :
7007
v0.weight - v1.weight;
7008
});
7009
}
7010
7011
function wrapBoxes(boxes) {
7012
var layoutBoxes = [];
7013
var i, ilen, box;
7014
7015
for (i = 0, ilen = (boxes || []).length; i < ilen; ++i) {
7016
box = boxes[i];
7017
layoutBoxes.push({
7018
index: i,
7019
box: box,
7020
pos: box.position,
7021
horizontal: box.isHorizontal(),
7022
weight: box.weight
7023
});
7024
}
7025
return layoutBoxes;
7026
}
7027
7028
function setLayoutDims(layouts, params) {
7029
var i, ilen, layout;
7030
for (i = 0, ilen = layouts.length; i < ilen; ++i) {
7031
layout = layouts[i];
7032
// store width used instead of chartArea.w in fitBoxes
7033
layout.width = layout.horizontal
7034
? layout.box.fullWidth && params.availableWidth
7035
: params.vBoxMaxWidth;
7036
// store height used instead of chartArea.h in fitBoxes
7037
layout.height = layout.horizontal && params.hBoxMaxHeight;
7038
}
7039
}
7040
7041
function buildLayoutBoxes(boxes) {
7042
var layoutBoxes = wrapBoxes(boxes);
7043
var left = sortByWeight(filterByPosition(layoutBoxes, 'left'), true);
7044
var right = sortByWeight(filterByPosition(layoutBoxes, 'right'));
7045
var top = sortByWeight(filterByPosition(layoutBoxes, 'top'), true);
7046
var bottom = sortByWeight(filterByPosition(layoutBoxes, 'bottom'));
7047
7048
return {
7049
leftAndTop: left.concat(top),
7050
rightAndBottom: right.concat(bottom),
7051
chartArea: filterByPosition(layoutBoxes, 'chartArea'),
7052
vertical: left.concat(right),
7053
horizontal: top.concat(bottom)
7054
};
7055
}
7056
7057
function getCombinedMax(maxPadding, chartArea, a, b) {
7058
return Math.max(maxPadding[a], chartArea[a]) + Math.max(maxPadding[b], chartArea[b]);
7059
}
7060
7061
function updateDims(chartArea, params, layout) {
7062
var box = layout.box;
7063
var maxPadding = chartArea.maxPadding;
7064
var newWidth, newHeight;
7065
7066
if (layout.size) {
7067
// this layout was already counted for, lets first reduce old size
7068
chartArea[layout.pos] -= layout.size;
7069
}
7070
layout.size = layout.horizontal ? box.height : box.width;
7071
chartArea[layout.pos] += layout.size;
7072
7073
if (box.getPadding) {
7074
var boxPadding = box.getPadding();
7075
maxPadding.top = Math.max(maxPadding.top, boxPadding.top);
7076
maxPadding.left = Math.max(maxPadding.left, boxPadding.left);
7077
maxPadding.bottom = Math.max(maxPadding.bottom, boxPadding.bottom);
7078
maxPadding.right = Math.max(maxPadding.right, boxPadding.right);
7079
}
7080
7081
newWidth = params.outerWidth - getCombinedMax(maxPadding, chartArea, 'left', 'right');
7082
newHeight = params.outerHeight - getCombinedMax(maxPadding, chartArea, 'top', 'bottom');
7083
7084
if (newWidth !== chartArea.w || newHeight !== chartArea.h) {
7085
chartArea.w = newWidth;
7086
chartArea.h = newHeight;
7087
7088
// return true if chart area changed in layout's direction
7089
return layout.horizontal ? newWidth !== chartArea.w : newHeight !== chartArea.h;
7090
}
7091
}
7092
7093
function handleMaxPadding(chartArea) {
7094
var maxPadding = chartArea.maxPadding;
7095
7096
function updatePos(pos) {
7097
var change = Math.max(maxPadding[pos] - chartArea[pos], 0);
7098
chartArea[pos] += change;
7099
return change;
7100
}
7101
chartArea.y += updatePos('top');
7102
chartArea.x += updatePos('left');
7103
updatePos('right');
7104
updatePos('bottom');
7105
}
7106
7107
function getMargins(horizontal, chartArea) {
7108
var maxPadding = chartArea.maxPadding;
7109
7110
function marginForPositions(positions) {
7111
var margin = {left: 0, top: 0, right: 0, bottom: 0};
7112
positions.forEach(function(pos) {
7113
margin[pos] = Math.max(chartArea[pos], maxPadding[pos]);
7114
});
7115
return margin;
7116
}
7117
7118
return horizontal
7119
? marginForPositions(['left', 'right'])
7120
: marginForPositions(['top', 'bottom']);
7121
}
7122
7123
function fitBoxes(boxes, chartArea, params) {
7124
var refitBoxes = [];
7125
var i, ilen, layout, box, refit, changed;
7126
7127
for (i = 0, ilen = boxes.length; i < ilen; ++i) {
7128
layout = boxes[i];
7129
box = layout.box;
7130
7131
box.update(
7132
layout.width || chartArea.w,
7133
layout.height || chartArea.h,
7134
getMargins(layout.horizontal, chartArea)
7135
);
7136
if (updateDims(chartArea, params, layout)) {
7137
changed = true;
7138
if (refitBoxes.length) {
7139
// Dimensions changed and there were non full width boxes before this
7140
// -> we have to refit those
7141
refit = true;
7142
}
7143
}
7144
if (!box.fullWidth) { // fullWidth boxes don't need to be re-fitted in any case
7145
refitBoxes.push(layout);
7146
}
7147
}
7148
7149
return refit ? fitBoxes(refitBoxes, chartArea, params) || changed : changed;
7150
}
7151
7152
function placeBoxes(boxes, chartArea, params) {
7153
var userPadding = params.padding;
7154
var x = chartArea.x;
7155
var y = chartArea.y;
7156
var i, ilen, layout, box;
7157
7158
for (i = 0, ilen = boxes.length; i < ilen; ++i) {
7159
layout = boxes[i];
7160
box = layout.box;
7161
if (layout.horizontal) {
7162
box.left = box.fullWidth ? userPadding.left : chartArea.left;
7163
box.right = box.fullWidth ? params.outerWidth - userPadding.right : chartArea.left + chartArea.w;
7164
box.top = y;
7165
box.bottom = y + box.height;
7166
box.width = box.right - box.left;
7167
y = box.bottom;
7168
} else {
7169
box.left = x;
7170
box.right = x + box.width;
7171
box.top = chartArea.top;
7172
box.bottom = chartArea.top + chartArea.h;
7173
box.height = box.bottom - box.top;
7174
x = box.right;
7175
}
7176
}
7177
7178
chartArea.x = x;
7179
chartArea.y = y;
7180
}
7181
7182
core_defaults._set('global', {
7183
layout: {
7184
padding: {
7185
top: 0,
7186
right: 0,
7187
bottom: 0,
7188
left: 0
7189
}
7190
}
7191
});
7192
7193
/**
7194
* @interface ILayoutItem
7195
* @prop {string} position - The position of the item in the chart layout. Possible values are
7196
* 'left', 'top', 'right', 'bottom', and 'chartArea'
7197
* @prop {number} weight - The weight used to sort the item. Higher weights are further away from the chart area
7198
* @prop {boolean} fullWidth - if true, and the item is horizontal, then push vertical boxes down
7199
* @prop {function} isHorizontal - returns true if the layout item is horizontal (ie. top or bottom)
7200
* @prop {function} update - Takes two parameters: width and height. Returns size of item
7201
* @prop {function} getPadding - Returns an object with padding on the edges
7202
* @prop {number} width - Width of item. Must be valid after update()
7203
* @prop {number} height - Height of item. Must be valid after update()
7204
* @prop {number} left - Left edge of the item. Set by layout system and cannot be used in update
7205
* @prop {number} top - Top edge of the item. Set by layout system and cannot be used in update
7206
* @prop {number} right - Right edge of the item. Set by layout system and cannot be used in update
7207
* @prop {number} bottom - Bottom edge of the item. Set by layout system and cannot be used in update
7208
*/
7209
7210
// The layout service is very self explanatory. It's responsible for the layout within a chart.
7211
// Scales, Legends and Plugins all rely on the layout service and can easily register to be placed anywhere they need
7212
// It is this service's responsibility of carrying out that layout.
7213
var core_layouts = {
7214
defaults: {},
7215
7216
/**
7217
* Register a box to a chart.
7218
* A box is simply a reference to an object that requires layout. eg. Scales, Legend, Title.
7219
* @param {Chart} chart - the chart to use
7220
* @param {ILayoutItem} item - the item to add to be layed out
7221
*/
7222
addBox: function(chart, item) {
7223
if (!chart.boxes) {
7224
chart.boxes = [];
7225
}
7226
7227
// initialize item with default values
7228
item.fullWidth = item.fullWidth || false;
7229
item.position = item.position || 'top';
7230
item.weight = item.weight || 0;
7231
item._layers = item._layers || function() {
7232
return [{
7233
z: 0,
7234
draw: function() {
7235
item.draw.apply(item, arguments);
7236
}
7237
}];
7238
};
7239
7240
chart.boxes.push(item);
7241
},
7242
7243
/**
7244
* Remove a layoutItem from a chart
7245
* @param {Chart} chart - the chart to remove the box from
7246
* @param {ILayoutItem} layoutItem - the item to remove from the layout
7247
*/
7248
removeBox: function(chart, layoutItem) {
7249
var index = chart.boxes ? chart.boxes.indexOf(layoutItem) : -1;
7250
if (index !== -1) {
7251
chart.boxes.splice(index, 1);
7252
}
7253
},
7254
7255
/**
7256
* Sets (or updates) options on the given `item`.
7257
* @param {Chart} chart - the chart in which the item lives (or will be added to)
7258
* @param {ILayoutItem} item - the item to configure with the given options
7259
* @param {object} options - the new item options.
7260
*/
7261
configure: function(chart, item, options) {
7262
var props = ['fullWidth', 'position', 'weight'];
7263
var ilen = props.length;
7264
var i = 0;
7265
var prop;
7266
7267
for (; i < ilen; ++i) {
7268
prop = props[i];
7269
if (options.hasOwnProperty(prop)) {
7270
item[prop] = options[prop];
7271
}
7272
}
7273
},
7274
7275
/**
7276
* Fits boxes of the given chart into the given size by having each box measure itself
7277
* then running a fitting algorithm
7278
* @param {Chart} chart - the chart
7279
* @param {number} width - the width to fit into
7280
* @param {number} height - the height to fit into
7281
*/
7282
update: function(chart, width, height) {
7283
if (!chart) {
7284
return;
7285
}
7286
7287
var layoutOptions = chart.options.layout || {};
7288
var padding = helpers$1.options.toPadding(layoutOptions.padding);
7289
7290
var availableWidth = width - padding.width;
7291
var availableHeight = height - padding.height;
7292
var boxes = buildLayoutBoxes(chart.boxes);
7293
var verticalBoxes = boxes.vertical;
7294
var horizontalBoxes = boxes.horizontal;
7295
7296
// Essentially we now have any number of boxes on each of the 4 sides.
7297
// Our canvas looks like the following.
7298
// The areas L1 and L2 are the left axes. R1 is the right axis, T1 is the top axis and
7299
// B1 is the bottom axis
7300
// There are also 4 quadrant-like locations (left to right instead of clockwise) reserved for chart overlays
7301
// These locations are single-box locations only, when trying to register a chartArea location that is already taken,
7302
// an error will be thrown.
7303
//
7304
// |----------------------------------------------------|
7305
// | T1 (Full Width) |
7306
// |----------------------------------------------------|
7307
// | | | T2 | |
7308
// | |----|-------------------------------------|----|
7309
// | | | C1 | | C2 | |
7310
// | | |----| |----| |
7311
// | | | | |
7312
// | L1 | L2 | ChartArea (C0) | R1 |
7313
// | | | | |
7314
// | | |----| |----| |
7315
// | | | C3 | | C4 | |
7316
// | |----|-------------------------------------|----|
7317
// | | | B1 | |
7318
// |----------------------------------------------------|
7319
// | B2 (Full Width) |
7320
// |----------------------------------------------------|
7321
//
7322
7323
var params = Object.freeze({
7324
outerWidth: width,
7325
outerHeight: height,
7326
padding: padding,
7327
availableWidth: availableWidth,
7328
vBoxMaxWidth: availableWidth / 2 / verticalBoxes.length,
7329
hBoxMaxHeight: availableHeight / 2
7330
});
7331
var chartArea = extend({
7332
maxPadding: extend({}, padding),
7333
w: availableWidth,
7334
h: availableHeight,
7335
x: padding.left,
7336
y: padding.top
7337
}, padding);
7338
7339
setLayoutDims(verticalBoxes.concat(horizontalBoxes), params);
7340
7341
// First fit vertical boxes
7342
fitBoxes(verticalBoxes, chartArea, params);
7343
7344
// Then fit horizontal boxes
7345
if (fitBoxes(horizontalBoxes, chartArea, params)) {
7346
// if the area changed, re-fit vertical boxes
7347
fitBoxes(verticalBoxes, chartArea, params);
7348
}
7349
7350
handleMaxPadding(chartArea);
7351
7352
// Finally place the boxes to correct coordinates
7353
placeBoxes(boxes.leftAndTop, chartArea, params);
7354
7355
// Move to opposite side of chart
7356
chartArea.x += chartArea.w;
7357
chartArea.y += chartArea.h;
7358
7359
placeBoxes(boxes.rightAndBottom, chartArea, params);
7360
7361
chart.chartArea = {
7362
left: chartArea.left,
7363
top: chartArea.top,
7364
right: chartArea.left + chartArea.w,
7365
bottom: chartArea.top + chartArea.h
7366
};
7367
7368
// Finally update boxes in chartArea (radial scale for example)
7369
helpers$1.each(boxes.chartArea, function(layout) {
7370
var box = layout.box;
7371
extend(box, chart.chartArea);
7372
box.update(chartArea.w, chartArea.h);
7373
});
7374
}
7375
};
7376
7377
/**
7378
* Platform fallback implementation (minimal).
7379
* @see https://github.com/chartjs/Chart.js/pull/4591#issuecomment-319575939
7380
*/
7381
7382
var platform_basic = {
7383
acquireContext: function(item) {
7384
if (item && item.canvas) {
7385
// Support for any object associated to a canvas (including a context2d)
7386
item = item.canvas;
7387
}
7388
7389
return item && item.getContext('2d') || null;
7390
}
7391
};
7392
7393
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";
7394
7395
var platform_dom$1 = /*#__PURE__*/Object.freeze({
7396
__proto__: null,
7397
'default': platform_dom
7398
});
7399
7400
var stylesheet = getCjsExportFromNamespace(platform_dom$1);
7401
7402
var EXPANDO_KEY = '$chartjs';
7403
var CSS_PREFIX = 'chartjs-';
7404
var CSS_SIZE_MONITOR = CSS_PREFIX + 'size-monitor';
7405
var CSS_RENDER_MONITOR = CSS_PREFIX + 'render-monitor';
7406
var CSS_RENDER_ANIMATION = CSS_PREFIX + 'render-animation';
7407
var ANIMATION_START_EVENTS = ['animationstart', 'webkitAnimationStart'];
7408
7409
/**
7410
* DOM event types -> Chart.js event types.
7411
* Note: only events with different types are mapped.
7412
* @see https://developer.mozilla.org/en-US/docs/Web/Events
7413
*/
7414
var EVENT_TYPES = {
7415
touchstart: 'mousedown',
7416
touchmove: 'mousemove',
7417
touchend: 'mouseup',
7418
pointerenter: 'mouseenter',
7419
pointerdown: 'mousedown',
7420
pointermove: 'mousemove',
7421
pointerup: 'mouseup',
7422
pointerleave: 'mouseout',
7423
pointerout: 'mouseout'
7424
};
7425
7426
/**
7427
* The "used" size is the final value of a dimension property after all calculations have
7428
* been performed. This method uses the computed style of `element` but returns undefined
7429
* if the computed style is not expressed in pixels. That can happen in some cases where
7430
* `element` has a size relative to its parent and this last one is not yet displayed,
7431
* for example because of `display: none` on a parent node.
7432
* @see https://developer.mozilla.org/en-US/docs/Web/CSS/used_value
7433
* @returns {number} Size in pixels or undefined if unknown.
7434
*/
7435
function readUsedSize(element, property) {
7436
var value = helpers$1.getStyle(element, property);
7437
var matches = value && value.match(/^(\d+)(\.\d+)?px$/);
7438
return matches ? Number(matches[1]) : undefined;
7439
}
7440
7441
/**
7442
* Initializes the canvas style and render size without modifying the canvas display size,
7443
* since responsiveness is handled by the controller.resize() method. The config is used
7444
* to determine the aspect ratio to apply in case no explicit height has been specified.
7445
*/
7446
function initCanvas(canvas, config) {
7447
var style = canvas.style;
7448
7449
// NOTE(SB) canvas.getAttribute('width') !== canvas.width: in the first case it
7450
// returns null or '' if no explicit value has been set to the canvas attribute.
7451
var renderHeight = canvas.getAttribute('height');
7452
var renderWidth = canvas.getAttribute('width');
7453
7454
// Chart.js modifies some canvas values that we want to restore on destroy
7455
canvas[EXPANDO_KEY] = {
7456
initial: {
7457
height: renderHeight,
7458
width: renderWidth,
7459
style: {
7460
display: style.display,
7461
height: style.height,
7462
width: style.width
7463
}
7464
}
7465
};
7466
7467
// Force canvas to display as block to avoid extra space caused by inline
7468
// elements, which would interfere with the responsive resize process.
7469
// https://github.com/chartjs/Chart.js/issues/2538
7470
style.display = style.display || 'block';
7471
7472
if (renderWidth === null || renderWidth === '') {
7473
var displayWidth = readUsedSize(canvas, 'width');
7474
if (displayWidth !== undefined) {
7475
canvas.width = displayWidth;
7476
}
7477
}
7478
7479
if (renderHeight === null || renderHeight === '') {
7480
if (canvas.style.height === '') {
7481
// If no explicit render height and style height, let's apply the aspect ratio,
7482
// which one can be specified by the user but also by charts as default option
7483
// (i.e. options.aspectRatio). If not specified, use canvas aspect ratio of 2.
7484
canvas.height = canvas.width / (config.options.aspectRatio || 2);
7485
} else {
7486
var displayHeight = readUsedSize(canvas, 'height');
7487
if (displayWidth !== undefined) {
7488
canvas.height = displayHeight;
7489
}
7490
}
7491
}
7492
7493
return canvas;
7494
}
7495
7496
/**
7497
* Detects support for options object argument in addEventListener.
7498
* https://developer.mozilla.org/en-US/docs/Web/API/EventTarget/addEventListener#Safely_detecting_option_support
7499
* @private
7500
*/
7501
var supportsEventListenerOptions = (function() {
7502
var supports = false;
7503
try {
7504
var options = Object.defineProperty({}, 'passive', {
7505
// eslint-disable-next-line getter-return
7506
get: function() {
7507
supports = true;
7508
}
7509
});
7510
window.addEventListener('e', null, options);
7511
} catch (e) {
7512
// continue regardless of error
7513
}
7514
return supports;
7515
}());
7516
7517
// Default passive to true as expected by Chrome for 'touchstart' and 'touchend' events.
7518
// https://github.com/chartjs/Chart.js/issues/4287
7519
var eventListenerOptions = supportsEventListenerOptions ? {passive: true} : false;
7520
7521
function addListener(node, type, listener) {
7522
node.addEventListener(type, listener, eventListenerOptions);
7523
}
7524
7525
function removeListener(node, type, listener) {
7526
node.removeEventListener(type, listener, eventListenerOptions);
7527
}
7528
7529
function createEvent(type, chart, x, y, nativeEvent) {
7530
return {
7531
type: type,
7532
chart: chart,
7533
native: nativeEvent || null,
7534
x: x !== undefined ? x : null,
7535
y: y !== undefined ? y : null,
7536
};
7537
}
7538
7539
function fromNativeEvent(event, chart) {
7540
var type = EVENT_TYPES[event.type] || event.type;
7541
var pos = helpers$1.getRelativePosition(event, chart);
7542
return createEvent(type, chart, pos.x, pos.y, event);
7543
}
7544
7545
function throttled(fn, thisArg) {
7546
var ticking = false;
7547
var args = [];
7548
7549
return function() {
7550
args = Array.prototype.slice.call(arguments);
7551
thisArg = thisArg || this;
7552
7553
if (!ticking) {
7554
ticking = true;
7555
helpers$1.requestAnimFrame.call(window, function() {
7556
ticking = false;
7557
fn.apply(thisArg, args);
7558
});
7559
}
7560
};
7561
}
7562
7563
function createDiv(cls) {
7564
var el = document.createElement('div');
7565
el.className = cls || '';
7566
return el;
7567
}
7568
7569
// Implementation based on https://github.com/marcj/css-element-queries
7570
function createResizer(handler) {
7571
var maxSize = 1000000;
7572
7573
// NOTE(SB) Don't use innerHTML because it could be considered unsafe.
7574
// https://github.com/chartjs/Chart.js/issues/5902
7575
var resizer = createDiv(CSS_SIZE_MONITOR);
7576
var expand = createDiv(CSS_SIZE_MONITOR + '-expand');
7577
var shrink = createDiv(CSS_SIZE_MONITOR + '-shrink');
7578
7579
expand.appendChild(createDiv());
7580
shrink.appendChild(createDiv());
7581
7582
resizer.appendChild(expand);
7583
resizer.appendChild(shrink);
7584
resizer._reset = function() {
7585
expand.scrollLeft = maxSize;
7586
expand.scrollTop = maxSize;
7587
shrink.scrollLeft = maxSize;
7588
shrink.scrollTop = maxSize;
7589
};
7590
7591
var onScroll = function() {
7592
resizer._reset();
7593
handler();
7594
};
7595
7596
addListener(expand, 'scroll', onScroll.bind(expand, 'expand'));
7597
addListener(shrink, 'scroll', onScroll.bind(shrink, 'shrink'));
7598
7599
return resizer;
7600
}
7601
7602
// https://davidwalsh.name/detect-node-insertion
7603
function watchForRender(node, handler) {
7604
var expando = node[EXPANDO_KEY] || (node[EXPANDO_KEY] = {});
7605
var proxy = expando.renderProxy = function(e) {
7606
if (e.animationName === CSS_RENDER_ANIMATION) {
7607
handler();
7608
}
7609
};
7610
7611
helpers$1.each(ANIMATION_START_EVENTS, function(type) {
7612
addListener(node, type, proxy);
7613
});
7614
7615
// #4737: Chrome might skip the CSS animation when the CSS_RENDER_MONITOR class
7616
// is removed then added back immediately (same animation frame?). Accessing the
7617
// `offsetParent` property will force a reflow and re-evaluate the CSS animation.
7618
// https://gist.github.com/paulirish/5d52fb081b3570c81e3a#box-metrics
7619
// https://github.com/chartjs/Chart.js/issues/4737
7620
expando.reflow = !!node.offsetParent;
7621
7622
node.classList.add(CSS_RENDER_MONITOR);
7623
}
7624
7625
function unwatchForRender(node) {
7626
var expando = node[EXPANDO_KEY] || {};
7627
var proxy = expando.renderProxy;
7628
7629
if (proxy) {
7630
helpers$1.each(ANIMATION_START_EVENTS, function(type) {
7631
removeListener(node, type, proxy);
7632
});
7633
7634
delete expando.renderProxy;
7635
}
7636
7637
node.classList.remove(CSS_RENDER_MONITOR);
7638
}
7639
7640
function addResizeListener(node, listener, chart) {
7641
var expando = node[EXPANDO_KEY] || (node[EXPANDO_KEY] = {});
7642
7643
// Let's keep track of this added resizer and thus avoid DOM query when removing it.
7644
var resizer = expando.resizer = createResizer(throttled(function() {
7645
if (expando.resizer) {
7646
var container = chart.options.maintainAspectRatio && node.parentNode;
7647
var w = container ? container.clientWidth : 0;
7648
listener(createEvent('resize', chart));
7649
if (container && container.clientWidth < w && chart.canvas) {
7650
// If the container size shrank during chart resize, let's assume
7651
// scrollbar appeared. So we resize again with the scrollbar visible -
7652
// effectively making chart smaller and the scrollbar hidden again.
7653
// Because we are inside `throttled`, and currently `ticking`, scroll
7654
// events are ignored during this whole 2 resize process.
7655
// If we assumed wrong and something else happened, we are resizing
7656
// twice in a frame (potential performance issue)
7657
listener(createEvent('resize', chart));
7658
}
7659
}
7660
}));
7661
7662
// The resizer needs to be attached to the node parent, so we first need to be
7663
// sure that `node` is attached to the DOM before injecting the resizer element.
7664
watchForRender(node, function() {
7665
if (expando.resizer) {
7666
var container = node.parentNode;
7667
if (container && container !== resizer.parentNode) {
7668
container.insertBefore(resizer, container.firstChild);
7669
}
7670
7671
// The container size might have changed, let's reset the resizer state.
7672
resizer._reset();
7673
}
7674
});
7675
}
7676
7677
function removeResizeListener(node) {
7678
var expando = node[EXPANDO_KEY] || {};
7679
var resizer = expando.resizer;
7680
7681
delete expando.resizer;
7682
unwatchForRender(node);
7683
7684
if (resizer && resizer.parentNode) {
7685
resizer.parentNode.removeChild(resizer);
7686
}
7687
}
7688
7689
/**
7690
* Injects CSS styles inline if the styles are not already present.
7691
* @param {HTMLDocument|ShadowRoot} rootNode - the node to contain the <style>.
7692
* @param {string} css - the CSS to be injected.
7693
*/
7694
function injectCSS(rootNode, css) {
7695
// https://stackoverflow.com/q/3922139
7696
var expando = rootNode[EXPANDO_KEY] || (rootNode[EXPANDO_KEY] = {});
7697
if (!expando.containsStyles) {
7698
expando.containsStyles = true;
7699
css = '/* Chart.js */\n' + css;
7700
var style = document.createElement('style');
7701
style.setAttribute('type', 'text/css');
7702
style.appendChild(document.createTextNode(css));
7703
rootNode.appendChild(style);
7704
}
7705
}
7706
7707
var platform_dom$2 = {
7708
/**
7709
* When `true`, prevents the automatic injection of the stylesheet required to
7710
* correctly detect when the chart is added to the DOM and then resized. This
7711
* switch has been added to allow external stylesheet (`dist/Chart(.min)?.js`)
7712
* to be manually imported to make this library compatible with any CSP.
7713
* See https://github.com/chartjs/Chart.js/issues/5208
7714
*/
7715
disableCSSInjection: false,
7716
7717
/**
7718
* This property holds whether this platform is enabled for the current environment.
7719
* Currently used by platform.js to select the proper implementation.
7720
* @private
7721
*/
7722
_enabled: typeof window !== 'undefined' && typeof document !== 'undefined',
7723
7724
/**
7725
* Initializes resources that depend on platform options.
7726
* @param {HTMLCanvasElement} canvas - The Canvas element.
7727
* @private
7728
*/
7729
_ensureLoaded: function(canvas) {
7730
if (!this.disableCSSInjection) {
7731
// If the canvas is in a shadow DOM, then the styles must also be inserted
7732
// into the same shadow DOM.
7733
// https://github.com/chartjs/Chart.js/issues/5763
7734
var root = canvas.getRootNode ? canvas.getRootNode() : document;
7735
var targetNode = root.host ? root : document.head;
7736
injectCSS(targetNode, stylesheet);
7737
}
7738
},
7739
7740
acquireContext: function(item, config) {
7741
if (typeof item === 'string') {
7742
item = document.getElementById(item);
7743
} else if (item.length) {
7744
// Support for array based queries (such as jQuery)
7745
item = item[0];
7746
}
7747
7748
if (item && item.canvas) {
7749
// Support for any object associated to a canvas (including a context2d)
7750
item = item.canvas;
7751
}
7752
7753
// To prevent canvas fingerprinting, some add-ons undefine the getContext
7754
// method, for example: https://github.com/kkapsner/CanvasBlocker
7755
// https://github.com/chartjs/Chart.js/issues/2807
7756
var context = item && item.getContext && item.getContext('2d');
7757
7758
// `instanceof HTMLCanvasElement/CanvasRenderingContext2D` fails when the item is
7759
// inside an iframe or when running in a protected environment. We could guess the
7760
// types from their toString() value but let's keep things flexible and assume it's
7761
// a sufficient condition if the item has a context2D which has item as `canvas`.
7762
// https://github.com/chartjs/Chart.js/issues/3887
7763
// https://github.com/chartjs/Chart.js/issues/4102
7764
// https://github.com/chartjs/Chart.js/issues/4152
7765
if (context && context.canvas === item) {
7766
// Load platform resources on first chart creation, to make it possible to
7767
// import the library before setting platform options.
7768
this._ensureLoaded(item);
7769
initCanvas(item, config);
7770
return context;
7771
}
7772
7773
return null;
7774
},
7775
7776
releaseContext: function(context) {
7777
var canvas = context.canvas;
7778
if (!canvas[EXPANDO_KEY]) {
7779
return;
7780
}
7781
7782
var initial = canvas[EXPANDO_KEY].initial;
7783
['height', 'width'].forEach(function(prop) {
7784
var value = initial[prop];
7785
if (helpers$1.isNullOrUndef(value)) {
7786
canvas.removeAttribute(prop);
7787
} else {
7788
canvas.setAttribute(prop, value);
7789
}
7790
});
7791
7792
helpers$1.each(initial.style || {}, function(value, key) {
7793
canvas.style[key] = value;
7794
});
7795
7796
// The canvas render size might have been changed (and thus the state stack discarded),
7797
// we can't use save() and restore() to restore the initial state. So make sure that at
7798
// least the canvas context is reset to the default state by setting the canvas width.
7799
// https://www.w3.org/TR/2011/WD-html5-20110525/the-canvas-element.html
7800
// eslint-disable-next-line no-self-assign
7801
canvas.width = canvas.width;
7802
7803
delete canvas[EXPANDO_KEY];
7804
},
7805
7806
addEventListener: function(chart, type, listener) {
7807
var canvas = chart.canvas;
7808
if (type === 'resize') {
7809
// Note: the resize event is not supported on all browsers.
7810
addResizeListener(canvas, listener, chart);
7811
return;
7812
}
7813
7814
var expando = listener[EXPANDO_KEY] || (listener[EXPANDO_KEY] = {});
7815
var proxies = expando.proxies || (expando.proxies = {});
7816
var proxy = proxies[chart.id + '_' + type] = function(event) {
7817
listener(fromNativeEvent(event, chart));
7818
};
7819
7820
addListener(canvas, type, proxy);
7821
},
7822
7823
removeEventListener: function(chart, type, listener) {
7824
var canvas = chart.canvas;
7825
if (type === 'resize') {
7826
// Note: the resize event is not supported on all browsers.
7827
removeResizeListener(canvas);
7828
return;
7829
}
7830
7831
var expando = listener[EXPANDO_KEY] || {};
7832
var proxies = expando.proxies || {};
7833
var proxy = proxies[chart.id + '_' + type];
7834
if (!proxy) {
7835
return;
7836
}
7837
7838
removeListener(canvas, type, proxy);
7839
}
7840
};
7841
7842
// DEPRECATIONS
7843
7844
/**
7845
* Provided for backward compatibility, use EventTarget.addEventListener instead.
7846
* EventTarget.addEventListener compatibility: Chrome, Opera 7, Safari, FF1.5+, IE9+
7847
* @see https://developer.mozilla.org/en-US/docs/Web/API/EventTarget/addEventListener
7848
* @function Chart.helpers.addEvent
7849
* @deprecated since version 2.7.0
7850
* @todo remove at version 3
7851
* @private
7852
*/
7853
helpers$1.addEvent = addListener;
7854
7855
/**
7856
* Provided for backward compatibility, use EventTarget.removeEventListener instead.
7857
* EventTarget.removeEventListener compatibility: Chrome, Opera 7, Safari, FF1.5+, IE9+
7858
* @see https://developer.mozilla.org/en-US/docs/Web/API/EventTarget/removeEventListener
7859
* @function Chart.helpers.removeEvent
7860
* @deprecated since version 2.7.0
7861
* @todo remove at version 3
7862
* @private
7863
*/
7864
helpers$1.removeEvent = removeListener;
7865
7866
// @TODO Make possible to select another platform at build time.
7867
var implementation = platform_dom$2._enabled ? platform_dom$2 : platform_basic;
7868
7869
/**
7870
* @namespace Chart.platform
7871
* @see https://chartjs.gitbooks.io/proposals/content/Platform.html
7872
* @since 2.4.0
7873
*/
7874
var platform = helpers$1.extend({
7875
/**
7876
* @since 2.7.0
7877
*/
7878
initialize: function() {},
7879
7880
/**
7881
* Called at chart construction time, returns a context2d instance implementing
7882
* the [W3C Canvas 2D Context API standard]{@link https://www.w3.org/TR/2dcontext/}.
7883
* @param {*} item - The native item from which to acquire context (platform specific)
7884
* @param {object} options - The chart options
7885
* @returns {CanvasRenderingContext2D} context2d instance
7886
*/
7887
acquireContext: function() {},
7888
7889
/**
7890
* Called at chart destruction time, releases any resources associated to the context
7891
* previously returned by the acquireContext() method.
7892
* @param {CanvasRenderingContext2D} context - The context2d instance
7893
* @returns {boolean} true if the method succeeded, else false
7894
*/
7895
releaseContext: function() {},
7896
7897
/**
7898
* Registers the specified listener on the given chart.
7899
* @param {Chart} chart - Chart from which to listen for event
7900
* @param {string} type - The ({@link IEvent}) type to listen for
7901
* @param {function} listener - Receives a notification (an object that implements
7902
* the {@link IEvent} interface) when an event of the specified type occurs.
7903
*/
7904
addEventListener: function() {},
7905
7906
/**
7907
* Removes the specified listener previously registered with addEventListener.
7908
* @param {Chart} chart - Chart from which to remove the listener
7909
* @param {string} type - The ({@link IEvent}) type to remove
7910
* @param {function} listener - The listener function to remove from the event target.
7911
*/
7912
removeEventListener: function() {}
7913
7914
}, implementation);
7915
7916
core_defaults._set('global', {
7917
plugins: {}
7918
});
7919
7920
/**
7921
* The plugin service singleton
7922
* @namespace Chart.plugins
7923
* @since 2.1.0
7924
*/
7925
var core_plugins = {
7926
/**
7927
* Globally registered plugins.
7928
* @private
7929
*/
7930
_plugins: [],
7931
7932
/**
7933
* This identifier is used to invalidate the descriptors cache attached to each chart
7934
* when a global plugin is registered or unregistered. In this case, the cache ID is
7935
* incremented and descriptors are regenerated during following API calls.
7936
* @private
7937
*/
7938
_cacheId: 0,
7939
7940
/**
7941
* Registers the given plugin(s) if not already registered.
7942
* @param {IPlugin[]|IPlugin} plugins plugin instance(s).
7943
*/
7944
register: function(plugins) {
7945
var p = this._plugins;
7946
([]).concat(plugins).forEach(function(plugin) {
7947
if (p.indexOf(plugin) === -1) {
7948
p.push(plugin);
7949
}
7950
});
7951
7952
this._cacheId++;
7953
},
7954
7955
/**
7956
* Unregisters the given plugin(s) only if registered.
7957
* @param {IPlugin[]|IPlugin} plugins plugin instance(s).
7958
*/
7959
unregister: function(plugins) {
7960
var p = this._plugins;
7961
([]).concat(plugins).forEach(function(plugin) {
7962
var idx = p.indexOf(plugin);
7963
if (idx !== -1) {
7964
p.splice(idx, 1);
7965
}
7966
});
7967
7968
this._cacheId++;
7969
},
7970
7971
/**
7972
* Remove all registered plugins.
7973
* @since 2.1.5
7974
*/
7975
clear: function() {
7976
this._plugins = [];
7977
this._cacheId++;
7978
},
7979
7980
/**
7981
* Returns the number of registered plugins?
7982
* @returns {number}
7983
* @since 2.1.5
7984
*/
7985
count: function() {
7986
return this._plugins.length;
7987
},
7988
7989
/**
7990
* Returns all registered plugin instances.
7991
* @returns {IPlugin[]} array of plugin objects.
7992
* @since 2.1.5
7993
*/
7994
getAll: function() {
7995
return this._plugins;
7996
},
7997
7998
/**
7999
* Calls enabled plugins for `chart` on the specified hook and with the given args.
8000
* This method immediately returns as soon as a plugin explicitly returns false. The
8001
* returned value can be used, for instance, to interrupt the current action.
8002
* @param {Chart} chart - The chart instance for which plugins should be called.
8003
* @param {string} hook - The name of the plugin method to call (e.g. 'beforeUpdate').
8004
* @param {Array} [args] - Extra arguments to apply to the hook call.
8005
* @returns {boolean} false if any of the plugins return false, else returns true.
8006
*/
8007
notify: function(chart, hook, args) {
8008
var descriptors = this.descriptors(chart);
8009
var ilen = descriptors.length;
8010
var i, descriptor, plugin, params, method;
8011
8012
for (i = 0; i < ilen; ++i) {
8013
descriptor = descriptors[i];
8014
plugin = descriptor.plugin;
8015
method = plugin[hook];
8016
if (typeof method === 'function') {
8017
params = [chart].concat(args || []);
8018
params.push(descriptor.options);
8019
if (method.apply(plugin, params) === false) {
8020
return false;
8021
}
8022
}
8023
}
8024
8025
return true;
8026
},
8027
8028
/**
8029
* Returns descriptors of enabled plugins for the given chart.
8030
* @returns {object[]} [{ plugin, options }]
8031
* @private
8032
*/
8033
descriptors: function(chart) {
8034
var cache = chart.$plugins || (chart.$plugins = {});
8035
if (cache.id === this._cacheId) {
8036
return cache.descriptors;
8037
}
8038
8039
var plugins = [];
8040
var descriptors = [];
8041
var config = (chart && chart.config) || {};
8042
var options = (config.options && config.options.plugins) || {};
8043
8044
this._plugins.concat(config.plugins || []).forEach(function(plugin) {
8045
var idx = plugins.indexOf(plugin);
8046
if (idx !== -1) {
8047
return;
8048
}
8049
8050
var id = plugin.id;
8051
var opts = options[id];
8052
if (opts === false) {
8053
return;
8054
}
8055
8056
if (opts === true) {
8057
opts = helpers$1.clone(core_defaults.global.plugins[id]);
8058
}
8059
8060
plugins.push(plugin);
8061
descriptors.push({
8062
plugin: plugin,
8063
options: opts || {}
8064
});
8065
});
8066
8067
cache.descriptors = descriptors;
8068
cache.id = this._cacheId;
8069
return descriptors;
8070
},
8071
8072
/**
8073
* Invalidates cache for the given chart: descriptors hold a reference on plugin option,
8074
* but in some cases, this reference can be changed by the user when updating options.
8075
* https://github.com/chartjs/Chart.js/issues/5111#issuecomment-355934167
8076
* @private
8077
*/
8078
_invalidate: function(chart) {
8079
delete chart.$plugins;
8080
}
8081
};
8082
8083
var core_scaleService = {
8084
// Scale registration object. Extensions can register new scale types (such as log or DB scales) and then
8085
// use the new chart options to grab the correct scale
8086
constructors: {},
8087
// Use a registration function so that we can move to an ES6 map when we no longer need to support
8088
// old browsers
8089
8090
// Scale config defaults
8091
defaults: {},
8092
registerScaleType: function(type, scaleConstructor, scaleDefaults) {
8093
this.constructors[type] = scaleConstructor;
8094
this.defaults[type] = helpers$1.clone(scaleDefaults);
8095
},
8096
getScaleConstructor: function(type) {
8097
return this.constructors.hasOwnProperty(type) ? this.constructors[type] : undefined;
8098
},
8099
getScaleDefaults: function(type) {
8100
// Return the scale defaults merged with the global settings so that we always use the latest ones
8101
return this.defaults.hasOwnProperty(type) ? helpers$1.merge({}, [core_defaults.scale, this.defaults[type]]) : {};
8102
},
8103
updateScaleDefaults: function(type, additions) {
8104
var me = this;
8105
if (me.defaults.hasOwnProperty(type)) {
8106
me.defaults[type] = helpers$1.extend(me.defaults[type], additions);
8107
}
8108
},
8109
addScalesToLayout: function(chart) {
8110
// Adds each scale to the chart.boxes array to be sized accordingly
8111
helpers$1.each(chart.scales, function(scale) {
8112
// Set ILayoutItem parameters for backwards compatibility
8113
scale.fullWidth = scale.options.fullWidth;
8114
scale.position = scale.options.position;
8115
scale.weight = scale.options.weight;
8116
core_layouts.addBox(chart, scale);
8117
});
8118
}
8119
};
8120
8121
var valueOrDefault$8 = helpers$1.valueOrDefault;
8122
var getRtlHelper = helpers$1.rtl.getRtlAdapter;
8123
8124
core_defaults._set('global', {
8125
tooltips: {
8126
enabled: true,
8127
custom: null,
8128
mode: 'nearest',
8129
position: 'average',
8130
intersect: true,
8131
backgroundColor: 'rgba(0,0,0,0.8)',
8132
titleFontStyle: 'bold',
8133
titleSpacing: 2,
8134
titleMarginBottom: 6,
8135
titleFontColor: '#fff',
8136
titleAlign: 'left',
8137
bodySpacing: 2,
8138
bodyFontColor: '#fff',
8139
bodyAlign: 'left',
8140
footerFontStyle: 'bold',
8141
footerSpacing: 2,
8142
footerMarginTop: 6,
8143
footerFontColor: '#fff',
8144
footerAlign: 'left',
8145
yPadding: 6,
8146
xPadding: 6,
8147
caretPadding: 2,
8148
caretSize: 5,
8149
cornerRadius: 6,
8150
multiKeyBackground: '#fff',
8151
displayColors: true,
8152
borderColor: 'rgba(0,0,0,0)',
8153
borderWidth: 0,
8154
callbacks: {
8155
// Args are: (tooltipItems, data)
8156
beforeTitle: helpers$1.noop,
8157
title: function(tooltipItems, data) {
8158
var title = '';
8159
var labels = data.labels;
8160
var labelCount = labels ? labels.length : 0;
8161
8162
if (tooltipItems.length > 0) {
8163
var item = tooltipItems[0];
8164
if (item.label) {
8165
title = item.label;
8166
} else if (item.xLabel) {
8167
title = item.xLabel;
8168
} else if (labelCount > 0 && item.index < labelCount) {
8169
title = labels[item.index];
8170
}
8171
}
8172
8173
return title;
8174
},
8175
afterTitle: helpers$1.noop,
8176
8177
// Args are: (tooltipItems, data)
8178
beforeBody: helpers$1.noop,
8179
8180
// Args are: (tooltipItem, data)
8181
beforeLabel: helpers$1.noop,
8182
label: function(tooltipItem, data) {
8183
var label = data.datasets[tooltipItem.datasetIndex].label || '';
8184
8185
if (label) {
8186
label += ': ';
8187
}
8188
if (!helpers$1.isNullOrUndef(tooltipItem.value)) {
8189
label += tooltipItem.value;
8190
} else {
8191
label += tooltipItem.yLabel;
8192
}
8193
return label;
8194
},
8195
labelColor: function(tooltipItem, chart) {
8196
var meta = chart.getDatasetMeta(tooltipItem.datasetIndex);
8197
var activeElement = meta.data[tooltipItem.index];
8198
var view = activeElement._view;
8199
return {
8200
borderColor: view.borderColor,
8201
backgroundColor: view.backgroundColor
8202
};
8203
},
8204
labelTextColor: function() {
8205
return this._options.bodyFontColor;
8206
},
8207
afterLabel: helpers$1.noop,
8208
8209
// Args are: (tooltipItems, data)
8210
afterBody: helpers$1.noop,
8211
8212
// Args are: (tooltipItems, data)
8213
beforeFooter: helpers$1.noop,
8214
footer: helpers$1.noop,
8215
afterFooter: helpers$1.noop
8216
}
8217
}
8218
});
8219
8220
var positioners = {
8221
/**
8222
* Average mode places the tooltip at the average position of the elements shown
8223
* @function Chart.Tooltip.positioners.average
8224
* @param elements {ChartElement[]} the elements being displayed in the tooltip
8225
* @returns {object} tooltip position
8226
*/
8227
average: function(elements) {
8228
if (!elements.length) {
8229
return false;
8230
}
8231
8232
var i, len;
8233
var x = 0;
8234
var y = 0;
8235
var count = 0;
8236
8237
for (i = 0, len = elements.length; i < len; ++i) {
8238
var el = elements[i];
8239
if (el && el.hasValue()) {
8240
var pos = el.tooltipPosition();
8241
x += pos.x;
8242
y += pos.y;
8243
++count;
8244
}
8245
}
8246
8247
return {
8248
x: x / count,
8249
y: y / count
8250
};
8251
},
8252
8253
/**
8254
* Gets the tooltip position nearest of the item nearest to the event position
8255
* @function Chart.Tooltip.positioners.nearest
8256
* @param elements {Chart.Element[]} the tooltip elements
8257
* @param eventPosition {object} the position of the event in canvas coordinates
8258
* @returns {object} the tooltip position
8259
*/
8260
nearest: function(elements, eventPosition) {
8261
var x = eventPosition.x;
8262
var y = eventPosition.y;
8263
var minDistance = Number.POSITIVE_INFINITY;
8264
var i, len, nearestElement;
8265
8266
for (i = 0, len = elements.length; i < len; ++i) {
8267
var el = elements[i];
8268
if (el && el.hasValue()) {
8269
var center = el.getCenterPoint();
8270
var d = helpers$1.distanceBetweenPoints(eventPosition, center);
8271
8272
if (d < minDistance) {
8273
minDistance = d;
8274
nearestElement = el;
8275
}
8276
}
8277
}
8278
8279
if (nearestElement) {
8280
var tp = nearestElement.tooltipPosition();
8281
x = tp.x;
8282
y = tp.y;
8283
}
8284
8285
return {
8286
x: x,
8287
y: y
8288
};
8289
}
8290
};
8291
8292
// Helper to push or concat based on if the 2nd parameter is an array or not
8293
function pushOrConcat(base, toPush) {
8294
if (toPush) {
8295
if (helpers$1.isArray(toPush)) {
8296
// base = base.concat(toPush);
8297
Array.prototype.push.apply(base, toPush);
8298
} else {
8299
base.push(toPush);
8300
}
8301
}
8302
8303
return base;
8304
}
8305
8306
/**
8307
* Returns array of strings split by newline
8308
* @param {string} value - The value to split by newline.
8309
* @returns {string[]} value if newline present - Returned from String split() method
8310
* @function
8311
*/
8312
function splitNewlines(str) {
8313
if ((typeof str === 'string' || str instanceof String) && str.indexOf('\n') > -1) {
8314
return str.split('\n');
8315
}
8316
return str;
8317
}
8318
8319
8320
/**
8321
* Private helper to create a tooltip item model
8322
* @param element - the chart element (point, arc, bar) to create the tooltip item for
8323
* @return new tooltip item
8324
*/
8325
function createTooltipItem(element) {
8326
var xScale = element._xScale;
8327
var yScale = element._yScale || element._scale; // handle radar || polarArea charts
8328
var index = element._index;
8329
var datasetIndex = element._datasetIndex;
8330
var controller = element._chart.getDatasetMeta(datasetIndex).controller;
8331
var indexScale = controller._getIndexScale();
8332
var valueScale = controller._getValueScale();
8333
8334
return {
8335
xLabel: xScale ? xScale.getLabelForIndex(index, datasetIndex) : '',
8336
yLabel: yScale ? yScale.getLabelForIndex(index, datasetIndex) : '',
8337
label: indexScale ? '' + indexScale.getLabelForIndex(index, datasetIndex) : '',
8338
value: valueScale ? '' + valueScale.getLabelForIndex(index, datasetIndex) : '',
8339
index: index,
8340
datasetIndex: datasetIndex,
8341
x: element._model.x,
8342
y: element._model.y
8343
};
8344
}
8345
8346
/**
8347
* Helper to get the reset model for the tooltip
8348
* @param tooltipOpts {object} the tooltip options
8349
*/
8350
function getBaseModel(tooltipOpts) {
8351
var globalDefaults = core_defaults.global;
8352
8353
return {
8354
// Positioning
8355
xPadding: tooltipOpts.xPadding,
8356
yPadding: tooltipOpts.yPadding,
8357
xAlign: tooltipOpts.xAlign,
8358
yAlign: tooltipOpts.yAlign,
8359
8360
// Drawing direction and text direction
8361
rtl: tooltipOpts.rtl,
8362
textDirection: tooltipOpts.textDirection,
8363
8364
// Body
8365
bodyFontColor: tooltipOpts.bodyFontColor,
8366
_bodyFontFamily: valueOrDefault$8(tooltipOpts.bodyFontFamily, globalDefaults.defaultFontFamily),
8367
_bodyFontStyle: valueOrDefault$8(tooltipOpts.bodyFontStyle, globalDefaults.defaultFontStyle),
8368
_bodyAlign: tooltipOpts.bodyAlign,
8369
bodyFontSize: valueOrDefault$8(tooltipOpts.bodyFontSize, globalDefaults.defaultFontSize),
8370
bodySpacing: tooltipOpts.bodySpacing,
8371
8372
// Title
8373
titleFontColor: tooltipOpts.titleFontColor,
8374
_titleFontFamily: valueOrDefault$8(tooltipOpts.titleFontFamily, globalDefaults.defaultFontFamily),
8375
_titleFontStyle: valueOrDefault$8(tooltipOpts.titleFontStyle, globalDefaults.defaultFontStyle),
8376
titleFontSize: valueOrDefault$8(tooltipOpts.titleFontSize, globalDefaults.defaultFontSize),
8377
_titleAlign: tooltipOpts.titleAlign,
8378
titleSpacing: tooltipOpts.titleSpacing,
8379
titleMarginBottom: tooltipOpts.titleMarginBottom,
8380
8381
// Footer
8382
footerFontColor: tooltipOpts.footerFontColor,
8383
_footerFontFamily: valueOrDefault$8(tooltipOpts.footerFontFamily, globalDefaults.defaultFontFamily),
8384
_footerFontStyle: valueOrDefault$8(tooltipOpts.footerFontStyle, globalDefaults.defaultFontStyle),
8385
footerFontSize: valueOrDefault$8(tooltipOpts.footerFontSize, globalDefaults.defaultFontSize),
8386
_footerAlign: tooltipOpts.footerAlign,
8387
footerSpacing: tooltipOpts.footerSpacing,
8388
footerMarginTop: tooltipOpts.footerMarginTop,
8389
8390
// Appearance
8391
caretSize: tooltipOpts.caretSize,
8392
cornerRadius: tooltipOpts.cornerRadius,
8393
backgroundColor: tooltipOpts.backgroundColor,
8394
opacity: 0,
8395
legendColorBackground: tooltipOpts.multiKeyBackground,
8396
displayColors: tooltipOpts.displayColors,
8397
borderColor: tooltipOpts.borderColor,
8398
borderWidth: tooltipOpts.borderWidth
8399
};
8400
}
8401
8402
/**
8403
* Get the size of the tooltip
8404
*/
8405
function getTooltipSize(tooltip, model) {
8406
var ctx = tooltip._chart.ctx;
8407
8408
var height = model.yPadding * 2; // Tooltip Padding
8409
var width = 0;
8410
8411
// Count of all lines in the body
8412
var body = model.body;
8413
var combinedBodyLength = body.reduce(function(count, bodyItem) {
8414
return count + bodyItem.before.length + bodyItem.lines.length + bodyItem.after.length;
8415
}, 0);
8416
combinedBodyLength += model.beforeBody.length + model.afterBody.length;
8417
8418
var titleLineCount = model.title.length;
8419
var footerLineCount = model.footer.length;
8420
var titleFontSize = model.titleFontSize;
8421
var bodyFontSize = model.bodyFontSize;
8422
var footerFontSize = model.footerFontSize;
8423
8424
height += titleLineCount * titleFontSize; // Title Lines
8425
height += titleLineCount ? (titleLineCount - 1) * model.titleSpacing : 0; // Title Line Spacing
8426
height += titleLineCount ? model.titleMarginBottom : 0; // Title's bottom Margin
8427
height += combinedBodyLength * bodyFontSize; // Body Lines
8428
height += combinedBodyLength ? (combinedBodyLength - 1) * model.bodySpacing : 0; // Body Line Spacing
8429
height += footerLineCount ? model.footerMarginTop : 0; // Footer Margin
8430
height += footerLineCount * (footerFontSize); // Footer Lines
8431
height += footerLineCount ? (footerLineCount - 1) * model.footerSpacing : 0; // Footer Line Spacing
8432
8433
// Title width
8434
var widthPadding = 0;
8435
var maxLineWidth = function(line) {
8436
width = Math.max(width, ctx.measureText(line).width + widthPadding);
8437
};
8438
8439
ctx.font = helpers$1.fontString(titleFontSize, model._titleFontStyle, model._titleFontFamily);
8440
helpers$1.each(model.title, maxLineWidth);
8441
8442
// Body width
8443
ctx.font = helpers$1.fontString(bodyFontSize, model._bodyFontStyle, model._bodyFontFamily);
8444
helpers$1.each(model.beforeBody.concat(model.afterBody), maxLineWidth);
8445
8446
// Body lines may include some extra width due to the color box
8447
widthPadding = model.displayColors ? (bodyFontSize + 2) : 0;
8448
helpers$1.each(body, function(bodyItem) {
8449
helpers$1.each(bodyItem.before, maxLineWidth);
8450
helpers$1.each(bodyItem.lines, maxLineWidth);
8451
helpers$1.each(bodyItem.after, maxLineWidth);
8452
});
8453
8454
// Reset back to 0
8455
widthPadding = 0;
8456
8457
// Footer width
8458
ctx.font = helpers$1.fontString(footerFontSize, model._footerFontStyle, model._footerFontFamily);
8459
helpers$1.each(model.footer, maxLineWidth);
8460
8461
// Add padding
8462
width += 2 * model.xPadding;
8463
8464
return {
8465
width: width,
8466
height: height
8467
};
8468
}
8469
8470
/**
8471
* Helper to get the alignment of a tooltip given the size
8472
*/
8473
function determineAlignment(tooltip, size) {
8474
var model = tooltip._model;
8475
var chart = tooltip._chart;
8476
var chartArea = tooltip._chart.chartArea;
8477
var xAlign = 'center';
8478
var yAlign = 'center';
8479
8480
if (model.y < size.height) {
8481
yAlign = 'top';
8482
} else if (model.y > (chart.height - size.height)) {
8483
yAlign = 'bottom';
8484
}
8485
8486
var lf, rf; // functions to determine left, right alignment
8487
var olf, orf; // functions to determine if left/right alignment causes tooltip to go outside chart
8488
var yf; // function to get the y alignment if the tooltip goes outside of the left or right edges
8489
var midX = (chartArea.left + chartArea.right) / 2;
8490
var midY = (chartArea.top + chartArea.bottom) / 2;
8491
8492
if (yAlign === 'center') {
8493
lf = function(x) {
8494
return x <= midX;
8495
};
8496
rf = function(x) {
8497
return x > midX;
8498
};
8499
} else {
8500
lf = function(x) {
8501
return x <= (size.width / 2);
8502
};
8503
rf = function(x) {
8504
return x >= (chart.width - (size.width / 2));
8505
};
8506
}
8507
8508
olf = function(x) {
8509
return x + size.width + model.caretSize + model.caretPadding > chart.width;
8510
};
8511
orf = function(x) {
8512
return x - size.width - model.caretSize - model.caretPadding < 0;
8513
};
8514
yf = function(y) {
8515
return y <= midY ? 'top' : 'bottom';
8516
};
8517
8518
if (lf(model.x)) {
8519
xAlign = 'left';
8520
8521
// Is tooltip too wide and goes over the right side of the chart.?
8522
if (olf(model.x)) {
8523
xAlign = 'center';
8524
yAlign = yf(model.y);
8525
}
8526
} else if (rf(model.x)) {
8527
xAlign = 'right';
8528
8529
// Is tooltip too wide and goes outside left edge of canvas?
8530
if (orf(model.x)) {
8531
xAlign = 'center';
8532
yAlign = yf(model.y);
8533
}
8534
}
8535
8536
var opts = tooltip._options;
8537
return {
8538
xAlign: opts.xAlign ? opts.xAlign : xAlign,
8539
yAlign: opts.yAlign ? opts.yAlign : yAlign
8540
};
8541
}
8542
8543
/**
8544
* Helper to get the location a tooltip needs to be placed at given the initial position (via the vm) and the size and alignment
8545
*/
8546
function getBackgroundPoint(vm, size, alignment, chart) {
8547
// Background Position
8548
var x = vm.x;
8549
var y = vm.y;
8550
8551
var caretSize = vm.caretSize;
8552
var caretPadding = vm.caretPadding;
8553
var cornerRadius = vm.cornerRadius;
8554
var xAlign = alignment.xAlign;
8555
var yAlign = alignment.yAlign;
8556
var paddingAndSize = caretSize + caretPadding;
8557
var radiusAndPadding = cornerRadius + caretPadding;
8558
8559
if (xAlign === 'right') {
8560
x -= size.width;
8561
} else if (xAlign === 'center') {
8562
x -= (size.width / 2);
8563
if (x + size.width > chart.width) {
8564
x = chart.width - size.width;
8565
}
8566
if (x < 0) {
8567
x = 0;
8568
}
8569
}
8570
8571
if (yAlign === 'top') {
8572
y += paddingAndSize;
8573
} else if (yAlign === 'bottom') {
8574
y -= size.height + paddingAndSize;
8575
} else {
8576
y -= (size.height / 2);
8577
}
8578
8579
if (yAlign === 'center') {
8580
if (xAlign === 'left') {
8581
x += paddingAndSize;
8582
} else if (xAlign === 'right') {
8583
x -= paddingAndSize;
8584
}
8585
} else if (xAlign === 'left') {
8586
x -= radiusAndPadding;
8587
} else if (xAlign === 'right') {
8588
x += radiusAndPadding;
8589
}
8590
8591
return {
8592
x: x,
8593
y: y
8594
};
8595
}
8596
8597
function getAlignedX(vm, align) {
8598
return align === 'center'
8599
? vm.x + vm.width / 2
8600
: align === 'right'
8601
? vm.x + vm.width - vm.xPadding
8602
: vm.x + vm.xPadding;
8603
}
8604
8605
/**
8606
* Helper to build before and after body lines
8607
*/
8608
function getBeforeAfterBodyLines(callback) {
8609
return pushOrConcat([], splitNewlines(callback));
8610
}
8611
8612
var exports$4 = core_element.extend({
8613
initialize: function() {
8614
this._model = getBaseModel(this._options);
8615
this._lastActive = [];
8616
},
8617
8618
// Get the title
8619
// Args are: (tooltipItem, data)
8620
getTitle: function() {
8621
var me = this;
8622
var opts = me._options;
8623
var callbacks = opts.callbacks;
8624
8625
var beforeTitle = callbacks.beforeTitle.apply(me, arguments);
8626
var title = callbacks.title.apply(me, arguments);
8627
var afterTitle = callbacks.afterTitle.apply(me, arguments);
8628
8629
var lines = [];
8630
lines = pushOrConcat(lines, splitNewlines(beforeTitle));
8631
lines = pushOrConcat(lines, splitNewlines(title));
8632
lines = pushOrConcat(lines, splitNewlines(afterTitle));
8633
8634
return lines;
8635
},
8636
8637
// Args are: (tooltipItem, data)
8638
getBeforeBody: function() {
8639
return getBeforeAfterBodyLines(this._options.callbacks.beforeBody.apply(this, arguments));
8640
},
8641
8642
// Args are: (tooltipItem, data)
8643
getBody: function(tooltipItems, data) {
8644
var me = this;
8645
var callbacks = me._options.callbacks;
8646
var bodyItems = [];
8647
8648
helpers$1.each(tooltipItems, function(tooltipItem) {
8649
var bodyItem = {
8650
before: [],
8651
lines: [],
8652
after: []
8653
};
8654
pushOrConcat(bodyItem.before, splitNewlines(callbacks.beforeLabel.call(me, tooltipItem, data)));
8655
pushOrConcat(bodyItem.lines, callbacks.label.call(me, tooltipItem, data));
8656
pushOrConcat(bodyItem.after, splitNewlines(callbacks.afterLabel.call(me, tooltipItem, data)));
8657
8658
bodyItems.push(bodyItem);
8659
});
8660
8661
return bodyItems;
8662
},
8663
8664
// Args are: (tooltipItem, data)
8665
getAfterBody: function() {
8666
return getBeforeAfterBodyLines(this._options.callbacks.afterBody.apply(this, arguments));
8667
},
8668
8669
// Get the footer and beforeFooter and afterFooter lines
8670
// Args are: (tooltipItem, data)
8671
getFooter: function() {
8672
var me = this;
8673
var callbacks = me._options.callbacks;
8674
8675
var beforeFooter = callbacks.beforeFooter.apply(me, arguments);
8676
var footer = callbacks.footer.apply(me, arguments);
8677
var afterFooter = callbacks.afterFooter.apply(me, arguments);
8678
8679
var lines = [];
8680
lines = pushOrConcat(lines, splitNewlines(beforeFooter));
8681
lines = pushOrConcat(lines, splitNewlines(footer));
8682
lines = pushOrConcat(lines, splitNewlines(afterFooter));
8683
8684
return lines;
8685
},
8686
8687
update: function(changed) {
8688
var me = this;
8689
var opts = me._options;
8690
8691
// Need to regenerate the model because its faster than using extend and it is necessary due to the optimization in Chart.Element.transition
8692
// that does _view = _model if ease === 1. This causes the 2nd tooltip update to set properties in both the view and model at the same time
8693
// which breaks any animations.
8694
var existingModel = me._model;
8695
var model = me._model = getBaseModel(opts);
8696
var active = me._active;
8697
8698
var data = me._data;
8699
8700
// In the case where active.length === 0 we need to keep these at existing values for good animations
8701
var alignment = {
8702
xAlign: existingModel.xAlign,
8703
yAlign: existingModel.yAlign
8704
};
8705
var backgroundPoint = {
8706
x: existingModel.x,
8707
y: existingModel.y
8708
};
8709
var tooltipSize = {
8710
width: existingModel.width,
8711
height: existingModel.height
8712
};
8713
var tooltipPosition = {
8714
x: existingModel.caretX,
8715
y: existingModel.caretY
8716
};
8717
8718
var i, len;
8719
8720
if (active.length) {
8721
model.opacity = 1;
8722
8723
var labelColors = [];
8724
var labelTextColors = [];
8725
tooltipPosition = positioners[opts.position].call(me, active, me._eventPosition);
8726
8727
var tooltipItems = [];
8728
for (i = 0, len = active.length; i < len; ++i) {
8729
tooltipItems.push(createTooltipItem(active[i]));
8730
}
8731
8732
// If the user provided a filter function, use it to modify the tooltip items
8733
if (opts.filter) {
8734
tooltipItems = tooltipItems.filter(function(a) {
8735
return opts.filter(a, data);
8736
});
8737
}
8738
8739
// If the user provided a sorting function, use it to modify the tooltip items
8740
if (opts.itemSort) {
8741
tooltipItems = tooltipItems.sort(function(a, b) {
8742
return opts.itemSort(a, b, data);
8743
});
8744
}
8745
8746
// Determine colors for boxes
8747
helpers$1.each(tooltipItems, function(tooltipItem) {
8748
labelColors.push(opts.callbacks.labelColor.call(me, tooltipItem, me._chart));
8749
labelTextColors.push(opts.callbacks.labelTextColor.call(me, tooltipItem, me._chart));
8750
});
8751
8752
8753
// Build the Text Lines
8754
model.title = me.getTitle(tooltipItems, data);
8755
model.beforeBody = me.getBeforeBody(tooltipItems, data);
8756
model.body = me.getBody(tooltipItems, data);
8757
model.afterBody = me.getAfterBody(tooltipItems, data);
8758
model.footer = me.getFooter(tooltipItems, data);
8759
8760
// Initial positioning and colors
8761
model.x = tooltipPosition.x;
8762
model.y = tooltipPosition.y;
8763
model.caretPadding = opts.caretPadding;
8764
model.labelColors = labelColors;
8765
model.labelTextColors = labelTextColors;
8766
8767
// data points
8768
model.dataPoints = tooltipItems;
8769
8770
// We need to determine alignment of the tooltip
8771
tooltipSize = getTooltipSize(this, model);
8772
alignment = determineAlignment(this, tooltipSize);
8773
// Final Size and Position
8774
backgroundPoint = getBackgroundPoint(model, tooltipSize, alignment, me._chart);
8775
} else {
8776
model.opacity = 0;
8777
}
8778
8779
model.xAlign = alignment.xAlign;
8780
model.yAlign = alignment.yAlign;
8781
model.x = backgroundPoint.x;
8782
model.y = backgroundPoint.y;
8783
model.width = tooltipSize.width;
8784
model.height = tooltipSize.height;
8785
8786
// Point where the caret on the tooltip points to
8787
model.caretX = tooltipPosition.x;
8788
model.caretY = tooltipPosition.y;
8789
8790
me._model = model;
8791
8792
if (changed && opts.custom) {
8793
opts.custom.call(me, model);
8794
}
8795
8796
return me;
8797
},
8798
8799
drawCaret: function(tooltipPoint, size) {
8800
var ctx = this._chart.ctx;
8801
var vm = this._view;
8802
var caretPosition = this.getCaretPosition(tooltipPoint, size, vm);
8803
8804
ctx.lineTo(caretPosition.x1, caretPosition.y1);
8805
ctx.lineTo(caretPosition.x2, caretPosition.y2);
8806
ctx.lineTo(caretPosition.x3, caretPosition.y3);
8807
},
8808
getCaretPosition: function(tooltipPoint, size, vm) {
8809
var x1, x2, x3, y1, y2, y3;
8810
var caretSize = vm.caretSize;
8811
var cornerRadius = vm.cornerRadius;
8812
var xAlign = vm.xAlign;
8813
var yAlign = vm.yAlign;
8814
var ptX = tooltipPoint.x;
8815
var ptY = tooltipPoint.y;
8816
var width = size.width;
8817
var height = size.height;
8818
8819
if (yAlign === 'center') {
8820
y2 = ptY + (height / 2);
8821
8822
if (xAlign === 'left') {
8823
x1 = ptX;
8824
x2 = x1 - caretSize;
8825
x3 = x1;
8826
8827
y1 = y2 + caretSize;
8828
y3 = y2 - caretSize;
8829
} else {
8830
x1 = ptX + width;
8831
x2 = x1 + caretSize;
8832
x3 = x1;
8833
8834
y1 = y2 - caretSize;
8835
y3 = y2 + caretSize;
8836
}
8837
} else {
8838
if (xAlign === 'left') {
8839
x2 = ptX + cornerRadius + (caretSize);
8840
x1 = x2 - caretSize;
8841
x3 = x2 + caretSize;
8842
} else if (xAlign === 'right') {
8843
x2 = ptX + width - cornerRadius - caretSize;
8844
x1 = x2 - caretSize;
8845
x3 = x2 + caretSize;
8846
} else {
8847
x2 = vm.caretX;
8848
x1 = x2 - caretSize;
8849
x3 = x2 + caretSize;
8850
}
8851
if (yAlign === 'top') {
8852
y1 = ptY;
8853
y2 = y1 - caretSize;
8854
y3 = y1;
8855
} else {
8856
y1 = ptY + height;
8857
y2 = y1 + caretSize;
8858
y3 = y1;
8859
// invert drawing order
8860
var tmp = x3;
8861
x3 = x1;
8862
x1 = tmp;
8863
}
8864
}
8865
return {x1: x1, x2: x2, x3: x3, y1: y1, y2: y2, y3: y3};
8866
},
8867
8868
drawTitle: function(pt, vm, ctx) {
8869
var title = vm.title;
8870
var length = title.length;
8871
var titleFontSize, titleSpacing, i;
8872
8873
if (length) {
8874
var rtlHelper = getRtlHelper(vm.rtl, vm.x, vm.width);
8875
8876
pt.x = getAlignedX(vm, vm._titleAlign);
8877
8878
ctx.textAlign = rtlHelper.textAlign(vm._titleAlign);
8879
ctx.textBaseline = 'middle';
8880
8881
titleFontSize = vm.titleFontSize;
8882
titleSpacing = vm.titleSpacing;
8883
8884
ctx.fillStyle = vm.titleFontColor;
8885
ctx.font = helpers$1.fontString(titleFontSize, vm._titleFontStyle, vm._titleFontFamily);
8886
8887
for (i = 0; i < length; ++i) {
8888
ctx.fillText(title[i], rtlHelper.x(pt.x), pt.y + titleFontSize / 2);
8889
pt.y += titleFontSize + titleSpacing; // Line Height and spacing
8890
8891
if (i + 1 === length) {
8892
pt.y += vm.titleMarginBottom - titleSpacing; // If Last, add margin, remove spacing
8893
}
8894
}
8895
}
8896
},
8897
8898
drawBody: function(pt, vm, ctx) {
8899
var bodyFontSize = vm.bodyFontSize;
8900
var bodySpacing = vm.bodySpacing;
8901
var bodyAlign = vm._bodyAlign;
8902
var body = vm.body;
8903
var drawColorBoxes = vm.displayColors;
8904
var xLinePadding = 0;
8905
var colorX = drawColorBoxes ? getAlignedX(vm, 'left') : 0;
8906
8907
var rtlHelper = getRtlHelper(vm.rtl, vm.x, vm.width);
8908
8909
var fillLineOfText = function(line) {
8910
ctx.fillText(line, rtlHelper.x(pt.x + xLinePadding), pt.y + bodyFontSize / 2);
8911
pt.y += bodyFontSize + bodySpacing;
8912
};
8913
8914
var bodyItem, textColor, labelColors, lines, i, j, ilen, jlen;
8915
var bodyAlignForCalculation = rtlHelper.textAlign(bodyAlign);
8916
8917
ctx.textAlign = bodyAlign;
8918
ctx.textBaseline = 'middle';
8919
ctx.font = helpers$1.fontString(bodyFontSize, vm._bodyFontStyle, vm._bodyFontFamily);
8920
8921
pt.x = getAlignedX(vm, bodyAlignForCalculation);
8922
8923
// Before body lines
8924
ctx.fillStyle = vm.bodyFontColor;
8925
helpers$1.each(vm.beforeBody, fillLineOfText);
8926
8927
xLinePadding = drawColorBoxes && bodyAlignForCalculation !== 'right'
8928
? bodyAlign === 'center' ? (bodyFontSize / 2 + 1) : (bodyFontSize + 2)
8929
: 0;
8930
8931
// Draw body lines now
8932
for (i = 0, ilen = body.length; i < ilen; ++i) {
8933
bodyItem = body[i];
8934
textColor = vm.labelTextColors[i];
8935
labelColors = vm.labelColors[i];
8936
8937
ctx.fillStyle = textColor;
8938
helpers$1.each(bodyItem.before, fillLineOfText);
8939
8940
lines = bodyItem.lines;
8941
for (j = 0, jlen = lines.length; j < jlen; ++j) {
8942
// Draw Legend-like boxes if needed
8943
if (drawColorBoxes) {
8944
var rtlColorX = rtlHelper.x(colorX);
8945
8946
// Fill a white rect so that colours merge nicely if the opacity is < 1
8947
ctx.fillStyle = vm.legendColorBackground;
8948
ctx.fillRect(rtlHelper.leftForLtr(rtlColorX, bodyFontSize), pt.y, bodyFontSize, bodyFontSize);
8949
8950
// Border
8951
ctx.lineWidth = 1;
8952
ctx.strokeStyle = labelColors.borderColor;
8953
ctx.strokeRect(rtlHelper.leftForLtr(rtlColorX, bodyFontSize), pt.y, bodyFontSize, bodyFontSize);
8954
8955
// Inner square
8956
ctx.fillStyle = labelColors.backgroundColor;
8957
ctx.fillRect(rtlHelper.leftForLtr(rtlHelper.xPlus(rtlColorX, 1), bodyFontSize - 2), pt.y + 1, bodyFontSize - 2, bodyFontSize - 2);
8958
ctx.fillStyle = textColor;
8959
}
8960
8961
fillLineOfText(lines[j]);
8962
}
8963
8964
helpers$1.each(bodyItem.after, fillLineOfText);
8965
}
8966
8967
// Reset back to 0 for after body
8968
xLinePadding = 0;
8969
8970
// After body lines
8971
helpers$1.each(vm.afterBody, fillLineOfText);
8972
pt.y -= bodySpacing; // Remove last body spacing
8973
},
8974
8975
drawFooter: function(pt, vm, ctx) {
8976
var footer = vm.footer;
8977
var length = footer.length;
8978
var footerFontSize, i;
8979
8980
if (length) {
8981
var rtlHelper = getRtlHelper(vm.rtl, vm.x, vm.width);
8982
8983
pt.x = getAlignedX(vm, vm._footerAlign);
8984
pt.y += vm.footerMarginTop;
8985
8986
ctx.textAlign = rtlHelper.textAlign(vm._footerAlign);
8987
ctx.textBaseline = 'middle';
8988
8989
footerFontSize = vm.footerFontSize;
8990
8991
ctx.fillStyle = vm.footerFontColor;
8992
ctx.font = helpers$1.fontString(footerFontSize, vm._footerFontStyle, vm._footerFontFamily);
8993
8994
for (i = 0; i < length; ++i) {
8995
ctx.fillText(footer[i], rtlHelper.x(pt.x), pt.y + footerFontSize / 2);
8996
pt.y += footerFontSize + vm.footerSpacing;
8997
}
8998
}
8999
},
9000
9001
drawBackground: function(pt, vm, ctx, tooltipSize) {
9002
ctx.fillStyle = vm.backgroundColor;
9003
ctx.strokeStyle = vm.borderColor;
9004
ctx.lineWidth = vm.borderWidth;
9005
var xAlign = vm.xAlign;
9006
var yAlign = vm.yAlign;
9007
var x = pt.x;
9008
var y = pt.y;
9009
var width = tooltipSize.width;
9010
var height = tooltipSize.height;
9011
var radius = vm.cornerRadius;
9012
9013
ctx.beginPath();
9014
ctx.moveTo(x + radius, y);
9015
if (yAlign === 'top') {
9016
this.drawCaret(pt, tooltipSize);
9017
}
9018
ctx.lineTo(x + width - radius, y);
9019
ctx.quadraticCurveTo(x + width, y, x + width, y + radius);
9020
if (yAlign === 'center' && xAlign === 'right') {
9021
this.drawCaret(pt, tooltipSize);
9022
}
9023
ctx.lineTo(x + width, y + height - radius);
9024
ctx.quadraticCurveTo(x + width, y + height, x + width - radius, y + height);
9025
if (yAlign === 'bottom') {
9026
this.drawCaret(pt, tooltipSize);
9027
}
9028
ctx.lineTo(x + radius, y + height);
9029
ctx.quadraticCurveTo(x, y + height, x, y + height - radius);
9030
if (yAlign === 'center' && xAlign === 'left') {
9031
this.drawCaret(pt, tooltipSize);
9032
}
9033
ctx.lineTo(x, y + radius);
9034
ctx.quadraticCurveTo(x, y, x + radius, y);
9035
ctx.closePath();
9036
9037
ctx.fill();
9038
9039
if (vm.borderWidth > 0) {
9040
ctx.stroke();
9041
}
9042
},
9043
9044
draw: function() {
9045
var ctx = this._chart.ctx;
9046
var vm = this._view;
9047
9048
if (vm.opacity === 0) {
9049
return;
9050
}
9051
9052
var tooltipSize = {
9053
width: vm.width,
9054
height: vm.height
9055
};
9056
var pt = {
9057
x: vm.x,
9058
y: vm.y
9059
};
9060
9061
// IE11/Edge does not like very small opacities, so snap to 0
9062
var opacity = Math.abs(vm.opacity < 1e-3) ? 0 : vm.opacity;
9063
9064
// Truthy/falsey value for empty tooltip
9065
var hasTooltipContent = vm.title.length || vm.beforeBody.length || vm.body.length || vm.afterBody.length || vm.footer.length;
9066
9067
if (this._options.enabled && hasTooltipContent) {
9068
ctx.save();
9069
ctx.globalAlpha = opacity;
9070
9071
// Draw Background
9072
this.drawBackground(pt, vm, ctx, tooltipSize);
9073
9074
// Draw Title, Body, and Footer
9075
pt.y += vm.yPadding;
9076
9077
helpers$1.rtl.overrideTextDirection(ctx, vm.textDirection);
9078
9079
// Titles
9080
this.drawTitle(pt, vm, ctx);
9081
9082
// Body
9083
this.drawBody(pt, vm, ctx);
9084
9085
// Footer
9086
this.drawFooter(pt, vm, ctx);
9087
9088
helpers$1.rtl.restoreTextDirection(ctx, vm.textDirection);
9089
9090
ctx.restore();
9091
}
9092
},
9093
9094
/**
9095
* Handle an event
9096
* @private
9097
* @param {IEvent} event - The event to handle
9098
* @returns {boolean} true if the tooltip changed
9099
*/
9100
handleEvent: function(e) {
9101
var me = this;
9102
var options = me._options;
9103
var changed = false;
9104
9105
me._lastActive = me._lastActive || [];
9106
9107
// Find Active Elements for tooltips
9108
if (e.type === 'mouseout') {
9109
me._active = [];
9110
} else {
9111
me._active = me._chart.getElementsAtEventForMode(e, options.mode, options);
9112
if (options.reverse) {
9113
me._active.reverse();
9114
}
9115
}
9116
9117
// Remember Last Actives
9118
changed = !helpers$1.arrayEquals(me._active, me._lastActive);
9119
9120
// Only handle target event on tooltip change
9121
if (changed) {
9122
me._lastActive = me._active;
9123
9124
if (options.enabled || options.custom) {
9125
me._eventPosition = {
9126
x: e.x,
9127
y: e.y
9128
};
9129
9130
me.update(true);
9131
me.pivot();
9132
}
9133
}
9134
9135
return changed;
9136
}
9137
});
9138
9139
/**
9140
* @namespace Chart.Tooltip.positioners
9141
*/
9142
var positioners_1 = positioners;
9143
9144
var core_tooltip = exports$4;
9145
core_tooltip.positioners = positioners_1;
9146
9147
var valueOrDefault$9 = helpers$1.valueOrDefault;
9148
9149
core_defaults._set('global', {
9150
elements: {},
9151
events: [
9152
'mousemove',
9153
'mouseout',
9154
'click',
9155
'touchstart',
9156
'touchmove'
9157
],
9158
hover: {
9159
onHover: null,
9160
mode: 'nearest',
9161
intersect: true,
9162
animationDuration: 400
9163
},
9164
onClick: null,
9165
maintainAspectRatio: true,
9166
responsive: true,
9167
responsiveAnimationDuration: 0
9168
});
9169
9170
/**
9171
* Recursively merge the given config objects representing the `scales` option
9172
* by incorporating scale defaults in `xAxes` and `yAxes` array items, then
9173
* returns a deep copy of the result, thus doesn't alter inputs.
9174
*/
9175
function mergeScaleConfig(/* config objects ... */) {
9176
return helpers$1.merge({}, [].slice.call(arguments), {
9177
merger: function(key, target, source, options) {
9178
if (key === 'xAxes' || key === 'yAxes') {
9179
var slen = source[key].length;
9180
var i, type, scale;
9181
9182
if (!target[key]) {
9183
target[key] = [];
9184
}
9185
9186
for (i = 0; i < slen; ++i) {
9187
scale = source[key][i];
9188
type = valueOrDefault$9(scale.type, key === 'xAxes' ? 'category' : 'linear');
9189
9190
if (i >= target[key].length) {
9191
target[key].push({});
9192
}
9193
9194
if (!target[key][i].type || (scale.type && scale.type !== target[key][i].type)) {
9195
// new/untyped scale or type changed: let's apply the new defaults
9196
// then merge source scale to correctly overwrite the defaults.
9197
helpers$1.merge(target[key][i], [core_scaleService.getScaleDefaults(type), scale]);
9198
} else {
9199
// scales type are the same
9200
helpers$1.merge(target[key][i], scale);
9201
}
9202
}
9203
} else {
9204
helpers$1._merger(key, target, source, options);
9205
}
9206
}
9207
});
9208
}
9209
9210
/**
9211
* Recursively merge the given config objects as the root options by handling
9212
* default scale options for the `scales` and `scale` properties, then returns
9213
* a deep copy of the result, thus doesn't alter inputs.
9214
*/
9215
function mergeConfig(/* config objects ... */) {
9216
return helpers$1.merge({}, [].slice.call(arguments), {
9217
merger: function(key, target, source, options) {
9218
var tval = target[key] || {};
9219
var sval = source[key];
9220
9221
if (key === 'scales') {
9222
// scale config merging is complex. Add our own function here for that
9223
target[key] = mergeScaleConfig(tval, sval);
9224
} else if (key === 'scale') {
9225
// used in polar area & radar charts since there is only one scale
9226
target[key] = helpers$1.merge(tval, [core_scaleService.getScaleDefaults(sval.type), sval]);
9227
} else {
9228
helpers$1._merger(key, target, source, options);
9229
}
9230
}
9231
});
9232
}
9233
9234
function initConfig(config) {
9235
config = config || {};
9236
9237
// Do NOT use mergeConfig for the data object because this method merges arrays
9238
// and so would change references to labels and datasets, preventing data updates.
9239
var data = config.data = config.data || {};
9240
data.datasets = data.datasets || [];
9241
data.labels = data.labels || [];
9242
9243
config.options = mergeConfig(
9244
core_defaults.global,
9245
core_defaults[config.type],
9246
config.options || {});
9247
9248
return config;
9249
}
9250
9251
function updateConfig(chart) {
9252
var newOptions = chart.options;
9253
9254
helpers$1.each(chart.scales, function(scale) {
9255
core_layouts.removeBox(chart, scale);
9256
});
9257
9258
newOptions = mergeConfig(
9259
core_defaults.global,
9260
core_defaults[chart.config.type],
9261
newOptions);
9262
9263
chart.options = chart.config.options = newOptions;
9264
chart.ensureScalesHaveIDs();
9265
chart.buildOrUpdateScales();
9266
9267
// Tooltip
9268
chart.tooltip._options = newOptions.tooltips;
9269
chart.tooltip.initialize();
9270
}
9271
9272
function nextAvailableScaleId(axesOpts, prefix, index) {
9273
var id;
9274
var hasId = function(obj) {
9275
return obj.id === id;
9276
};
9277
9278
do {
9279
id = prefix + index++;
9280
} while (helpers$1.findIndex(axesOpts, hasId) >= 0);
9281
9282
return id;
9283
}
9284
9285
function positionIsHorizontal(position) {
9286
return position === 'top' || position === 'bottom';
9287
}
9288
9289
function compare2Level(l1, l2) {
9290
return function(a, b) {
9291
return a[l1] === b[l1]
9292
? a[l2] - b[l2]
9293
: a[l1] - b[l1];
9294
};
9295
}
9296
9297
var Chart = function(item, config) {
9298
this.construct(item, config);
9299
return this;
9300
};
9301
9302
helpers$1.extend(Chart.prototype, /** @lends Chart */ {
9303
/**
9304
* @private
9305
*/
9306
construct: function(item, config) {
9307
var me = this;
9308
9309
config = initConfig(config);
9310
9311
var context = platform.acquireContext(item, config);
9312
var canvas = context && context.canvas;
9313
var height = canvas && canvas.height;
9314
var width = canvas && canvas.width;
9315
9316
me.id = helpers$1.uid();
9317
me.ctx = context;
9318
me.canvas = canvas;
9319
me.config = config;
9320
me.width = width;
9321
me.height = height;
9322
me.aspectRatio = height ? width / height : null;
9323
me.options = config.options;
9324
me._bufferedRender = false;
9325
me._layers = [];
9326
9327
/**
9328
* Provided for backward compatibility, Chart and Chart.Controller have been merged,
9329
* the "instance" still need to be defined since it might be called from plugins.
9330
* @prop Chart#chart
9331
* @deprecated since version 2.6.0
9332
* @todo remove at version 3
9333
* @private
9334
*/
9335
me.chart = me;
9336
me.controller = me; // chart.chart.controller #inception
9337
9338
// Add the chart instance to the global namespace
9339
Chart.instances[me.id] = me;
9340
9341
// Define alias to the config data: `chart.data === chart.config.data`
9342
Object.defineProperty(me, 'data', {
9343
get: function() {
9344
return me.config.data;
9345
},
9346
set: function(value) {
9347
me.config.data = value;
9348
}
9349
});
9350
9351
if (!context || !canvas) {
9352
// The given item is not a compatible context2d element, let's return before finalizing
9353
// the chart initialization but after setting basic chart / controller properties that
9354
// can help to figure out that the chart is not valid (e.g chart.canvas !== null);
9355
// https://github.com/chartjs/Chart.js/issues/2807
9356
console.error("Failed to create chart: can't acquire context from the given item");
9357
return;
9358
}
9359
9360
me.initialize();
9361
me.update();
9362
},
9363
9364
/**
9365
* @private
9366
*/
9367
initialize: function() {
9368
var me = this;
9369
9370
// Before init plugin notification
9371
core_plugins.notify(me, 'beforeInit');
9372
9373
helpers$1.retinaScale(me, me.options.devicePixelRatio);
9374
9375
me.bindEvents();
9376
9377
if (me.options.responsive) {
9378
// Initial resize before chart draws (must be silent to preserve initial animations).
9379
me.resize(true);
9380
}
9381
9382
me.initToolTip();
9383
9384
// After init plugin notification
9385
core_plugins.notify(me, 'afterInit');
9386
9387
return me;
9388
},
9389
9390
clear: function() {
9391
helpers$1.canvas.clear(this);
9392
return this;
9393
},
9394
9395
stop: function() {
9396
// Stops any current animation loop occurring
9397
core_animations.cancelAnimation(this);
9398
return this;
9399
},
9400
9401
resize: function(silent) {
9402
var me = this;
9403
var options = me.options;
9404
var canvas = me.canvas;
9405
var aspectRatio = (options.maintainAspectRatio && me.aspectRatio) || null;
9406
9407
// the canvas render width and height will be casted to integers so make sure that
9408
// the canvas display style uses the same integer values to avoid blurring effect.
9409
9410
// Set to 0 instead of canvas.size because the size defaults to 300x150 if the element is collapsed
9411
var newWidth = Math.max(0, Math.floor(helpers$1.getMaximumWidth(canvas)));
9412
var newHeight = Math.max(0, Math.floor(aspectRatio ? newWidth / aspectRatio : helpers$1.getMaximumHeight(canvas)));
9413
9414
if (me.width === newWidth && me.height === newHeight) {
9415
return;
9416
}
9417
9418
canvas.width = me.width = newWidth;
9419
canvas.height = me.height = newHeight;
9420
canvas.style.width = newWidth + 'px';
9421
canvas.style.height = newHeight + 'px';
9422
9423
helpers$1.retinaScale(me, options.devicePixelRatio);
9424
9425
if (!silent) {
9426
// Notify any plugins about the resize
9427
var newSize = {width: newWidth, height: newHeight};
9428
core_plugins.notify(me, 'resize', [newSize]);
9429
9430
// Notify of resize
9431
if (options.onResize) {
9432
options.onResize(me, newSize);
9433
}
9434
9435
me.stop();
9436
me.update({
9437
duration: options.responsiveAnimationDuration
9438
});
9439
}
9440
},
9441
9442
ensureScalesHaveIDs: function() {
9443
var options = this.options;
9444
var scalesOptions = options.scales || {};
9445
var scaleOptions = options.scale;
9446
9447
helpers$1.each(scalesOptions.xAxes, function(xAxisOptions, index) {
9448
if (!xAxisOptions.id) {
9449
xAxisOptions.id = nextAvailableScaleId(scalesOptions.xAxes, 'x-axis-', index);
9450
}
9451
});
9452
9453
helpers$1.each(scalesOptions.yAxes, function(yAxisOptions, index) {
9454
if (!yAxisOptions.id) {
9455
yAxisOptions.id = nextAvailableScaleId(scalesOptions.yAxes, 'y-axis-', index);
9456
}
9457
});
9458
9459
if (scaleOptions) {
9460
scaleOptions.id = scaleOptions.id || 'scale';
9461
}
9462
},
9463
9464
/**
9465
* Builds a map of scale ID to scale object for future lookup.
9466
*/
9467
buildOrUpdateScales: function() {
9468
var me = this;
9469
var options = me.options;
9470
var scales = me.scales || {};
9471
var items = [];
9472
var updated = Object.keys(scales).reduce(function(obj, id) {
9473
obj[id] = false;
9474
return obj;
9475
}, {});
9476
9477
if (options.scales) {
9478
items = items.concat(
9479
(options.scales.xAxes || []).map(function(xAxisOptions) {
9480
return {options: xAxisOptions, dtype: 'category', dposition: 'bottom'};
9481
}),
9482
(options.scales.yAxes || []).map(function(yAxisOptions) {
9483
return {options: yAxisOptions, dtype: 'linear', dposition: 'left'};
9484
})
9485
);
9486
}
9487
9488
if (options.scale) {
9489
items.push({
9490
options: options.scale,
9491
dtype: 'radialLinear',
9492
isDefault: true,
9493
dposition: 'chartArea'
9494
});
9495
}
9496
9497
helpers$1.each(items, function(item) {
9498
var scaleOptions = item.options;
9499
var id = scaleOptions.id;
9500
var scaleType = valueOrDefault$9(scaleOptions.type, item.dtype);
9501
9502
if (positionIsHorizontal(scaleOptions.position) !== positionIsHorizontal(item.dposition)) {
9503
scaleOptions.position = item.dposition;
9504
}
9505
9506
updated[id] = true;
9507
var scale = null;
9508
if (id in scales && scales[id].type === scaleType) {
9509
scale = scales[id];
9510
scale.options = scaleOptions;
9511
scale.ctx = me.ctx;
9512
scale.chart = me;
9513
} else {
9514
var scaleClass = core_scaleService.getScaleConstructor(scaleType);
9515
if (!scaleClass) {
9516
return;
9517
}
9518
scale = new scaleClass({
9519
id: id,
9520
type: scaleType,
9521
options: scaleOptions,
9522
ctx: me.ctx,
9523
chart: me
9524
});
9525
scales[scale.id] = scale;
9526
}
9527
9528
scale.mergeTicksOptions();
9529
9530
// TODO(SB): I think we should be able to remove this custom case (options.scale)
9531
// and consider it as a regular scale part of the "scales"" map only! This would
9532
// make the logic easier and remove some useless? custom code.
9533
if (item.isDefault) {
9534
me.scale = scale;
9535
}
9536
});
9537
// clear up discarded scales
9538
helpers$1.each(updated, function(hasUpdated, id) {
9539
if (!hasUpdated) {
9540
delete scales[id];
9541
}
9542
});
9543
9544
me.scales = scales;
9545
9546
core_scaleService.addScalesToLayout(this);
9547
},
9548
9549
buildOrUpdateControllers: function() {
9550
var me = this;
9551
var newControllers = [];
9552
var datasets = me.data.datasets;
9553
var i, ilen;
9554
9555
for (i = 0, ilen = datasets.length; i < ilen; i++) {
9556
var dataset = datasets[i];
9557
var meta = me.getDatasetMeta(i);
9558
var type = dataset.type || me.config.type;
9559
9560
if (meta.type && meta.type !== type) {
9561
me.destroyDatasetMeta(i);
9562
meta = me.getDatasetMeta(i);
9563
}
9564
meta.type = type;
9565
meta.order = dataset.order || 0;
9566
meta.index = i;
9567
9568
if (meta.controller) {
9569
meta.controller.updateIndex(i);
9570
meta.controller.linkScales();
9571
} else {
9572
var ControllerClass = controllers[meta.type];
9573
if (ControllerClass === undefined) {
9574
throw new Error('"' + meta.type + '" is not a chart type.');
9575
}
9576
9577
meta.controller = new ControllerClass(me, i);
9578
newControllers.push(meta.controller);
9579
}
9580
}
9581
9582
return newControllers;
9583
},
9584
9585
/**
9586
* Reset the elements of all datasets
9587
* @private
9588
*/
9589
resetElements: function() {
9590
var me = this;
9591
helpers$1.each(me.data.datasets, function(dataset, datasetIndex) {
9592
me.getDatasetMeta(datasetIndex).controller.reset();
9593
}, me);
9594
},
9595
9596
/**
9597
* Resets the chart back to it's state before the initial animation
9598
*/
9599
reset: function() {
9600
this.resetElements();
9601
this.tooltip.initialize();
9602
},
9603
9604
update: function(config) {
9605
var me = this;
9606
var i, ilen;
9607
9608
if (!config || typeof config !== 'object') {
9609
// backwards compatibility
9610
config = {
9611
duration: config,
9612
lazy: arguments[1]
9613
};
9614
}
9615
9616
updateConfig(me);
9617
9618
// plugins options references might have change, let's invalidate the cache
9619
// https://github.com/chartjs/Chart.js/issues/5111#issuecomment-355934167
9620
core_plugins._invalidate(me);
9621
9622
if (core_plugins.notify(me, 'beforeUpdate') === false) {
9623
return;
9624
}
9625
9626
// In case the entire data object changed
9627
me.tooltip._data = me.data;
9628
9629
// Make sure dataset controllers are updated and new controllers are reset
9630
var newControllers = me.buildOrUpdateControllers();
9631
9632
// Make sure all dataset controllers have correct meta data counts
9633
for (i = 0, ilen = me.data.datasets.length; i < ilen; i++) {
9634
me.getDatasetMeta(i).controller.buildOrUpdateElements();
9635
}
9636
9637
me.updateLayout();
9638
9639
// Can only reset the new controllers after the scales have been updated
9640
if (me.options.animation && me.options.animation.duration) {
9641
helpers$1.each(newControllers, function(controller) {
9642
controller.reset();
9643
});
9644
}
9645
9646
me.updateDatasets();
9647
9648
// Need to reset tooltip in case it is displayed with elements that are removed
9649
// after update.
9650
me.tooltip.initialize();
9651
9652
// Last active contains items that were previously in the tooltip.
9653
// When we reset the tooltip, we need to clear it
9654
me.lastActive = [];
9655
9656
// Do this before render so that any plugins that need final scale updates can use it
9657
core_plugins.notify(me, 'afterUpdate');
9658
9659
me._layers.sort(compare2Level('z', '_idx'));
9660
9661
if (me._bufferedRender) {
9662
me._bufferedRequest = {
9663
duration: config.duration,
9664
easing: config.easing,
9665
lazy: config.lazy
9666
};
9667
} else {
9668
me.render(config);
9669
}
9670
},
9671
9672
/**
9673
* Updates the chart layout unless a plugin returns `false` to the `beforeLayout`
9674
* hook, in which case, plugins will not be called on `afterLayout`.
9675
* @private
9676
*/
9677
updateLayout: function() {
9678
var me = this;
9679
9680
if (core_plugins.notify(me, 'beforeLayout') === false) {
9681
return;
9682
}
9683
9684
core_layouts.update(this, this.width, this.height);
9685
9686
me._layers = [];
9687
helpers$1.each(me.boxes, function(box) {
9688
// _configure is called twice, once in core.scale.update and once here.
9689
// Here the boxes are fully updated and at their final positions.
9690
if (box._configure) {
9691
box._configure();
9692
}
9693
me._layers.push.apply(me._layers, box._layers());
9694
}, me);
9695
9696
me._layers.forEach(function(item, index) {
9697
item._idx = index;
9698
});
9699
9700
/**
9701
* Provided for backward compatibility, use `afterLayout` instead.
9702
* @method IPlugin#afterScaleUpdate
9703
* @deprecated since version 2.5.0
9704
* @todo remove at version 3
9705
* @private
9706
*/
9707
core_plugins.notify(me, 'afterScaleUpdate');
9708
core_plugins.notify(me, 'afterLayout');
9709
},
9710
9711
/**
9712
* Updates all datasets unless a plugin returns `false` to the `beforeDatasetsUpdate`
9713
* hook, in which case, plugins will not be called on `afterDatasetsUpdate`.
9714
* @private
9715
*/
9716
updateDatasets: function() {
9717
var me = this;
9718
9719
if (core_plugins.notify(me, 'beforeDatasetsUpdate') === false) {
9720
return;
9721
}
9722
9723
for (var i = 0, ilen = me.data.datasets.length; i < ilen; ++i) {
9724
me.updateDataset(i);
9725
}
9726
9727
core_plugins.notify(me, 'afterDatasetsUpdate');
9728
},
9729
9730
/**
9731
* Updates dataset at index unless a plugin returns `false` to the `beforeDatasetUpdate`
9732
* hook, in which case, plugins will not be called on `afterDatasetUpdate`.
9733
* @private
9734
*/
9735
updateDataset: function(index) {
9736
var me = this;
9737
var meta = me.getDatasetMeta(index);
9738
var args = {
9739
meta: meta,
9740
index: index
9741
};
9742
9743
if (core_plugins.notify(me, 'beforeDatasetUpdate', [args]) === false) {
9744
return;
9745
}
9746
9747
meta.controller._update();
9748
9749
core_plugins.notify(me, 'afterDatasetUpdate', [args]);
9750
},
9751
9752
render: function(config) {
9753
var me = this;
9754
9755
if (!config || typeof config !== 'object') {
9756
// backwards compatibility
9757
config = {
9758
duration: config,
9759
lazy: arguments[1]
9760
};
9761
}
9762
9763
var animationOptions = me.options.animation;
9764
var duration = valueOrDefault$9(config.duration, animationOptions && animationOptions.duration);
9765
var lazy = config.lazy;
9766
9767
if (core_plugins.notify(me, 'beforeRender') === false) {
9768
return;
9769
}
9770
9771
var onComplete = function(animation) {
9772
core_plugins.notify(me, 'afterRender');
9773
helpers$1.callback(animationOptions && animationOptions.onComplete, [animation], me);
9774
};
9775
9776
if (animationOptions && duration) {
9777
var animation = new core_animation({
9778
numSteps: duration / 16.66, // 60 fps
9779
easing: config.easing || animationOptions.easing,
9780
9781
render: function(chart, animationObject) {
9782
var easingFunction = helpers$1.easing.effects[animationObject.easing];
9783
var currentStep = animationObject.currentStep;
9784
var stepDecimal = currentStep / animationObject.numSteps;
9785
9786
chart.draw(easingFunction(stepDecimal), stepDecimal, currentStep);
9787
},
9788
9789
onAnimationProgress: animationOptions.onProgress,
9790
onAnimationComplete: onComplete
9791
});
9792
9793
core_animations.addAnimation(me, animation, duration, lazy);
9794
} else {
9795
me.draw();
9796
9797
// See https://github.com/chartjs/Chart.js/issues/3781
9798
onComplete(new core_animation({numSteps: 0, chart: me}));
9799
}
9800
9801
return me;
9802
},
9803
9804
draw: function(easingValue) {
9805
var me = this;
9806
var i, layers;
9807
9808
me.clear();
9809
9810
if (helpers$1.isNullOrUndef(easingValue)) {
9811
easingValue = 1;
9812
}
9813
9814
me.transition(easingValue);
9815
9816
if (me.width <= 0 || me.height <= 0) {
9817
return;
9818
}
9819
9820
if (core_plugins.notify(me, 'beforeDraw', [easingValue]) === false) {
9821
return;
9822
}
9823
9824
// Because of plugin hooks (before/afterDatasetsDraw), datasets can't
9825
// currently be part of layers. Instead, we draw
9826
// layers <= 0 before(default, backward compat), and the rest after
9827
layers = me._layers;
9828
for (i = 0; i < layers.length && layers[i].z <= 0; ++i) {
9829
layers[i].draw(me.chartArea);
9830
}
9831
9832
me.drawDatasets(easingValue);
9833
9834
// Rest of layers
9835
for (; i < layers.length; ++i) {
9836
layers[i].draw(me.chartArea);
9837
}
9838
9839
me._drawTooltip(easingValue);
9840
9841
core_plugins.notify(me, 'afterDraw', [easingValue]);
9842
},
9843
9844
/**
9845
* @private
9846
*/
9847
transition: function(easingValue) {
9848
var me = this;
9849
9850
for (var i = 0, ilen = (me.data.datasets || []).length; i < ilen; ++i) {
9851
if (me.isDatasetVisible(i)) {
9852
me.getDatasetMeta(i).controller.transition(easingValue);
9853
}
9854
}
9855
9856
me.tooltip.transition(easingValue);
9857
},
9858
9859
/**
9860
* @private
9861
*/
9862
_getSortedDatasetMetas: function(filterVisible) {
9863
var me = this;
9864
var datasets = me.data.datasets || [];
9865
var result = [];
9866
var i, ilen;
9867
9868
for (i = 0, ilen = datasets.length; i < ilen; ++i) {
9869
if (!filterVisible || me.isDatasetVisible(i)) {
9870
result.push(me.getDatasetMeta(i));
9871
}
9872
}
9873
9874
result.sort(compare2Level('order', 'index'));
9875
9876
return result;
9877
},
9878
9879
/**
9880
* @private
9881
*/
9882
_getSortedVisibleDatasetMetas: function() {
9883
return this._getSortedDatasetMetas(true);
9884
},
9885
9886
/**
9887
* Draws all datasets unless a plugin returns `false` to the `beforeDatasetsDraw`
9888
* hook, in which case, plugins will not be called on `afterDatasetsDraw`.
9889
* @private
9890
*/
9891
drawDatasets: function(easingValue) {
9892
var me = this;
9893
var metasets, i;
9894
9895
if (core_plugins.notify(me, 'beforeDatasetsDraw', [easingValue]) === false) {
9896
return;
9897
}
9898
9899
metasets = me._getSortedVisibleDatasetMetas();
9900
for (i = metasets.length - 1; i >= 0; --i) {
9901
me.drawDataset(metasets[i], easingValue);
9902
}
9903
9904
core_plugins.notify(me, 'afterDatasetsDraw', [easingValue]);
9905
},
9906
9907
/**
9908
* Draws dataset at index unless a plugin returns `false` to the `beforeDatasetDraw`
9909
* hook, in which case, plugins will not be called on `afterDatasetDraw`.
9910
* @private
9911
*/
9912
drawDataset: function(meta, easingValue) {
9913
var me = this;
9914
var args = {
9915
meta: meta,
9916
index: meta.index,
9917
easingValue: easingValue
9918
};
9919
9920
if (core_plugins.notify(me, 'beforeDatasetDraw', [args]) === false) {
9921
return;
9922
}
9923
9924
meta.controller.draw(easingValue);
9925
9926
core_plugins.notify(me, 'afterDatasetDraw', [args]);
9927
},
9928
9929
/**
9930
* Draws tooltip unless a plugin returns `false` to the `beforeTooltipDraw`
9931
* hook, in which case, plugins will not be called on `afterTooltipDraw`.
9932
* @private
9933
*/
9934
_drawTooltip: function(easingValue) {
9935
var me = this;
9936
var tooltip = me.tooltip;
9937
var args = {
9938
tooltip: tooltip,
9939
easingValue: easingValue
9940
};
9941
9942
if (core_plugins.notify(me, 'beforeTooltipDraw', [args]) === false) {
9943
return;
9944
}
9945
9946
tooltip.draw();
9947
9948
core_plugins.notify(me, 'afterTooltipDraw', [args]);
9949
},
9950
9951
/**
9952
* Get the single element that was clicked on
9953
* @return An object containing the dataset index and element index of the matching element. Also contains the rectangle that was draw
9954
*/
9955
getElementAtEvent: function(e) {
9956
return core_interaction.modes.single(this, e);
9957
},
9958
9959
getElementsAtEvent: function(e) {
9960
return core_interaction.modes.label(this, e, {intersect: true});
9961
},
9962
9963
getElementsAtXAxis: function(e) {
9964
return core_interaction.modes['x-axis'](this, e, {intersect: true});
9965
},
9966
9967
getElementsAtEventForMode: function(e, mode, options) {
9968
var method = core_interaction.modes[mode];
9969
if (typeof method === 'function') {
9970
return method(this, e, options);
9971
}
9972
9973
return [];
9974
},
9975
9976
getDatasetAtEvent: function(e) {
9977
return core_interaction.modes.dataset(this, e, {intersect: true});
9978
},
9979
9980
getDatasetMeta: function(datasetIndex) {
9981
var me = this;
9982
var dataset = me.data.datasets[datasetIndex];
9983
if (!dataset._meta) {
9984
dataset._meta = {};
9985
}
9986
9987
var meta = dataset._meta[me.id];
9988
if (!meta) {
9989
meta = dataset._meta[me.id] = {
9990
type: null,
9991
data: [],
9992
dataset: null,
9993
controller: null,
9994
hidden: null, // See isDatasetVisible() comment
9995
xAxisID: null,
9996
yAxisID: null,
9997
order: dataset.order || 0,
9998
index: datasetIndex
9999
};
10000
}
10001
10002
return meta;
10003
},
10004
10005
getVisibleDatasetCount: function() {
10006
var count = 0;
10007
for (var i = 0, ilen = this.data.datasets.length; i < ilen; ++i) {
10008
if (this.isDatasetVisible(i)) {
10009
count++;
10010
}
10011
}
10012
return count;
10013
},
10014
10015
isDatasetVisible: function(datasetIndex) {
10016
var meta = this.getDatasetMeta(datasetIndex);
10017
10018
// meta.hidden is a per chart dataset hidden flag override with 3 states: if true or false,
10019
// the dataset.hidden value is ignored, else if null, the dataset hidden state is returned.
10020
return typeof meta.hidden === 'boolean' ? !meta.hidden : !this.data.datasets[datasetIndex].hidden;
10021
},
10022
10023
generateLegend: function() {
10024
return this.options.legendCallback(this);
10025
},
10026
10027
/**
10028
* @private
10029
*/
10030
destroyDatasetMeta: function(datasetIndex) {
10031
var id = this.id;
10032
var dataset = this.data.datasets[datasetIndex];
10033
var meta = dataset._meta && dataset._meta[id];
10034
10035
if (meta) {
10036
meta.controller.destroy();
10037
delete dataset._meta[id];
10038
}
10039
},
10040
10041
destroy: function() {
10042
var me = this;
10043
var canvas = me.canvas;
10044
var i, ilen;
10045
10046
me.stop();
10047
10048
// dataset controllers need to cleanup associated data
10049
for (i = 0, ilen = me.data.datasets.length; i < ilen; ++i) {
10050
me.destroyDatasetMeta(i);
10051
}
10052
10053
if (canvas) {
10054
me.unbindEvents();
10055
helpers$1.canvas.clear(me);
10056
platform.releaseContext(me.ctx);
10057
me.canvas = null;
10058
me.ctx = null;
10059
}
10060
10061
core_plugins.notify(me, 'destroy');
10062
10063
delete Chart.instances[me.id];
10064
},
10065
10066
toBase64Image: function() {
10067
return this.canvas.toDataURL.apply(this.canvas, arguments);
10068
},
10069
10070
initToolTip: function() {
10071
var me = this;
10072
me.tooltip = new core_tooltip({
10073
_chart: me,
10074
_chartInstance: me, // deprecated, backward compatibility
10075
_data: me.data,
10076
_options: me.options.tooltips
10077
}, me);
10078
},
10079
10080
/**
10081
* @private
10082
*/
10083
bindEvents: function() {
10084
var me = this;
10085
var listeners = me._listeners = {};
10086
var listener = function() {
10087
me.eventHandler.apply(me, arguments);
10088
};
10089
10090
helpers$1.each(me.options.events, function(type) {
10091
platform.addEventListener(me, type, listener);
10092
listeners[type] = listener;
10093
});
10094
10095
// Elements used to detect size change should not be injected for non responsive charts.
10096
// See https://github.com/chartjs/Chart.js/issues/2210
10097
if (me.options.responsive) {
10098
listener = function() {
10099
me.resize();
10100
};
10101
10102
platform.addEventListener(me, 'resize', listener);
10103
listeners.resize = listener;
10104
}
10105
},
10106
10107
/**
10108
* @private
10109
*/
10110
unbindEvents: function() {
10111
var me = this;
10112
var listeners = me._listeners;
10113
if (!listeners) {
10114
return;
10115
}
10116
10117
delete me._listeners;
10118
helpers$1.each(listeners, function(listener, type) {
10119
platform.removeEventListener(me, type, listener);
10120
});
10121
},
10122
10123
updateHoverStyle: function(elements, mode, enabled) {
10124
var prefix = enabled ? 'set' : 'remove';
10125
var element, i, ilen;
10126
10127
for (i = 0, ilen = elements.length; i < ilen; ++i) {
10128
element = elements[i];
10129
if (element) {
10130
this.getDatasetMeta(element._datasetIndex).controller[prefix + 'HoverStyle'](element);
10131
}
10132
}
10133
10134
if (mode === 'dataset') {
10135
this.getDatasetMeta(elements[0]._datasetIndex).controller['_' + prefix + 'DatasetHoverStyle']();
10136
}
10137
},
10138
10139
/**
10140
* @private
10141
*/
10142
eventHandler: function(e) {
10143
var me = this;
10144
var tooltip = me.tooltip;
10145
10146
if (core_plugins.notify(me, 'beforeEvent', [e]) === false) {
10147
return;
10148
}
10149
10150
// Buffer any update calls so that renders do not occur
10151
me._bufferedRender = true;
10152
me._bufferedRequest = null;
10153
10154
var changed = me.handleEvent(e);
10155
// for smooth tooltip animations issue #4989
10156
// the tooltip should be the source of change
10157
// Animation check workaround:
10158
// tooltip._start will be null when tooltip isn't animating
10159
if (tooltip) {
10160
changed = tooltip._start
10161
? tooltip.handleEvent(e)
10162
: changed | tooltip.handleEvent(e);
10163
}
10164
10165
core_plugins.notify(me, 'afterEvent', [e]);
10166
10167
var bufferedRequest = me._bufferedRequest;
10168
if (bufferedRequest) {
10169
// If we have an update that was triggered, we need to do a normal render
10170
me.render(bufferedRequest);
10171
} else if (changed && !me.animating) {
10172
// If entering, leaving, or changing elements, animate the change via pivot
10173
me.stop();
10174
10175
// We only need to render at this point. Updating will cause scales to be
10176
// recomputed generating flicker & using more memory than necessary.
10177
me.render({
10178
duration: me.options.hover.animationDuration,
10179
lazy: true
10180
});
10181
}
10182
10183
me._bufferedRender = false;
10184
me._bufferedRequest = null;
10185
10186
return me;
10187
},
10188
10189
/**
10190
* Handle an event
10191
* @private
10192
* @param {IEvent} event the event to handle
10193
* @return {boolean} true if the chart needs to re-render
10194
*/
10195
handleEvent: function(e) {
10196
var me = this;
10197
var options = me.options || {};
10198
var hoverOptions = options.hover;
10199
var changed = false;
10200
10201
me.lastActive = me.lastActive || [];
10202
10203
// Find Active Elements for hover and tooltips
10204
if (e.type === 'mouseout') {
10205
me.active = [];
10206
} else {
10207
me.active = me.getElementsAtEventForMode(e, hoverOptions.mode, hoverOptions);
10208
}
10209
10210
// Invoke onHover hook
10211
// Need to call with native event here to not break backwards compatibility
10212
helpers$1.callback(options.onHover || options.hover.onHover, [e.native, me.active], me);
10213
10214
if (e.type === 'mouseup' || e.type === 'click') {
10215
if (options.onClick) {
10216
// Use e.native here for backwards compatibility
10217
options.onClick.call(me, e.native, me.active);
10218
}
10219
}
10220
10221
// Remove styling for last active (even if it may still be active)
10222
if (me.lastActive.length) {
10223
me.updateHoverStyle(me.lastActive, hoverOptions.mode, false);
10224
}
10225
10226
// Built in hover styling
10227
if (me.active.length && hoverOptions.mode) {
10228
me.updateHoverStyle(me.active, hoverOptions.mode, true);
10229
}
10230
10231
changed = !helpers$1.arrayEquals(me.active, me.lastActive);
10232
10233
// Remember Last Actives
10234
me.lastActive = me.active;
10235
10236
return changed;
10237
}
10238
});
10239
10240
/**
10241
* NOTE(SB) We actually don't use this container anymore but we need to keep it
10242
* for backward compatibility. Though, it can still be useful for plugins that
10243
* would need to work on multiple charts?!
10244
*/
10245
Chart.instances = {};
10246
10247
var core_controller = Chart;
10248
10249
// DEPRECATIONS
10250
10251
/**
10252
* Provided for backward compatibility, use Chart instead.
10253
* @class Chart.Controller
10254
* @deprecated since version 2.6
10255
* @todo remove at version 3
10256
* @private
10257
*/
10258
Chart.Controller = Chart;
10259
10260
/**
10261
* Provided for backward compatibility, not available anymore.
10262
* @namespace Chart
10263
* @deprecated since version 2.8
10264
* @todo remove at version 3
10265
* @private
10266
*/
10267
Chart.types = {};
10268
10269
/**
10270
* Provided for backward compatibility, not available anymore.
10271
* @namespace Chart.helpers.configMerge
10272
* @deprecated since version 2.8.0
10273
* @todo remove at version 3
10274
* @private
10275
*/
10276
helpers$1.configMerge = mergeConfig;
10277
10278
/**
10279
* Provided for backward compatibility, not available anymore.
10280
* @namespace Chart.helpers.scaleMerge
10281
* @deprecated since version 2.8.0
10282
* @todo remove at version 3
10283
* @private
10284
*/
10285
helpers$1.scaleMerge = mergeScaleConfig;
10286
10287
var core_helpers = function() {
10288
10289
// -- Basic js utility methods
10290
10291
helpers$1.where = function(collection, filterCallback) {
10292
if (helpers$1.isArray(collection) && Array.prototype.filter) {
10293
return collection.filter(filterCallback);
10294
}
10295
var filtered = [];
10296
10297
helpers$1.each(collection, function(item) {
10298
if (filterCallback(item)) {
10299
filtered.push(item);
10300
}
10301
});
10302
10303
return filtered;
10304
};
10305
helpers$1.findIndex = Array.prototype.findIndex ?
10306
function(array, callback, scope) {
10307
return array.findIndex(callback, scope);
10308
} :
10309
function(array, callback, scope) {
10310
scope = scope === undefined ? array : scope;
10311
for (var i = 0, ilen = array.length; i < ilen; ++i) {
10312
if (callback.call(scope, array[i], i, array)) {
10313
return i;
10314
}
10315
}
10316
return -1;
10317
};
10318
helpers$1.findNextWhere = function(arrayToSearch, filterCallback, startIndex) {
10319
// Default to start of the array
10320
if (helpers$1.isNullOrUndef(startIndex)) {
10321
startIndex = -1;
10322
}
10323
for (var i = startIndex + 1; i < arrayToSearch.length; i++) {
10324
var currentItem = arrayToSearch[i];
10325
if (filterCallback(currentItem)) {
10326
return currentItem;
10327
}
10328
}
10329
};
10330
helpers$1.findPreviousWhere = function(arrayToSearch, filterCallback, startIndex) {
10331
// Default to end of the array
10332
if (helpers$1.isNullOrUndef(startIndex)) {
10333
startIndex = arrayToSearch.length;
10334
}
10335
for (var i = startIndex - 1; i >= 0; i--) {
10336
var currentItem = arrayToSearch[i];
10337
if (filterCallback(currentItem)) {
10338
return currentItem;
10339
}
10340
}
10341
};
10342
10343
// -- Math methods
10344
helpers$1.isNumber = function(n) {
10345
return !isNaN(parseFloat(n)) && isFinite(n);
10346
};
10347
helpers$1.almostEquals = function(x, y, epsilon) {
10348
return Math.abs(x - y) < epsilon;
10349
};
10350
helpers$1.almostWhole = function(x, epsilon) {
10351
var rounded = Math.round(x);
10352
return ((rounded - epsilon) <= x) && ((rounded + epsilon) >= x);
10353
};
10354
helpers$1.max = function(array) {
10355
return array.reduce(function(max, value) {
10356
if (!isNaN(value)) {
10357
return Math.max(max, value);
10358
}
10359
return max;
10360
}, Number.NEGATIVE_INFINITY);
10361
};
10362
helpers$1.min = function(array) {
10363
return array.reduce(function(min, value) {
10364
if (!isNaN(value)) {
10365
return Math.min(min, value);
10366
}
10367
return min;
10368
}, Number.POSITIVE_INFINITY);
10369
};
10370
helpers$1.sign = Math.sign ?
10371
function(x) {
10372
return Math.sign(x);
10373
} :
10374
function(x) {
10375
x = +x; // convert to a number
10376
if (x === 0 || isNaN(x)) {
10377
return x;
10378
}
10379
return x > 0 ? 1 : -1;
10380
};
10381
helpers$1.toRadians = function(degrees) {
10382
return degrees * (Math.PI / 180);
10383
};
10384
helpers$1.toDegrees = function(radians) {
10385
return radians * (180 / Math.PI);
10386
};
10387
10388
/**
10389
* Returns the number of decimal places
10390
* i.e. the number of digits after the decimal point, of the value of this Number.
10391
* @param {number} x - A number.
10392
* @returns {number} The number of decimal places.
10393
* @private
10394
*/
10395
helpers$1._decimalPlaces = function(x) {
10396
if (!helpers$1.isFinite(x)) {
10397
return;
10398
}
10399
var e = 1;
10400
var p = 0;
10401
while (Math.round(x * e) / e !== x) {
10402
e *= 10;
10403
p++;
10404
}
10405
return p;
10406
};
10407
10408
// Gets the angle from vertical upright to the point about a centre.
10409
helpers$1.getAngleFromPoint = function(centrePoint, anglePoint) {
10410
var distanceFromXCenter = anglePoint.x - centrePoint.x;
10411
var distanceFromYCenter = anglePoint.y - centrePoint.y;
10412
var radialDistanceFromCenter = Math.sqrt(distanceFromXCenter * distanceFromXCenter + distanceFromYCenter * distanceFromYCenter);
10413
10414
var angle = Math.atan2(distanceFromYCenter, distanceFromXCenter);
10415
10416
if (angle < (-0.5 * Math.PI)) {
10417
angle += 2.0 * Math.PI; // make sure the returned angle is in the range of (-PI/2, 3PI/2]
10418
}
10419
10420
return {
10421
angle: angle,
10422
distance: radialDistanceFromCenter
10423
};
10424
};
10425
helpers$1.distanceBetweenPoints = function(pt1, pt2) {
10426
return Math.sqrt(Math.pow(pt2.x - pt1.x, 2) + Math.pow(pt2.y - pt1.y, 2));
10427
};
10428
10429
/**
10430
* Provided for backward compatibility, not available anymore
10431
* @function Chart.helpers.aliasPixel
10432
* @deprecated since version 2.8.0
10433
* @todo remove at version 3
10434
*/
10435
helpers$1.aliasPixel = function(pixelWidth) {
10436
return (pixelWidth % 2 === 0) ? 0 : 0.5;
10437
};
10438
10439
/**
10440
* Returns the aligned pixel value to avoid anti-aliasing blur
10441
* @param {Chart} chart - The chart instance.
10442
* @param {number} pixel - A pixel value.
10443
* @param {number} width - The width of the element.
10444
* @returns {number} The aligned pixel value.
10445
* @private
10446
*/
10447
helpers$1._alignPixel = function(chart, pixel, width) {
10448
var devicePixelRatio = chart.currentDevicePixelRatio;
10449
var halfWidth = width / 2;
10450
return Math.round((pixel - halfWidth) * devicePixelRatio) / devicePixelRatio + halfWidth;
10451
};
10452
10453
helpers$1.splineCurve = function(firstPoint, middlePoint, afterPoint, t) {
10454
// Props to Rob Spencer at scaled innovation for his post on splining between points
10455
// http://scaledinnovation.com/analytics/splines/aboutSplines.html
10456
10457
// This function must also respect "skipped" points
10458
10459
var previous = firstPoint.skip ? middlePoint : firstPoint;
10460
var current = middlePoint;
10461
var next = afterPoint.skip ? middlePoint : afterPoint;
10462
10463
var d01 = Math.sqrt(Math.pow(current.x - previous.x, 2) + Math.pow(current.y - previous.y, 2));
10464
var d12 = Math.sqrt(Math.pow(next.x - current.x, 2) + Math.pow(next.y - current.y, 2));
10465
10466
var s01 = d01 / (d01 + d12);
10467
var s12 = d12 / (d01 + d12);
10468
10469
// If all points are the same, s01 & s02 will be inf
10470
s01 = isNaN(s01) ? 0 : s01;
10471
s12 = isNaN(s12) ? 0 : s12;
10472
10473
var fa = t * s01; // scaling factor for triangle Ta
10474
var fb = t * s12;
10475
10476
return {
10477
previous: {
10478
x: current.x - fa * (next.x - previous.x),
10479
y: current.y - fa * (next.y - previous.y)
10480
},
10481
next: {
10482
x: current.x + fb * (next.x - previous.x),
10483
y: current.y + fb * (next.y - previous.y)
10484
}
10485
};
10486
};
10487
helpers$1.EPSILON = Number.EPSILON || 1e-14;
10488
helpers$1.splineCurveMonotone = function(points) {
10489
// This function calculates Bézier control points in a similar way than |splineCurve|,
10490
// but preserves monotonicity of the provided data and ensures no local extremums are added
10491
// between the dataset discrete points due to the interpolation.
10492
// See : https://en.wikipedia.org/wiki/Monotone_cubic_interpolation
10493
10494
var pointsWithTangents = (points || []).map(function(point) {
10495
return {
10496
model: point._model,
10497
deltaK: 0,
10498
mK: 0
10499
};
10500
});
10501
10502
// Calculate slopes (deltaK) and initialize tangents (mK)
10503
var pointsLen = pointsWithTangents.length;
10504
var i, pointBefore, pointCurrent, pointAfter;
10505
for (i = 0; i < pointsLen; ++i) {
10506
pointCurrent = pointsWithTangents[i];
10507
if (pointCurrent.model.skip) {
10508
continue;
10509
}
10510
10511
pointBefore = i > 0 ? pointsWithTangents[i - 1] : null;
10512
pointAfter = i < pointsLen - 1 ? pointsWithTangents[i + 1] : null;
10513
if (pointAfter && !pointAfter.model.skip) {
10514
var slopeDeltaX = (pointAfter.model.x - pointCurrent.model.x);
10515
10516
// In the case of two points that appear at the same x pixel, slopeDeltaX is 0
10517
pointCurrent.deltaK = slopeDeltaX !== 0 ? (pointAfter.model.y - pointCurrent.model.y) / slopeDeltaX : 0;
10518
}
10519
10520
if (!pointBefore || pointBefore.model.skip) {
10521
pointCurrent.mK = pointCurrent.deltaK;
10522
} else if (!pointAfter || pointAfter.model.skip) {
10523
pointCurrent.mK = pointBefore.deltaK;
10524
} else if (this.sign(pointBefore.deltaK) !== this.sign(pointCurrent.deltaK)) {
10525
pointCurrent.mK = 0;
10526
} else {
10527
pointCurrent.mK = (pointBefore.deltaK + pointCurrent.deltaK) / 2;
10528
}
10529
}
10530
10531
// Adjust tangents to ensure monotonic properties
10532
var alphaK, betaK, tauK, squaredMagnitude;
10533
for (i = 0; i < pointsLen - 1; ++i) {
10534
pointCurrent = pointsWithTangents[i];
10535
pointAfter = pointsWithTangents[i + 1];
10536
if (pointCurrent.model.skip || pointAfter.model.skip) {
10537
continue;
10538
}
10539
10540
if (helpers$1.almostEquals(pointCurrent.deltaK, 0, this.EPSILON)) {
10541
pointCurrent.mK = pointAfter.mK = 0;
10542
continue;
10543
}
10544
10545
alphaK = pointCurrent.mK / pointCurrent.deltaK;
10546
betaK = pointAfter.mK / pointCurrent.deltaK;
10547
squaredMagnitude = Math.pow(alphaK, 2) + Math.pow(betaK, 2);
10548
if (squaredMagnitude <= 9) {
10549
continue;
10550
}
10551
10552
tauK = 3 / Math.sqrt(squaredMagnitude);
10553
pointCurrent.mK = alphaK * tauK * pointCurrent.deltaK;
10554
pointAfter.mK = betaK * tauK * pointCurrent.deltaK;
10555
}
10556
10557
// Compute control points
10558
var deltaX;
10559
for (i = 0; i < pointsLen; ++i) {
10560
pointCurrent = pointsWithTangents[i];
10561
if (pointCurrent.model.skip) {
10562
continue;
10563
}
10564
10565
pointBefore = i > 0 ? pointsWithTangents[i - 1] : null;
10566
pointAfter = i < pointsLen - 1 ? pointsWithTangents[i + 1] : null;
10567
if (pointBefore && !pointBefore.model.skip) {
10568
deltaX = (pointCurrent.model.x - pointBefore.model.x) / 3;
10569
pointCurrent.model.controlPointPreviousX = pointCurrent.model.x - deltaX;
10570
pointCurrent.model.controlPointPreviousY = pointCurrent.model.y - deltaX * pointCurrent.mK;
10571
}
10572
if (pointAfter && !pointAfter.model.skip) {
10573
deltaX = (pointAfter.model.x - pointCurrent.model.x) / 3;
10574
pointCurrent.model.controlPointNextX = pointCurrent.model.x + deltaX;
10575
pointCurrent.model.controlPointNextY = pointCurrent.model.y + deltaX * pointCurrent.mK;
10576
}
10577
}
10578
};
10579
helpers$1.nextItem = function(collection, index, loop) {
10580
if (loop) {
10581
return index >= collection.length - 1 ? collection[0] : collection[index + 1];
10582
}
10583
return index >= collection.length - 1 ? collection[collection.length - 1] : collection[index + 1];
10584
};
10585
helpers$1.previousItem = function(collection, index, loop) {
10586
if (loop) {
10587
return index <= 0 ? collection[collection.length - 1] : collection[index - 1];
10588
}
10589
return index <= 0 ? collection[0] : collection[index - 1];
10590
};
10591
// Implementation of the nice number algorithm used in determining where axis labels will go
10592
helpers$1.niceNum = function(range, round) {
10593
var exponent = Math.floor(helpers$1.log10(range));
10594
var fraction = range / Math.pow(10, exponent);
10595
var niceFraction;
10596
10597
if (round) {
10598
if (fraction < 1.5) {
10599
niceFraction = 1;
10600
} else if (fraction < 3) {
10601
niceFraction = 2;
10602
} else if (fraction < 7) {
10603
niceFraction = 5;
10604
} else {
10605
niceFraction = 10;
10606
}
10607
} else if (fraction <= 1.0) {
10608
niceFraction = 1;
10609
} else if (fraction <= 2) {
10610
niceFraction = 2;
10611
} else if (fraction <= 5) {
10612
niceFraction = 5;
10613
} else {
10614
niceFraction = 10;
10615
}
10616
10617
return niceFraction * Math.pow(10, exponent);
10618
};
10619
// Request animation polyfill - https://www.paulirish.com/2011/requestanimationframe-for-smart-animating/
10620
helpers$1.requestAnimFrame = (function() {
10621
if (typeof window === 'undefined') {
10622
return function(callback) {
10623
callback();
10624
};
10625
}
10626
return window.requestAnimationFrame ||
10627
window.webkitRequestAnimationFrame ||
10628
window.mozRequestAnimationFrame ||
10629
window.oRequestAnimationFrame ||
10630
window.msRequestAnimationFrame ||
10631
function(callback) {
10632
return window.setTimeout(callback, 1000 / 60);
10633
};
10634
}());
10635
// -- DOM methods
10636
helpers$1.getRelativePosition = function(evt, chart) {
10637
var mouseX, mouseY;
10638
var e = evt.originalEvent || evt;
10639
var canvas = evt.target || evt.srcElement;
10640
var boundingRect = canvas.getBoundingClientRect();
10641
10642
var touches = e.touches;
10643
if (touches && touches.length > 0) {
10644
mouseX = touches[0].clientX;
10645
mouseY = touches[0].clientY;
10646
10647
} else {
10648
mouseX = e.clientX;
10649
mouseY = e.clientY;
10650
}
10651
10652
// Scale mouse coordinates into canvas coordinates
10653
// by following the pattern laid out by 'jerryj' in the comments of
10654
// https://www.html5canvastutorials.com/advanced/html5-canvas-mouse-coordinates/
10655
var paddingLeft = parseFloat(helpers$1.getStyle(canvas, 'padding-left'));
10656
var paddingTop = parseFloat(helpers$1.getStyle(canvas, 'padding-top'));
10657
var paddingRight = parseFloat(helpers$1.getStyle(canvas, 'padding-right'));
10658
var paddingBottom = parseFloat(helpers$1.getStyle(canvas, 'padding-bottom'));
10659
var width = boundingRect.right - boundingRect.left - paddingLeft - paddingRight;
10660
var height = boundingRect.bottom - boundingRect.top - paddingTop - paddingBottom;
10661
10662
// We divide by the current device pixel ratio, because the canvas is scaled up by that amount in each direction. However
10663
// the backend model is in unscaled coordinates. Since we are going to deal with our model coordinates, we go back here
10664
mouseX = Math.round((mouseX - boundingRect.left - paddingLeft) / (width) * canvas.width / chart.currentDevicePixelRatio);
10665
mouseY = Math.round((mouseY - boundingRect.top - paddingTop) / (height) * canvas.height / chart.currentDevicePixelRatio);
10666
10667
return {
10668
x: mouseX,
10669
y: mouseY
10670
};
10671
10672
};
10673
10674
// Private helper function to convert max-width/max-height values that may be percentages into a number
10675
function parseMaxStyle(styleValue, node, parentProperty) {
10676
var valueInPixels;
10677
if (typeof styleValue === 'string') {
10678
valueInPixels = parseInt(styleValue, 10);
10679
10680
if (styleValue.indexOf('%') !== -1) {
10681
// percentage * size in dimension
10682
valueInPixels = valueInPixels / 100 * node.parentNode[parentProperty];
10683
}
10684
} else {
10685
valueInPixels = styleValue;
10686
}
10687
10688
return valueInPixels;
10689
}
10690
10691
/**
10692
* Returns if the given value contains an effective constraint.
10693
* @private
10694
*/
10695
function isConstrainedValue(value) {
10696
return value !== undefined && value !== null && value !== 'none';
10697
}
10698
10699
/**
10700
* Returns the max width or height of the given DOM node in a cross-browser compatible fashion
10701
* @param {HTMLElement} domNode - the node to check the constraint on
10702
* @param {string} maxStyle - the style that defines the maximum for the direction we are using ('max-width' / 'max-height')
10703
* @param {string} percentageProperty - property of parent to use when calculating width as a percentage
10704
* @see {@link https://www.nathanaeljones.com/blog/2013/reading-max-width-cross-browser}
10705
*/
10706
function getConstraintDimension(domNode, maxStyle, percentageProperty) {
10707
var view = document.defaultView;
10708
var parentNode = helpers$1._getParentNode(domNode);
10709
var constrainedNode = view.getComputedStyle(domNode)[maxStyle];
10710
var constrainedContainer = view.getComputedStyle(parentNode)[maxStyle];
10711
var hasCNode = isConstrainedValue(constrainedNode);
10712
var hasCContainer = isConstrainedValue(constrainedContainer);
10713
var infinity = Number.POSITIVE_INFINITY;
10714
10715
if (hasCNode || hasCContainer) {
10716
return Math.min(
10717
hasCNode ? parseMaxStyle(constrainedNode, domNode, percentageProperty) : infinity,
10718
hasCContainer ? parseMaxStyle(constrainedContainer, parentNode, percentageProperty) : infinity);
10719
}
10720
10721
return 'none';
10722
}
10723
// returns Number or undefined if no constraint
10724
helpers$1.getConstraintWidth = function(domNode) {
10725
return getConstraintDimension(domNode, 'max-width', 'clientWidth');
10726
};
10727
// returns Number or undefined if no constraint
10728
helpers$1.getConstraintHeight = function(domNode) {
10729
return getConstraintDimension(domNode, 'max-height', 'clientHeight');
10730
};
10731
/**
10732
* @private
10733
*/
10734
helpers$1._calculatePadding = function(container, padding, parentDimension) {
10735
padding = helpers$1.getStyle(container, padding);
10736
10737
return padding.indexOf('%') > -1 ? parentDimension * parseInt(padding, 10) / 100 : parseInt(padding, 10);
10738
};
10739
/**
10740
* @private
10741
*/
10742
helpers$1._getParentNode = function(domNode) {
10743
var parent = domNode.parentNode;
10744
if (parent && parent.toString() === '[object ShadowRoot]') {
10745
parent = parent.host;
10746
}
10747
return parent;
10748
};
10749
helpers$1.getMaximumWidth = function(domNode) {
10750
var container = helpers$1._getParentNode(domNode);
10751
if (!container) {
10752
return domNode.clientWidth;
10753
}
10754
10755
var clientWidth = container.clientWidth;
10756
var paddingLeft = helpers$1._calculatePadding(container, 'padding-left', clientWidth);
10757
var paddingRight = helpers$1._calculatePadding(container, 'padding-right', clientWidth);
10758
10759
var w = clientWidth - paddingLeft - paddingRight;
10760
var cw = helpers$1.getConstraintWidth(domNode);
10761
return isNaN(cw) ? w : Math.min(w, cw);
10762
};
10763
helpers$1.getMaximumHeight = function(domNode) {
10764
var container = helpers$1._getParentNode(domNode);
10765
if (!container) {
10766
return domNode.clientHeight;
10767
}
10768
10769
var clientHeight = container.clientHeight;
10770
var paddingTop = helpers$1._calculatePadding(container, 'padding-top', clientHeight);
10771
var paddingBottom = helpers$1._calculatePadding(container, 'padding-bottom', clientHeight);
10772
10773
var h = clientHeight - paddingTop - paddingBottom;
10774
var ch = helpers$1.getConstraintHeight(domNode);
10775
return isNaN(ch) ? h : Math.min(h, ch);
10776
};
10777
helpers$1.getStyle = function(el, property) {
10778
return el.currentStyle ?
10779
el.currentStyle[property] :
10780
document.defaultView.getComputedStyle(el, null).getPropertyValue(property);
10781
};
10782
helpers$1.retinaScale = function(chart, forceRatio) {
10783
var pixelRatio = chart.currentDevicePixelRatio = forceRatio || (typeof window !== 'undefined' && window.devicePixelRatio) || 1;
10784
if (pixelRatio === 1) {
10785
return;
10786
}
10787
10788
var canvas = chart.canvas;
10789
var height = chart.height;
10790
var width = chart.width;
10791
10792
canvas.height = height * pixelRatio;
10793
canvas.width = width * pixelRatio;
10794
chart.ctx.scale(pixelRatio, pixelRatio);
10795
10796
// If no style has been set on the canvas, the render size is used as display size,
10797
// making the chart visually bigger, so let's enforce it to the "correct" values.
10798
// See https://github.com/chartjs/Chart.js/issues/3575
10799
if (!canvas.style.height && !canvas.style.width) {
10800
canvas.style.height = height + 'px';
10801
canvas.style.width = width + 'px';
10802
}
10803
};
10804
// -- Canvas methods
10805
helpers$1.fontString = function(pixelSize, fontStyle, fontFamily) {
10806
return fontStyle + ' ' + pixelSize + 'px ' + fontFamily;
10807
};
10808
helpers$1.longestText = function(ctx, font, arrayOfThings, cache) {
10809
cache = cache || {};
10810
var data = cache.data = cache.data || {};
10811
var gc = cache.garbageCollect = cache.garbageCollect || [];
10812
10813
if (cache.font !== font) {
10814
data = cache.data = {};
10815
gc = cache.garbageCollect = [];
10816
cache.font = font;
10817
}
10818
10819
ctx.font = font;
10820
var longest = 0;
10821
var ilen = arrayOfThings.length;
10822
var i, j, jlen, thing, nestedThing;
10823
for (i = 0; i < ilen; i++) {
10824
thing = arrayOfThings[i];
10825
10826
// Undefined strings and arrays should not be measured
10827
if (thing !== undefined && thing !== null && helpers$1.isArray(thing) !== true) {
10828
longest = helpers$1.measureText(ctx, data, gc, longest, thing);
10829
} else if (helpers$1.isArray(thing)) {
10830
// if it is an array lets measure each element
10831
// to do maybe simplify this function a bit so we can do this more recursively?
10832
for (j = 0, jlen = thing.length; j < jlen; j++) {
10833
nestedThing = thing[j];
10834
// Undefined strings and arrays should not be measured
10835
if (nestedThing !== undefined && nestedThing !== null && !helpers$1.isArray(nestedThing)) {
10836
longest = helpers$1.measureText(ctx, data, gc, longest, nestedThing);
10837
}
10838
}
10839
}
10840
}
10841
10842
var gcLen = gc.length / 2;
10843
if (gcLen > arrayOfThings.length) {
10844
for (i = 0; i < gcLen; i++) {
10845
delete data[gc[i]];
10846
}
10847
gc.splice(0, gcLen);
10848
}
10849
return longest;
10850
};
10851
helpers$1.measureText = function(ctx, data, gc, longest, string) {
10852
var textWidth = data[string];
10853
if (!textWidth) {
10854
textWidth = data[string] = ctx.measureText(string).width;
10855
gc.push(string);
10856
}
10857
if (textWidth > longest) {
10858
longest = textWidth;
10859
}
10860
return longest;
10861
};
10862
10863
/**
10864
* @deprecated
10865
*/
10866
helpers$1.numberOfLabelLines = function(arrayOfThings) {
10867
var numberOfLines = 1;
10868
helpers$1.each(arrayOfThings, function(thing) {
10869
if (helpers$1.isArray(thing)) {
10870
if (thing.length > numberOfLines) {
10871
numberOfLines = thing.length;
10872
}
10873
}
10874
});
10875
return numberOfLines;
10876
};
10877
10878
helpers$1.color = !chartjsColor ?
10879
function(value) {
10880
console.error('Color.js not found!');
10881
return value;
10882
} :
10883
function(value) {
10884
/* global CanvasGradient */
10885
if (value instanceof CanvasGradient) {
10886
value = core_defaults.global.defaultColor;
10887
}
10888
10889
return chartjsColor(value);
10890
};
10891
10892
helpers$1.getHoverColor = function(colorValue) {
10893
/* global CanvasPattern */
10894
return (colorValue instanceof CanvasPattern || colorValue instanceof CanvasGradient) ?
10895
colorValue :
10896
helpers$1.color(colorValue).saturate(0.5).darken(0.1).rgbString();
10897
};
10898
};
10899
10900
function abstract() {
10901
throw new Error(
10902
'This method is not implemented: either no adapter can ' +
10903
'be found or an incomplete integration was provided.'
10904
);
10905
}
10906
10907
/**
10908
* Date adapter (current used by the time scale)
10909
* @namespace Chart._adapters._date
10910
* @memberof Chart._adapters
10911
* @private
10912
*/
10913
10914
/**
10915
* Currently supported unit string values.
10916
* @typedef {('millisecond'|'second'|'minute'|'hour'|'day'|'week'|'month'|'quarter'|'year')}
10917
* @memberof Chart._adapters._date
10918
* @name Unit
10919
*/
10920
10921
/**
10922
* @class
10923
*/
10924
function DateAdapter(options) {
10925
this.options = options || {};
10926
}
10927
10928
helpers$1.extend(DateAdapter.prototype, /** @lends DateAdapter */ {
10929
/**
10930
* Returns a map of time formats for the supported formatting units defined
10931
* in Unit as well as 'datetime' representing a detailed date/time string.
10932
* @returns {{string: string}}
10933
*/
10934
formats: abstract,
10935
10936
/**
10937
* Parses the given `value` and return the associated timestamp.
10938
* @param {any} value - the value to parse (usually comes from the data)
10939
* @param {string} [format] - the expected data format
10940
* @returns {(number|null)}
10941
* @function
10942
*/
10943
parse: abstract,
10944
10945
/**
10946
* Returns the formatted date in the specified `format` for a given `timestamp`.
10947
* @param {number} timestamp - the timestamp to format
10948
* @param {string} format - the date/time token
10949
* @return {string}
10950
* @function
10951
*/
10952
format: abstract,
10953
10954
/**
10955
* Adds the specified `amount` of `unit` to the given `timestamp`.
10956
* @param {number} timestamp - the input timestamp
10957
* @param {number} amount - the amount to add
10958
* @param {Unit} unit - the unit as string
10959
* @return {number}
10960
* @function
10961
*/
10962
add: abstract,
10963
10964
/**
10965
* Returns the number of `unit` between the given timestamps.
10966
* @param {number} max - the input timestamp (reference)
10967
* @param {number} min - the timestamp to substract
10968
* @param {Unit} unit - the unit as string
10969
* @return {number}
10970
* @function
10971
*/
10972
diff: abstract,
10973
10974
/**
10975
* Returns start of `unit` for the given `timestamp`.
10976
* @param {number} timestamp - the input timestamp
10977
* @param {Unit} unit - the unit as string
10978
* @param {number} [weekday] - the ISO day of the week with 1 being Monday
10979
* and 7 being Sunday (only needed if param *unit* is `isoWeek`).
10980
* @function
10981
*/
10982
startOf: abstract,
10983
10984
/**
10985
* Returns end of `unit` for the given `timestamp`.
10986
* @param {number} timestamp - the input timestamp
10987
* @param {Unit} unit - the unit as string
10988
* @function
10989
*/
10990
endOf: abstract,
10991
10992
// DEPRECATIONS
10993
10994
/**
10995
* Provided for backward compatibility for scale.getValueForPixel(),
10996
* this method should be overridden only by the moment adapter.
10997
* @deprecated since version 2.8.0
10998
* @todo remove at version 3
10999
* @private
11000
*/
11001
_create: function(value) {
11002
return value;
11003
}
11004
});
11005
11006
DateAdapter.override = function(members) {
11007
helpers$1.extend(DateAdapter.prototype, members);
11008
};
11009
11010
var _date = DateAdapter;
11011
11012
var core_adapters = {
11013
_date: _date
11014
};
11015
11016
/**
11017
* Namespace to hold static tick generation functions
11018
* @namespace Chart.Ticks
11019
*/
11020
var core_ticks = {
11021
/**
11022
* Namespace to hold formatters for different types of ticks
11023
* @namespace Chart.Ticks.formatters
11024
*/
11025
formatters: {
11026
/**
11027
* Formatter for value labels
11028
* @method Chart.Ticks.formatters.values
11029
* @param value the value to display
11030
* @return {string|string[]} the label to display
11031
*/
11032
values: function(value) {
11033
return helpers$1.isArray(value) ? value : '' + value;
11034
},
11035
11036
/**
11037
* Formatter for linear numeric ticks
11038
* @method Chart.Ticks.formatters.linear
11039
* @param tickValue {number} the value to be formatted
11040
* @param index {number} the position of the tickValue parameter in the ticks array
11041
* @param ticks {number[]} the list of ticks being converted
11042
* @return {string} string representation of the tickValue parameter
11043
*/
11044
linear: function(tickValue, index, ticks) {
11045
// If we have lots of ticks, don't use the ones
11046
var delta = ticks.length > 3 ? ticks[2] - ticks[1] : ticks[1] - ticks[0];
11047
11048
// If we have a number like 2.5 as the delta, figure out how many decimal places we need
11049
if (Math.abs(delta) > 1) {
11050
if (tickValue !== Math.floor(tickValue)) {
11051
// not an integer
11052
delta = tickValue - Math.floor(tickValue);
11053
}
11054
}
11055
11056
var logDelta = helpers$1.log10(Math.abs(delta));
11057
var tickString = '';
11058
11059
if (tickValue !== 0) {
11060
var maxTick = Math.max(Math.abs(ticks[0]), Math.abs(ticks[ticks.length - 1]));
11061
if (maxTick < 1e-4) { // all ticks are small numbers; use scientific notation
11062
var logTick = helpers$1.log10(Math.abs(tickValue));
11063
var numExponential = Math.floor(logTick) - Math.floor(logDelta);
11064
numExponential = Math.max(Math.min(numExponential, 20), 0);
11065
tickString = tickValue.toExponential(numExponential);
11066
} else {
11067
var numDecimal = -1 * Math.floor(logDelta);
11068
numDecimal = Math.max(Math.min(numDecimal, 20), 0); // toFixed has a max of 20 decimal places
11069
tickString = tickValue.toFixed(numDecimal);
11070
}
11071
} else {
11072
tickString = '0'; // never show decimal places for 0
11073
}
11074
11075
return tickString;
11076
},
11077
11078
logarithmic: function(tickValue, index, ticks) {
11079
var remain = tickValue / (Math.pow(10, Math.floor(helpers$1.log10(tickValue))));
11080
11081
if (tickValue === 0) {
11082
return '0';
11083
} else if (remain === 1 || remain === 2 || remain === 5 || index === 0 || index === ticks.length - 1) {
11084
return tickValue.toExponential();
11085
}
11086
return '';
11087
}
11088
}
11089
};
11090
11091
var isArray = helpers$1.isArray;
11092
var isNullOrUndef = helpers$1.isNullOrUndef;
11093
var valueOrDefault$a = helpers$1.valueOrDefault;
11094
var valueAtIndexOrDefault = helpers$1.valueAtIndexOrDefault;
11095
11096
core_defaults._set('scale', {
11097
display: true,
11098
position: 'left',
11099
offset: false,
11100
11101
// grid line settings
11102
gridLines: {
11103
display: true,
11104
color: 'rgba(0,0,0,0.1)',
11105
lineWidth: 1,
11106
drawBorder: true,
11107
drawOnChartArea: true,
11108
drawTicks: true,
11109
tickMarkLength: 10,
11110
zeroLineWidth: 1,
11111
zeroLineColor: 'rgba(0,0,0,0.25)',
11112
zeroLineBorderDash: [],
11113
zeroLineBorderDashOffset: 0.0,
11114
offsetGridLines: false,
11115
borderDash: [],
11116
borderDashOffset: 0.0
11117
},
11118
11119
// scale label
11120
scaleLabel: {
11121
// display property
11122
display: false,
11123
11124
// actual label
11125
labelString: '',
11126
11127
// top/bottom padding
11128
padding: {
11129
top: 4,
11130
bottom: 4
11131
}
11132
},
11133
11134
// label settings
11135
ticks: {
11136
beginAtZero: false,
11137
minRotation: 0,
11138
maxRotation: 50,
11139
mirror: false,
11140
padding: 0,
11141
reverse: false,
11142
display: true,
11143
autoSkip: true,
11144
autoSkipPadding: 0,
11145
labelOffset: 0,
11146
// We pass through arrays to be rendered as multiline labels, we convert Others to strings here.
11147
callback: core_ticks.formatters.values,
11148
minor: {},
11149
major: {}
11150
}
11151
});
11152
11153
/** Returns a new array containing numItems from arr */
11154
function sample(arr, numItems) {
11155
var result = [];
11156
var increment = arr.length / numItems;
11157
var i = 0;
11158
var len = arr.length;
11159
11160
for (; i < len; i += increment) {
11161
result.push(arr[Math.floor(i)]);
11162
}
11163
return result;
11164
}
11165
11166
function getPixelForGridLine(scale, index, offsetGridLines) {
11167
var length = scale.getTicks().length;
11168
var validIndex = Math.min(index, length - 1);
11169
var lineValue = scale.getPixelForTick(validIndex);
11170
var start = scale._startPixel;
11171
var end = scale._endPixel;
11172
var epsilon = 1e-6; // 1e-6 is margin in pixels for accumulated error.
11173
var offset;
11174
11175
if (offsetGridLines) {
11176
if (length === 1) {
11177
offset = Math.max(lineValue - start, end - lineValue);
11178
} else if (index === 0) {
11179
offset = (scale.getPixelForTick(1) - lineValue) / 2;
11180
} else {
11181
offset = (lineValue - scale.getPixelForTick(validIndex - 1)) / 2;
11182
}
11183
lineValue += validIndex < index ? offset : -offset;
11184
11185
// Return undefined if the pixel is out of the range
11186
if (lineValue < start - epsilon || lineValue > end + epsilon) {
11187
return;
11188
}
11189
}
11190
return lineValue;
11191
}
11192
11193
function garbageCollect(caches, length) {
11194
helpers$1.each(caches, function(cache) {
11195
var gc = cache.gc;
11196
var gcLen = gc.length / 2;
11197
var i;
11198
if (gcLen > length) {
11199
for (i = 0; i < gcLen; ++i) {
11200
delete cache.data[gc[i]];
11201
}
11202
gc.splice(0, gcLen);
11203
}
11204
});
11205
}
11206
11207
/**
11208
* Returns {width, height, offset} objects for the first, last, widest, highest tick
11209
* labels where offset indicates the anchor point offset from the top in pixels.
11210
*/
11211
function computeLabelSizes(ctx, tickFonts, ticks, caches) {
11212
var length = ticks.length;
11213
var widths = [];
11214
var heights = [];
11215
var offsets = [];
11216
var i, j, jlen, label, tickFont, fontString, cache, lineHeight, width, height, nestedLabel, widest, highest;
11217
11218
for (i = 0; i < length; ++i) {
11219
label = ticks[i].label;
11220
tickFont = ticks[i].major ? tickFonts.major : tickFonts.minor;
11221
ctx.font = fontString = tickFont.string;
11222
cache = caches[fontString] = caches[fontString] || {data: {}, gc: []};
11223
lineHeight = tickFont.lineHeight;
11224
width = height = 0;
11225
// Undefined labels and arrays should not be measured
11226
if (!isNullOrUndef(label) && !isArray(label)) {
11227
width = helpers$1.measureText(ctx, cache.data, cache.gc, width, label);
11228
height = lineHeight;
11229
} else if (isArray(label)) {
11230
// if it is an array let's measure each element
11231
for (j = 0, jlen = label.length; j < jlen; ++j) {
11232
nestedLabel = label[j];
11233
// Undefined labels and arrays should not be measured
11234
if (!isNullOrUndef(nestedLabel) && !isArray(nestedLabel)) {
11235
width = helpers$1.measureText(ctx, cache.data, cache.gc, width, nestedLabel);
11236
height += lineHeight;
11237
}
11238
}
11239
}
11240
widths.push(width);
11241
heights.push(height);
11242
offsets.push(lineHeight / 2);
11243
}
11244
garbageCollect(caches, length);
11245
11246
widest = widths.indexOf(Math.max.apply(null, widths));
11247
highest = heights.indexOf(Math.max.apply(null, heights));
11248
11249
function valueAt(idx) {
11250
return {
11251
width: widths[idx] || 0,
11252
height: heights[idx] || 0,
11253
offset: offsets[idx] || 0
11254
};
11255
}
11256
11257
return {
11258
first: valueAt(0),
11259
last: valueAt(length - 1),
11260
widest: valueAt(widest),
11261
highest: valueAt(highest)
11262
};
11263
}
11264
11265
function getTickMarkLength(options) {
11266
return options.drawTicks ? options.tickMarkLength : 0;
11267
}
11268
11269
function getScaleLabelHeight(options) {
11270
var font, padding;
11271
11272
if (!options.display) {
11273
return 0;
11274
}
11275
11276
font = helpers$1.options._parseFont(options);
11277
padding = helpers$1.options.toPadding(options.padding);
11278
11279
return font.lineHeight + padding.height;
11280
}
11281
11282
function parseFontOptions(options, nestedOpts) {
11283
return helpers$1.extend(helpers$1.options._parseFont({
11284
fontFamily: valueOrDefault$a(nestedOpts.fontFamily, options.fontFamily),
11285
fontSize: valueOrDefault$a(nestedOpts.fontSize, options.fontSize),
11286
fontStyle: valueOrDefault$a(nestedOpts.fontStyle, options.fontStyle),
11287
lineHeight: valueOrDefault$a(nestedOpts.lineHeight, options.lineHeight)
11288
}), {
11289
color: helpers$1.options.resolve([nestedOpts.fontColor, options.fontColor, core_defaults.global.defaultFontColor])
11290
});
11291
}
11292
11293
function parseTickFontOptions(options) {
11294
var minor = parseFontOptions(options, options.minor);
11295
var major = options.major.enabled ? parseFontOptions(options, options.major) : minor;
11296
11297
return {minor: minor, major: major};
11298
}
11299
11300
function nonSkipped(ticksToFilter) {
11301
var filtered = [];
11302
var item, index, len;
11303
for (index = 0, len = ticksToFilter.length; index < len; ++index) {
11304
item = ticksToFilter[index];
11305
if (typeof item._index !== 'undefined') {
11306
filtered.push(item);
11307
}
11308
}
11309
return filtered;
11310
}
11311
11312
function getEvenSpacing(arr) {
11313
var len = arr.length;
11314
var i, diff;
11315
11316
if (len < 2) {
11317
return false;
11318
}
11319
11320
for (diff = arr[0], i = 1; i < len; ++i) {
11321
if (arr[i] - arr[i - 1] !== diff) {
11322
return false;
11323
}
11324
}
11325
return diff;
11326
}
11327
11328
function calculateSpacing(majorIndices, ticks, axisLength, ticksLimit) {
11329
var evenMajorSpacing = getEvenSpacing(majorIndices);
11330
var spacing = (ticks.length - 1) / ticksLimit;
11331
var factors, factor, i, ilen;
11332
11333
// If the major ticks are evenly spaced apart, place the minor ticks
11334
// so that they divide the major ticks into even chunks
11335
if (!evenMajorSpacing) {
11336
return Math.max(spacing, 1);
11337
}
11338
11339
factors = helpers$1.math._factorize(evenMajorSpacing);
11340
for (i = 0, ilen = factors.length - 1; i < ilen; i++) {
11341
factor = factors[i];
11342
if (factor > spacing) {
11343
return factor;
11344
}
11345
}
11346
return Math.max(spacing, 1);
11347
}
11348
11349
function getMajorIndices(ticks) {
11350
var result = [];
11351
var i, ilen;
11352
for (i = 0, ilen = ticks.length; i < ilen; i++) {
11353
if (ticks[i].major) {
11354
result.push(i);
11355
}
11356
}
11357
return result;
11358
}
11359
11360
function skipMajors(ticks, majorIndices, spacing) {
11361
var count = 0;
11362
var next = majorIndices[0];
11363
var i, tick;
11364
11365
spacing = Math.ceil(spacing);
11366
for (i = 0; i < ticks.length; i++) {
11367
tick = ticks[i];
11368
if (i === next) {
11369
tick._index = i;
11370
count++;
11371
next = majorIndices[count * spacing];
11372
} else {
11373
delete tick.label;
11374
}
11375
}
11376
}
11377
11378
function skip(ticks, spacing, majorStart, majorEnd) {
11379
var start = valueOrDefault$a(majorStart, 0);
11380
var end = Math.min(valueOrDefault$a(majorEnd, ticks.length), ticks.length);
11381
var count = 0;
11382
var length, i, tick, next;
11383
11384
spacing = Math.ceil(spacing);
11385
if (majorEnd) {
11386
length = majorEnd - majorStart;
11387
spacing = length / Math.floor(length / spacing);
11388
}
11389
11390
next = start;
11391
11392
while (next < 0) {
11393
count++;
11394
next = Math.round(start + count * spacing);
11395
}
11396
11397
for (i = Math.max(start, 0); i < end; i++) {
11398
tick = ticks[i];
11399
if (i === next) {
11400
tick._index = i;
11401
count++;
11402
next = Math.round(start + count * spacing);
11403
} else {
11404
delete tick.label;
11405
}
11406
}
11407
}
11408
11409
var Scale = core_element.extend({
11410
11411
zeroLineIndex: 0,
11412
11413
/**
11414
* Get the padding needed for the scale
11415
* @method getPadding
11416
* @private
11417
* @returns {Padding} the necessary padding
11418
*/
11419
getPadding: function() {
11420
var me = this;
11421
return {
11422
left: me.paddingLeft || 0,
11423
top: me.paddingTop || 0,
11424
right: me.paddingRight || 0,
11425
bottom: me.paddingBottom || 0
11426
};
11427
},
11428
11429
/**
11430
* Returns the scale tick objects ({label, major})
11431
* @since 2.7
11432
*/
11433
getTicks: function() {
11434
return this._ticks;
11435
},
11436
11437
/**
11438
* @private
11439
*/
11440
_getLabels: function() {
11441
var data = this.chart.data;
11442
return this.options.labels || (this.isHorizontal() ? data.xLabels : data.yLabels) || data.labels || [];
11443
},
11444
11445
// These methods are ordered by lifecyle. Utilities then follow.
11446
// Any function defined here is inherited by all scale types.
11447
// Any function can be extended by the scale type
11448
11449
/**
11450
* Provided for backward compatibility, not available anymore
11451
* @function Chart.Scale.mergeTicksOptions
11452
* @deprecated since version 2.8.0
11453
* @todo remove at version 3
11454
*/
11455
mergeTicksOptions: function() {
11456
// noop
11457
},
11458
11459
beforeUpdate: function() {
11460
helpers$1.callback(this.options.beforeUpdate, [this]);
11461
},
11462
11463
/**
11464
* @param {number} maxWidth - the max width in pixels
11465
* @param {number} maxHeight - the max height in pixels
11466
* @param {object} margins - the space between the edge of the other scales and edge of the chart
11467
* This space comes from two sources:
11468
* - padding - space that's required to show the labels at the edges of the scale
11469
* - thickness of scales or legends in another orientation
11470
*/
11471
update: function(maxWidth, maxHeight, margins) {
11472
var me = this;
11473
var tickOpts = me.options.ticks;
11474
var sampleSize = tickOpts.sampleSize;
11475
var i, ilen, labels, ticks, samplingEnabled;
11476
11477
// Update Lifecycle - Probably don't want to ever extend or overwrite this function ;)
11478
me.beforeUpdate();
11479
11480
// Absorb the master measurements
11481
me.maxWidth = maxWidth;
11482
me.maxHeight = maxHeight;
11483
me.margins = helpers$1.extend({
11484
left: 0,
11485
right: 0,
11486
top: 0,
11487
bottom: 0
11488
}, margins);
11489
11490
me._ticks = null;
11491
me.ticks = null;
11492
me._labelSizes = null;
11493
me._maxLabelLines = 0;
11494
me.longestLabelWidth = 0;
11495
me.longestTextCache = me.longestTextCache || {};
11496
me._gridLineItems = null;
11497
me._labelItems = null;
11498
11499
// Dimensions
11500
me.beforeSetDimensions();
11501
me.setDimensions();
11502
me.afterSetDimensions();
11503
11504
// Data min/max
11505
me.beforeDataLimits();
11506
me.determineDataLimits();
11507
me.afterDataLimits();
11508
11509
// Ticks - `this.ticks` is now DEPRECATED!
11510
// Internal ticks are now stored as objects in the PRIVATE `this._ticks` member
11511
// and must not be accessed directly from outside this class. `this.ticks` being
11512
// around for long time and not marked as private, we can't change its structure
11513
// without unexpected breaking changes. If you need to access the scale ticks,
11514
// use scale.getTicks() instead.
11515
11516
me.beforeBuildTicks();
11517
11518
// New implementations should return an array of objects but for BACKWARD COMPAT,
11519
// we still support no return (`this.ticks` internally set by calling this method).
11520
ticks = me.buildTicks() || [];
11521
11522
// Allow modification of ticks in callback.
11523
ticks = me.afterBuildTicks(ticks) || ticks;
11524
11525
// Ensure ticks contains ticks in new tick format
11526
if ((!ticks || !ticks.length) && me.ticks) {
11527
ticks = [];
11528
for (i = 0, ilen = me.ticks.length; i < ilen; ++i) {
11529
ticks.push({
11530
value: me.ticks[i],
11531
major: false
11532
});
11533
}
11534
}
11535
11536
me._ticks = ticks;
11537
11538
// Compute tick rotation and fit using a sampled subset of labels
11539
// We generally don't need to compute the size of every single label for determining scale size
11540
samplingEnabled = sampleSize < ticks.length;
11541
labels = me._convertTicksToLabels(samplingEnabled ? sample(ticks, sampleSize) : ticks);
11542
11543
// _configure is called twice, once here, once from core.controller.updateLayout.
11544
// Here we haven't been positioned yet, but dimensions are correct.
11545
// Variables set in _configure are needed for calculateTickRotation, and
11546
// it's ok that coordinates are not correct there, only dimensions matter.
11547
me._configure();
11548
11549
// Tick Rotation
11550
me.beforeCalculateTickRotation();
11551
me.calculateTickRotation();
11552
me.afterCalculateTickRotation();
11553
11554
me.beforeFit();
11555
me.fit();
11556
me.afterFit();
11557
11558
// Auto-skip
11559
me._ticksToDraw = tickOpts.display && (tickOpts.autoSkip || tickOpts.source === 'auto') ? me._autoSkip(ticks) : ticks;
11560
11561
if (samplingEnabled) {
11562
// Generate labels using all non-skipped ticks
11563
labels = me._convertTicksToLabels(me._ticksToDraw);
11564
}
11565
11566
me.ticks = labels; // BACKWARD COMPATIBILITY
11567
11568
// IMPORTANT: after this point, we consider that `this.ticks` will NEVER change!
11569
11570
me.afterUpdate();
11571
11572
// TODO(v3): remove minSize as a public property and return value from all layout boxes. It is unused
11573
// make maxWidth and maxHeight private
11574
return me.minSize;
11575
},
11576
11577
/**
11578
* @private
11579
*/
11580
_configure: function() {
11581
var me = this;
11582
var reversePixels = me.options.ticks.reverse;
11583
var startPixel, endPixel;
11584
11585
if (me.isHorizontal()) {
11586
startPixel = me.left;
11587
endPixel = me.right;
11588
} else {
11589
startPixel = me.top;
11590
endPixel = me.bottom;
11591
// by default vertical scales are from bottom to top, so pixels are reversed
11592
reversePixels = !reversePixels;
11593
}
11594
me._startPixel = startPixel;
11595
me._endPixel = endPixel;
11596
me._reversePixels = reversePixels;
11597
me._length = endPixel - startPixel;
11598
},
11599
11600
afterUpdate: function() {
11601
helpers$1.callback(this.options.afterUpdate, [this]);
11602
},
11603
11604
//
11605
11606
beforeSetDimensions: function() {
11607
helpers$1.callback(this.options.beforeSetDimensions, [this]);
11608
},
11609
setDimensions: function() {
11610
var me = this;
11611
// Set the unconstrained dimension before label rotation
11612
if (me.isHorizontal()) {
11613
// Reset position before calculating rotation
11614
me.width = me.maxWidth;
11615
me.left = 0;
11616
me.right = me.width;
11617
} else {
11618
me.height = me.maxHeight;
11619
11620
// Reset position before calculating rotation
11621
me.top = 0;
11622
me.bottom = me.height;
11623
}
11624
11625
// Reset padding
11626
me.paddingLeft = 0;
11627
me.paddingTop = 0;
11628
me.paddingRight = 0;
11629
me.paddingBottom = 0;
11630
},
11631
afterSetDimensions: function() {
11632
helpers$1.callback(this.options.afterSetDimensions, [this]);
11633
},
11634
11635
// Data limits
11636
beforeDataLimits: function() {
11637
helpers$1.callback(this.options.beforeDataLimits, [this]);
11638
},
11639
determineDataLimits: helpers$1.noop,
11640
afterDataLimits: function() {
11641
helpers$1.callback(this.options.afterDataLimits, [this]);
11642
},
11643
11644
//
11645
beforeBuildTicks: function() {
11646
helpers$1.callback(this.options.beforeBuildTicks, [this]);
11647
},
11648
buildTicks: helpers$1.noop,
11649
afterBuildTicks: function(ticks) {
11650
var me = this;
11651
// ticks is empty for old axis implementations here
11652
if (isArray(ticks) && ticks.length) {
11653
return helpers$1.callback(me.options.afterBuildTicks, [me, ticks]);
11654
}
11655
// Support old implementations (that modified `this.ticks` directly in buildTicks)
11656
me.ticks = helpers$1.callback(me.options.afterBuildTicks, [me, me.ticks]) || me.ticks;
11657
return ticks;
11658
},
11659
11660
beforeTickToLabelConversion: function() {
11661
helpers$1.callback(this.options.beforeTickToLabelConversion, [this]);
11662
},
11663
convertTicksToLabels: function() {
11664
var me = this;
11665
// Convert ticks to strings
11666
var tickOpts = me.options.ticks;
11667
me.ticks = me.ticks.map(tickOpts.userCallback || tickOpts.callback, this);
11668
},
11669
afterTickToLabelConversion: function() {
11670
helpers$1.callback(this.options.afterTickToLabelConversion, [this]);
11671
},
11672
11673
//
11674
11675
beforeCalculateTickRotation: function() {
11676
helpers$1.callback(this.options.beforeCalculateTickRotation, [this]);
11677
},
11678
calculateTickRotation: function() {
11679
var me = this;
11680
var options = me.options;
11681
var tickOpts = options.ticks;
11682
var numTicks = me.getTicks().length;
11683
var minRotation = tickOpts.minRotation || 0;
11684
var maxRotation = tickOpts.maxRotation;
11685
var labelRotation = minRotation;
11686
var labelSizes, maxLabelWidth, maxLabelHeight, maxWidth, tickWidth, maxHeight, maxLabelDiagonal;
11687
11688
if (!me._isVisible() || !tickOpts.display || minRotation >= maxRotation || numTicks <= 1 || !me.isHorizontal()) {
11689
me.labelRotation = minRotation;
11690
return;
11691
}
11692
11693
labelSizes = me._getLabelSizes();
11694
maxLabelWidth = labelSizes.widest.width;
11695
maxLabelHeight = labelSizes.highest.height - labelSizes.highest.offset;
11696
11697
// Estimate the width of each grid based on the canvas width, the maximum
11698
// label width and the number of tick intervals
11699
maxWidth = Math.min(me.maxWidth, me.chart.width - maxLabelWidth);
11700
tickWidth = options.offset ? me.maxWidth / numTicks : maxWidth / (numTicks - 1);
11701
11702
// Allow 3 pixels x2 padding either side for label readability
11703
if (maxLabelWidth + 6 > tickWidth) {
11704
tickWidth = maxWidth / (numTicks - (options.offset ? 0.5 : 1));
11705
maxHeight = me.maxHeight - getTickMarkLength(options.gridLines)
11706
- tickOpts.padding - getScaleLabelHeight(options.scaleLabel);
11707
maxLabelDiagonal = Math.sqrt(maxLabelWidth * maxLabelWidth + maxLabelHeight * maxLabelHeight);
11708
labelRotation = helpers$1.toDegrees(Math.min(
11709
Math.asin(Math.min((labelSizes.highest.height + 6) / tickWidth, 1)),
11710
Math.asin(Math.min(maxHeight / maxLabelDiagonal, 1)) - Math.asin(maxLabelHeight / maxLabelDiagonal)
11711
));
11712
labelRotation = Math.max(minRotation, Math.min(maxRotation, labelRotation));
11713
}
11714
11715
me.labelRotation = labelRotation;
11716
},
11717
afterCalculateTickRotation: function() {
11718
helpers$1.callback(this.options.afterCalculateTickRotation, [this]);
11719
},
11720
11721
//
11722
11723
beforeFit: function() {
11724
helpers$1.callback(this.options.beforeFit, [this]);
11725
},
11726
fit: function() {
11727
var me = this;
11728
// Reset
11729
var minSize = me.minSize = {
11730
width: 0,
11731
height: 0
11732
};
11733
11734
var chart = me.chart;
11735
var opts = me.options;
11736
var tickOpts = opts.ticks;
11737
var scaleLabelOpts = opts.scaleLabel;
11738
var gridLineOpts = opts.gridLines;
11739
var display = me._isVisible();
11740
var isBottom = opts.position === 'bottom';
11741
var isHorizontal = me.isHorizontal();
11742
11743
// Width
11744
if (isHorizontal) {
11745
minSize.width = me.maxWidth;
11746
} else if (display) {
11747
minSize.width = getTickMarkLength(gridLineOpts) + getScaleLabelHeight(scaleLabelOpts);
11748
}
11749
11750
// height
11751
if (!isHorizontal) {
11752
minSize.height = me.maxHeight; // fill all the height
11753
} else if (display) {
11754
minSize.height = getTickMarkLength(gridLineOpts) + getScaleLabelHeight(scaleLabelOpts);
11755
}
11756
11757
// Don't bother fitting the ticks if we are not showing the labels
11758
if (tickOpts.display && display) {
11759
var tickFonts = parseTickFontOptions(tickOpts);
11760
var labelSizes = me._getLabelSizes();
11761
var firstLabelSize = labelSizes.first;
11762
var lastLabelSize = labelSizes.last;
11763
var widestLabelSize = labelSizes.widest;
11764
var highestLabelSize = labelSizes.highest;
11765
var lineSpace = tickFonts.minor.lineHeight * 0.4;
11766
var tickPadding = tickOpts.padding;
11767
11768
if (isHorizontal) {
11769
// A horizontal axis is more constrained by the height.
11770
var isRotated = me.labelRotation !== 0;
11771
var angleRadians = helpers$1.toRadians(me.labelRotation);
11772
var cosRotation = Math.cos(angleRadians);
11773
var sinRotation = Math.sin(angleRadians);
11774
11775
var labelHeight = sinRotation * widestLabelSize.width
11776
+ cosRotation * (highestLabelSize.height - (isRotated ? highestLabelSize.offset : 0))
11777
+ (isRotated ? 0 : lineSpace); // padding
11778
11779
minSize.height = Math.min(me.maxHeight, minSize.height + labelHeight + tickPadding);
11780
11781
var offsetLeft = me.getPixelForTick(0) - me.left;
11782
var offsetRight = me.right - me.getPixelForTick(me.getTicks().length - 1);
11783
var paddingLeft, paddingRight;
11784
11785
// Ensure that our ticks are always inside the canvas. When rotated, ticks are right aligned
11786
// which means that the right padding is dominated by the font height
11787
if (isRotated) {
11788
paddingLeft = isBottom ?
11789
cosRotation * firstLabelSize.width + sinRotation * firstLabelSize.offset :
11790
sinRotation * (firstLabelSize.height - firstLabelSize.offset);
11791
paddingRight = isBottom ?
11792
sinRotation * (lastLabelSize.height - lastLabelSize.offset) :
11793
cosRotation * lastLabelSize.width + sinRotation * lastLabelSize.offset;
11794
} else {
11795
paddingLeft = firstLabelSize.width / 2;
11796
paddingRight = lastLabelSize.width / 2;
11797
}
11798
11799
// Adjust padding taking into account changes in offsets
11800
// and add 3 px to move away from canvas edges
11801
me.paddingLeft = Math.max((paddingLeft - offsetLeft) * me.width / (me.width - offsetLeft), 0) + 3;
11802
me.paddingRight = Math.max((paddingRight - offsetRight) * me.width / (me.width - offsetRight), 0) + 3;
11803
} else {
11804
// A vertical axis is more constrained by the width. Labels are the
11805
// dominant factor here, so get that length first and account for padding
11806
var labelWidth = tickOpts.mirror ? 0 :
11807
// use lineSpace for consistency with horizontal axis
11808
// tickPadding is not implemented for horizontal
11809
widestLabelSize.width + tickPadding + lineSpace;
11810
11811
minSize.width = Math.min(me.maxWidth, minSize.width + labelWidth);
11812
11813
me.paddingTop = firstLabelSize.height / 2;
11814
me.paddingBottom = lastLabelSize.height / 2;
11815
}
11816
}
11817
11818
me.handleMargins();
11819
11820
if (isHorizontal) {
11821
me.width = me._length = chart.width - me.margins.left - me.margins.right;
11822
me.height = minSize.height;
11823
} else {
11824
me.width = minSize.width;
11825
me.height = me._length = chart.height - me.margins.top - me.margins.bottom;
11826
}
11827
},
11828
11829
/**
11830
* Handle margins and padding interactions
11831
* @private
11832
*/
11833
handleMargins: function() {
11834
var me = this;
11835
if (me.margins) {
11836
me.margins.left = Math.max(me.paddingLeft, me.margins.left);
11837
me.margins.top = Math.max(me.paddingTop, me.margins.top);
11838
me.margins.right = Math.max(me.paddingRight, me.margins.right);
11839
me.margins.bottom = Math.max(me.paddingBottom, me.margins.bottom);
11840
}
11841
},
11842
11843
afterFit: function() {
11844
helpers$1.callback(this.options.afterFit, [this]);
11845
},
11846
11847
// Shared Methods
11848
isHorizontal: function() {
11849
var pos = this.options.position;
11850
return pos === 'top' || pos === 'bottom';
11851
},
11852
isFullWidth: function() {
11853
return this.options.fullWidth;
11854
},
11855
11856
// 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
11857
getRightValue: function(rawValue) {
11858
// Null and undefined values first
11859
if (isNullOrUndef(rawValue)) {
11860
return NaN;
11861
}
11862
// isNaN(object) returns true, so make sure NaN is checking for a number; Discard Infinite values
11863
if ((typeof rawValue === 'number' || rawValue instanceof Number) && !isFinite(rawValue)) {
11864
return NaN;
11865
}
11866
11867
// If it is in fact an object, dive in one more level
11868
if (rawValue) {
11869
if (this.isHorizontal()) {
11870
if (rawValue.x !== undefined) {
11871
return this.getRightValue(rawValue.x);
11872
}
11873
} else if (rawValue.y !== undefined) {
11874
return this.getRightValue(rawValue.y);
11875
}
11876
}
11877
11878
// Value is good, return it
11879
return rawValue;
11880
},
11881
11882
_convertTicksToLabels: function(ticks) {
11883
var me = this;
11884
var labels, i, ilen;
11885
11886
me.ticks = ticks.map(function(tick) {
11887
return tick.value;
11888
});
11889
11890
me.beforeTickToLabelConversion();
11891
11892
// New implementations should return the formatted tick labels but for BACKWARD
11893
// COMPAT, we still support no return (`this.ticks` internally changed by calling
11894
// this method and supposed to contain only string values).
11895
labels = me.convertTicksToLabels(ticks) || me.ticks;
11896
11897
me.afterTickToLabelConversion();
11898
11899
// BACKWARD COMPAT: synchronize `_ticks` with labels (so potentially `this.ticks`)
11900
for (i = 0, ilen = ticks.length; i < ilen; ++i) {
11901
ticks[i].label = labels[i];
11902
}
11903
11904
return labels;
11905
},
11906
11907
/**
11908
* @private
11909
*/
11910
_getLabelSizes: function() {
11911
var me = this;
11912
var labelSizes = me._labelSizes;
11913
11914
if (!labelSizes) {
11915
me._labelSizes = labelSizes = computeLabelSizes(me.ctx, parseTickFontOptions(me.options.ticks), me.getTicks(), me.longestTextCache);
11916
me.longestLabelWidth = labelSizes.widest.width;
11917
}
11918
11919
return labelSizes;
11920
},
11921
11922
/**
11923
* @private
11924
*/
11925
_parseValue: function(value) {
11926
var start, end, min, max;
11927
11928
if (isArray(value)) {
11929
start = +this.getRightValue(value[0]);
11930
end = +this.getRightValue(value[1]);
11931
min = Math.min(start, end);
11932
max = Math.max(start, end);
11933
} else {
11934
value = +this.getRightValue(value);
11935
start = undefined;
11936
end = value;
11937
min = value;
11938
max = value;
11939
}
11940
11941
return {
11942
min: min,
11943
max: max,
11944
start: start,
11945
end: end
11946
};
11947
},
11948
11949
/**
11950
* @private
11951
*/
11952
_getScaleLabel: function(rawValue) {
11953
var v = this._parseValue(rawValue);
11954
if (v.start !== undefined) {
11955
return '[' + v.start + ', ' + v.end + ']';
11956
}
11957
11958
return +this.getRightValue(rawValue);
11959
},
11960
11961
/**
11962
* Used to get the value to display in the tooltip for the data at the given index
11963
* @param index
11964
* @param datasetIndex
11965
*/
11966
getLabelForIndex: helpers$1.noop,
11967
11968
/**
11969
* Returns the location of the given data point. Value can either be an index or a numerical value
11970
* The coordinate (0, 0) is at the upper-left corner of the canvas
11971
* @param value
11972
* @param index
11973
* @param datasetIndex
11974
*/
11975
getPixelForValue: helpers$1.noop,
11976
11977
/**
11978
* Used to get the data value from a given pixel. This is the inverse of getPixelForValue
11979
* The coordinate (0, 0) is at the upper-left corner of the canvas
11980
* @param pixel
11981
*/
11982
getValueForPixel: helpers$1.noop,
11983
11984
/**
11985
* Returns the location of the tick at the given index
11986
* The coordinate (0, 0) is at the upper-left corner of the canvas
11987
*/
11988
getPixelForTick: function(index) {
11989
var me = this;
11990
var offset = me.options.offset;
11991
var numTicks = me._ticks.length;
11992
var tickWidth = 1 / Math.max(numTicks - (offset ? 0 : 1), 1);
11993
11994
return index < 0 || index > numTicks - 1
11995
? null
11996
: me.getPixelForDecimal(index * tickWidth + (offset ? tickWidth / 2 : 0));
11997
},
11998
11999
/**
12000
* Utility for getting the pixel location of a percentage of scale
12001
* The coordinate (0, 0) is at the upper-left corner of the canvas
12002
*/
12003
getPixelForDecimal: function(decimal) {
12004
var me = this;
12005
12006
if (me._reversePixels) {
12007
decimal = 1 - decimal;
12008
}
12009
12010
return me._startPixel + decimal * me._length;
12011
},
12012
12013
getDecimalForPixel: function(pixel) {
12014
var decimal = (pixel - this._startPixel) / this._length;
12015
return this._reversePixels ? 1 - decimal : decimal;
12016
},
12017
12018
/**
12019
* Returns the pixel for the minimum chart value
12020
* The coordinate (0, 0) is at the upper-left corner of the canvas
12021
*/
12022
getBasePixel: function() {
12023
return this.getPixelForValue(this.getBaseValue());
12024
},
12025
12026
getBaseValue: function() {
12027
var me = this;
12028
var min = me.min;
12029
var max = me.max;
12030
12031
return me.beginAtZero ? 0 :
12032
min < 0 && max < 0 ? max :
12033
min > 0 && max > 0 ? min :
12034
0;
12035
},
12036
12037
/**
12038
* Returns a subset of ticks to be plotted to avoid overlapping labels.
12039
* @private
12040
*/
12041
_autoSkip: function(ticks) {
12042
var me = this;
12043
var tickOpts = me.options.ticks;
12044
var axisLength = me._length;
12045
var ticksLimit = tickOpts.maxTicksLimit || axisLength / me._tickSize() + 1;
12046
var majorIndices = tickOpts.major.enabled ? getMajorIndices(ticks) : [];
12047
var numMajorIndices = majorIndices.length;
12048
var first = majorIndices[0];
12049
var last = majorIndices[numMajorIndices - 1];
12050
var i, ilen, spacing, avgMajorSpacing;
12051
12052
// If there are too many major ticks to display them all
12053
if (numMajorIndices > ticksLimit) {
12054
skipMajors(ticks, majorIndices, numMajorIndices / ticksLimit);
12055
return nonSkipped(ticks);
12056
}
12057
12058
spacing = calculateSpacing(majorIndices, ticks, axisLength, ticksLimit);
12059
12060
if (numMajorIndices > 0) {
12061
for (i = 0, ilen = numMajorIndices - 1; i < ilen; i++) {
12062
skip(ticks, spacing, majorIndices[i], majorIndices[i + 1]);
12063
}
12064
avgMajorSpacing = numMajorIndices > 1 ? (last - first) / (numMajorIndices - 1) : null;
12065
skip(ticks, spacing, helpers$1.isNullOrUndef(avgMajorSpacing) ? 0 : first - avgMajorSpacing, first);
12066
skip(ticks, spacing, last, helpers$1.isNullOrUndef(avgMajorSpacing) ? ticks.length : last + avgMajorSpacing);
12067
return nonSkipped(ticks);
12068
}
12069
skip(ticks, spacing);
12070
return nonSkipped(ticks);
12071
},
12072
12073
/**
12074
* @private
12075
*/
12076
_tickSize: function() {
12077
var me = this;
12078
var optionTicks = me.options.ticks;
12079
12080
// Calculate space needed by label in axis direction.
12081
var rot = helpers$1.toRadians(me.labelRotation);
12082
var cos = Math.abs(Math.cos(rot));
12083
var sin = Math.abs(Math.sin(rot));
12084
12085
var labelSizes = me._getLabelSizes();
12086
var padding = optionTicks.autoSkipPadding || 0;
12087
var w = labelSizes ? labelSizes.widest.width + padding : 0;
12088
var h = labelSizes ? labelSizes.highest.height + padding : 0;
12089
12090
// Calculate space needed for 1 tick in axis direction.
12091
return me.isHorizontal()
12092
? h * cos > w * sin ? w / cos : h / sin
12093
: h * sin < w * cos ? h / cos : w / sin;
12094
},
12095
12096
/**
12097
* @private
12098
*/
12099
_isVisible: function() {
12100
var me = this;
12101
var chart = me.chart;
12102
var display = me.options.display;
12103
var i, ilen, meta;
12104
12105
if (display !== 'auto') {
12106
return !!display;
12107
}
12108
12109
// When 'auto', the scale is visible if at least one associated dataset is visible.
12110
for (i = 0, ilen = chart.data.datasets.length; i < ilen; ++i) {
12111
if (chart.isDatasetVisible(i)) {
12112
meta = chart.getDatasetMeta(i);
12113
if (meta.xAxisID === me.id || meta.yAxisID === me.id) {
12114
return true;
12115
}
12116
}
12117
}
12118
12119
return false;
12120
},
12121
12122
/**
12123
* @private
12124
*/
12125
_computeGridLineItems: function(chartArea) {
12126
var me = this;
12127
var chart = me.chart;
12128
var options = me.options;
12129
var gridLines = options.gridLines;
12130
var position = options.position;
12131
var offsetGridLines = gridLines.offsetGridLines;
12132
var isHorizontal = me.isHorizontal();
12133
var ticks = me._ticksToDraw;
12134
var ticksLength = ticks.length + (offsetGridLines ? 1 : 0);
12135
12136
var tl = getTickMarkLength(gridLines);
12137
var items = [];
12138
var axisWidth = gridLines.drawBorder ? valueAtIndexOrDefault(gridLines.lineWidth, 0, 0) : 0;
12139
var axisHalfWidth = axisWidth / 2;
12140
var alignPixel = helpers$1._alignPixel;
12141
var alignBorderValue = function(pixel) {
12142
return alignPixel(chart, pixel, axisWidth);
12143
};
12144
var borderValue, i, tick, lineValue, alignedLineValue;
12145
var tx1, ty1, tx2, ty2, x1, y1, x2, y2, lineWidth, lineColor, borderDash, borderDashOffset;
12146
12147
if (position === 'top') {
12148
borderValue = alignBorderValue(me.bottom);
12149
ty1 = me.bottom - tl;
12150
ty2 = borderValue - axisHalfWidth;
12151
y1 = alignBorderValue(chartArea.top) + axisHalfWidth;
12152
y2 = chartArea.bottom;
12153
} else if (position === 'bottom') {
12154
borderValue = alignBorderValue(me.top);
12155
y1 = chartArea.top;
12156
y2 = alignBorderValue(chartArea.bottom) - axisHalfWidth;
12157
ty1 = borderValue + axisHalfWidth;
12158
ty2 = me.top + tl;
12159
} else if (position === 'left') {
12160
borderValue = alignBorderValue(me.right);
12161
tx1 = me.right - tl;
12162
tx2 = borderValue - axisHalfWidth;
12163
x1 = alignBorderValue(chartArea.left) + axisHalfWidth;
12164
x2 = chartArea.right;
12165
} else {
12166
borderValue = alignBorderValue(me.left);
12167
x1 = chartArea.left;
12168
x2 = alignBorderValue(chartArea.right) - axisHalfWidth;
12169
tx1 = borderValue + axisHalfWidth;
12170
tx2 = me.left + tl;
12171
}
12172
12173
for (i = 0; i < ticksLength; ++i) {
12174
tick = ticks[i] || {};
12175
12176
// autoskipper skipped this tick (#4635)
12177
if (isNullOrUndef(tick.label) && i < ticks.length) {
12178
continue;
12179
}
12180
12181
if (i === me.zeroLineIndex && options.offset === offsetGridLines) {
12182
// Draw the first index specially
12183
lineWidth = gridLines.zeroLineWidth;
12184
lineColor = gridLines.zeroLineColor;
12185
borderDash = gridLines.zeroLineBorderDash || [];
12186
borderDashOffset = gridLines.zeroLineBorderDashOffset || 0.0;
12187
} else {
12188
lineWidth = valueAtIndexOrDefault(gridLines.lineWidth, i, 1);
12189
lineColor = valueAtIndexOrDefault(gridLines.color, i, 'rgba(0,0,0,0.1)');
12190
borderDash = gridLines.borderDash || [];
12191
borderDashOffset = gridLines.borderDashOffset || 0.0;
12192
}
12193
12194
lineValue = getPixelForGridLine(me, tick._index || i, offsetGridLines);
12195
12196
// Skip if the pixel is out of the range
12197
if (lineValue === undefined) {
12198
continue;
12199
}
12200
12201
alignedLineValue = alignPixel(chart, lineValue, lineWidth);
12202
12203
if (isHorizontal) {
12204
tx1 = tx2 = x1 = x2 = alignedLineValue;
12205
} else {
12206
ty1 = ty2 = y1 = y2 = alignedLineValue;
12207
}
12208
12209
items.push({
12210
tx1: tx1,
12211
ty1: ty1,
12212
tx2: tx2,
12213
ty2: ty2,
12214
x1: x1,
12215
y1: y1,
12216
x2: x2,
12217
y2: y2,
12218
width: lineWidth,
12219
color: lineColor,
12220
borderDash: borderDash,
12221
borderDashOffset: borderDashOffset,
12222
});
12223
}
12224
12225
items.ticksLength = ticksLength;
12226
items.borderValue = borderValue;
12227
12228
return items;
12229
},
12230
12231
/**
12232
* @private
12233
*/
12234
_computeLabelItems: function() {
12235
var me = this;
12236
var options = me.options;
12237
var optionTicks = options.ticks;
12238
var position = options.position;
12239
var isMirrored = optionTicks.mirror;
12240
var isHorizontal = me.isHorizontal();
12241
var ticks = me._ticksToDraw;
12242
var fonts = parseTickFontOptions(optionTicks);
12243
var tickPadding = optionTicks.padding;
12244
var tl = getTickMarkLength(options.gridLines);
12245
var rotation = -helpers$1.toRadians(me.labelRotation);
12246
var items = [];
12247
var i, ilen, tick, label, x, y, textAlign, pixel, font, lineHeight, lineCount, textOffset;
12248
12249
if (position === 'top') {
12250
y = me.bottom - tl - tickPadding;
12251
textAlign = !rotation ? 'center' : 'left';
12252
} else if (position === 'bottom') {
12253
y = me.top + tl + tickPadding;
12254
textAlign = !rotation ? 'center' : 'right';
12255
} else if (position === 'left') {
12256
x = me.right - (isMirrored ? 0 : tl) - tickPadding;
12257
textAlign = isMirrored ? 'left' : 'right';
12258
} else {
12259
x = me.left + (isMirrored ? 0 : tl) + tickPadding;
12260
textAlign = isMirrored ? 'right' : 'left';
12261
}
12262
12263
for (i = 0, ilen = ticks.length; i < ilen; ++i) {
12264
tick = ticks[i];
12265
label = tick.label;
12266
12267
// autoskipper skipped this tick (#4635)
12268
if (isNullOrUndef(label)) {
12269
continue;
12270
}
12271
12272
pixel = me.getPixelForTick(tick._index || i) + optionTicks.labelOffset;
12273
font = tick.major ? fonts.major : fonts.minor;
12274
lineHeight = font.lineHeight;
12275
lineCount = isArray(label) ? label.length : 1;
12276
12277
if (isHorizontal) {
12278
x = pixel;
12279
textOffset = position === 'top'
12280
? ((!rotation ? 0.5 : 1) - lineCount) * lineHeight
12281
: (!rotation ? 0.5 : 0) * lineHeight;
12282
} else {
12283
y = pixel;
12284
textOffset = (1 - lineCount) * lineHeight / 2;
12285
}
12286
12287
items.push({
12288
x: x,
12289
y: y,
12290
rotation: rotation,
12291
label: label,
12292
font: font,
12293
textOffset: textOffset,
12294
textAlign: textAlign
12295
});
12296
}
12297
12298
return items;
12299
},
12300
12301
/**
12302
* @private
12303
*/
12304
_drawGrid: function(chartArea) {
12305
var me = this;
12306
var gridLines = me.options.gridLines;
12307
12308
if (!gridLines.display) {
12309
return;
12310
}
12311
12312
var ctx = me.ctx;
12313
var chart = me.chart;
12314
var alignPixel = helpers$1._alignPixel;
12315
var axisWidth = gridLines.drawBorder ? valueAtIndexOrDefault(gridLines.lineWidth, 0, 0) : 0;
12316
var items = me._gridLineItems || (me._gridLineItems = me._computeGridLineItems(chartArea));
12317
var width, color, i, ilen, item;
12318
12319
for (i = 0, ilen = items.length; i < ilen; ++i) {
12320
item = items[i];
12321
width = item.width;
12322
color = item.color;
12323
12324
if (width && color) {
12325
ctx.save();
12326
ctx.lineWidth = width;
12327
ctx.strokeStyle = color;
12328
if (ctx.setLineDash) {
12329
ctx.setLineDash(item.borderDash);
12330
ctx.lineDashOffset = item.borderDashOffset;
12331
}
12332
12333
ctx.beginPath();
12334
12335
if (gridLines.drawTicks) {
12336
ctx.moveTo(item.tx1, item.ty1);
12337
ctx.lineTo(item.tx2, item.ty2);
12338
}
12339
12340
if (gridLines.drawOnChartArea) {
12341
ctx.moveTo(item.x1, item.y1);
12342
ctx.lineTo(item.x2, item.y2);
12343
}
12344
12345
ctx.stroke();
12346
ctx.restore();
12347
}
12348
}
12349
12350
if (axisWidth) {
12351
// Draw the line at the edge of the axis
12352
var firstLineWidth = axisWidth;
12353
var lastLineWidth = valueAtIndexOrDefault(gridLines.lineWidth, items.ticksLength - 1, 1);
12354
var borderValue = items.borderValue;
12355
var x1, x2, y1, y2;
12356
12357
if (me.isHorizontal()) {
12358
x1 = alignPixel(chart, me.left, firstLineWidth) - firstLineWidth / 2;
12359
x2 = alignPixel(chart, me.right, lastLineWidth) + lastLineWidth / 2;
12360
y1 = y2 = borderValue;
12361
} else {
12362
y1 = alignPixel(chart, me.top, firstLineWidth) - firstLineWidth / 2;
12363
y2 = alignPixel(chart, me.bottom, lastLineWidth) + lastLineWidth / 2;
12364
x1 = x2 = borderValue;
12365
}
12366
12367
ctx.lineWidth = axisWidth;
12368
ctx.strokeStyle = valueAtIndexOrDefault(gridLines.color, 0);
12369
ctx.beginPath();
12370
ctx.moveTo(x1, y1);
12371
ctx.lineTo(x2, y2);
12372
ctx.stroke();
12373
}
12374
},
12375
12376
/**
12377
* @private
12378
*/
12379
_drawLabels: function() {
12380
var me = this;
12381
var optionTicks = me.options.ticks;
12382
12383
if (!optionTicks.display) {
12384
return;
12385
}
12386
12387
var ctx = me.ctx;
12388
var items = me._labelItems || (me._labelItems = me._computeLabelItems());
12389
var i, j, ilen, jlen, item, tickFont, label, y;
12390
12391
for (i = 0, ilen = items.length; i < ilen; ++i) {
12392
item = items[i];
12393
tickFont = item.font;
12394
12395
// Make sure we draw text in the correct color and font
12396
ctx.save();
12397
ctx.translate(item.x, item.y);
12398
ctx.rotate(item.rotation);
12399
ctx.font = tickFont.string;
12400
ctx.fillStyle = tickFont.color;
12401
ctx.textBaseline = 'middle';
12402
ctx.textAlign = item.textAlign;
12403
12404
label = item.label;
12405
y = item.textOffset;
12406
if (isArray(label)) {
12407
for (j = 0, jlen = label.length; j < jlen; ++j) {
12408
// We just make sure the multiline element is a string here..
12409
ctx.fillText('' + label[j], 0, y);
12410
y += tickFont.lineHeight;
12411
}
12412
} else {
12413
ctx.fillText(label, 0, y);
12414
}
12415
ctx.restore();
12416
}
12417
},
12418
12419
/**
12420
* @private
12421
*/
12422
_drawTitle: function() {
12423
var me = this;
12424
var ctx = me.ctx;
12425
var options = me.options;
12426
var scaleLabel = options.scaleLabel;
12427
12428
if (!scaleLabel.display) {
12429
return;
12430
}
12431
12432
var scaleLabelFontColor = valueOrDefault$a(scaleLabel.fontColor, core_defaults.global.defaultFontColor);
12433
var scaleLabelFont = helpers$1.options._parseFont(scaleLabel);
12434
var scaleLabelPadding = helpers$1.options.toPadding(scaleLabel.padding);
12435
var halfLineHeight = scaleLabelFont.lineHeight / 2;
12436
var position = options.position;
12437
var rotation = 0;
12438
var scaleLabelX, scaleLabelY;
12439
12440
if (me.isHorizontal()) {
12441
scaleLabelX = me.left + me.width / 2; // midpoint of the width
12442
scaleLabelY = position === 'bottom'
12443
? me.bottom - halfLineHeight - scaleLabelPadding.bottom
12444
: me.top + halfLineHeight + scaleLabelPadding.top;
12445
} else {
12446
var isLeft = position === 'left';
12447
scaleLabelX = isLeft
12448
? me.left + halfLineHeight + scaleLabelPadding.top
12449
: me.right - halfLineHeight - scaleLabelPadding.top;
12450
scaleLabelY = me.top + me.height / 2;
12451
rotation = isLeft ? -0.5 * Math.PI : 0.5 * Math.PI;
12452
}
12453
12454
ctx.save();
12455
ctx.translate(scaleLabelX, scaleLabelY);
12456
ctx.rotate(rotation);
12457
ctx.textAlign = 'center';
12458
ctx.textBaseline = 'middle';
12459
ctx.fillStyle = scaleLabelFontColor; // render in correct colour
12460
ctx.font = scaleLabelFont.string;
12461
ctx.fillText(scaleLabel.labelString, 0, 0);
12462
ctx.restore();
12463
},
12464
12465
draw: function(chartArea) {
12466
var me = this;
12467
12468
if (!me._isVisible()) {
12469
return;
12470
}
12471
12472
me._drawGrid(chartArea);
12473
me._drawTitle();
12474
me._drawLabels();
12475
},
12476
12477
/**
12478
* @private
12479
*/
12480
_layers: function() {
12481
var me = this;
12482
var opts = me.options;
12483
var tz = opts.ticks && opts.ticks.z || 0;
12484
var gz = opts.gridLines && opts.gridLines.z || 0;
12485
12486
if (!me._isVisible() || tz === gz || me.draw !== me._draw) {
12487
// backward compatibility: draw has been overridden by custom scale
12488
return [{
12489
z: tz,
12490
draw: function() {
12491
me.draw.apply(me, arguments);
12492
}
12493
}];
12494
}
12495
12496
return [{
12497
z: gz,
12498
draw: function() {
12499
me._drawGrid.apply(me, arguments);
12500
me._drawTitle.apply(me, arguments);
12501
}
12502
}, {
12503
z: tz,
12504
draw: function() {
12505
me._drawLabels.apply(me, arguments);
12506
}
12507
}];
12508
},
12509
12510
/**
12511
* @private
12512
*/
12513
_getMatchingVisibleMetas: function(type) {
12514
var me = this;
12515
var isHorizontal = me.isHorizontal();
12516
return me.chart._getSortedVisibleDatasetMetas()
12517
.filter(function(meta) {
12518
return (!type || meta.type === type)
12519
&& (isHorizontal ? meta.xAxisID === me.id : meta.yAxisID === me.id);
12520
});
12521
}
12522
});
12523
12524
Scale.prototype._draw = Scale.prototype.draw;
12525
12526
var core_scale = Scale;
12527
12528
var isNullOrUndef$1 = helpers$1.isNullOrUndef;
12529
12530
var defaultConfig = {
12531
position: 'bottom'
12532
};
12533
12534
var scale_category = core_scale.extend({
12535
determineDataLimits: function() {
12536
var me = this;
12537
var labels = me._getLabels();
12538
var ticksOpts = me.options.ticks;
12539
var min = ticksOpts.min;
12540
var max = ticksOpts.max;
12541
var minIndex = 0;
12542
var maxIndex = labels.length - 1;
12543
var findIndex;
12544
12545
if (min !== undefined) {
12546
// user specified min value
12547
findIndex = labels.indexOf(min);
12548
if (findIndex >= 0) {
12549
minIndex = findIndex;
12550
}
12551
}
12552
12553
if (max !== undefined) {
12554
// user specified max value
12555
findIndex = labels.indexOf(max);
12556
if (findIndex >= 0) {
12557
maxIndex = findIndex;
12558
}
12559
}
12560
12561
me.minIndex = minIndex;
12562
me.maxIndex = maxIndex;
12563
me.min = labels[minIndex];
12564
me.max = labels[maxIndex];
12565
},
12566
12567
buildTicks: function() {
12568
var me = this;
12569
var labels = me._getLabels();
12570
var minIndex = me.minIndex;
12571
var maxIndex = me.maxIndex;
12572
12573
// If we are viewing some subset of labels, slice the original array
12574
me.ticks = (minIndex === 0 && maxIndex === labels.length - 1) ? labels : labels.slice(minIndex, maxIndex + 1);
12575
},
12576
12577
getLabelForIndex: function(index, datasetIndex) {
12578
var me = this;
12579
var chart = me.chart;
12580
12581
if (chart.getDatasetMeta(datasetIndex).controller._getValueScaleId() === me.id) {
12582
return me.getRightValue(chart.data.datasets[datasetIndex].data[index]);
12583
}
12584
12585
return me._getLabels()[index];
12586
},
12587
12588
_configure: function() {
12589
var me = this;
12590
var offset = me.options.offset;
12591
var ticks = me.ticks;
12592
12593
core_scale.prototype._configure.call(me);
12594
12595
if (!me.isHorizontal()) {
12596
// For backward compatibility, vertical category scale reverse is inverted.
12597
me._reversePixels = !me._reversePixels;
12598
}
12599
12600
if (!ticks) {
12601
return;
12602
}
12603
12604
me._startValue = me.minIndex - (offset ? 0.5 : 0);
12605
me._valueRange = Math.max(ticks.length - (offset ? 0 : 1), 1);
12606
},
12607
12608
// Used to get data value locations. Value can either be an index or a numerical value
12609
getPixelForValue: function(value, index, datasetIndex) {
12610
var me = this;
12611
var valueCategory, labels, idx;
12612
12613
if (!isNullOrUndef$1(index) && !isNullOrUndef$1(datasetIndex)) {
12614
value = me.chart.data.datasets[datasetIndex].data[index];
12615
}
12616
12617
// If value is a data object, then index is the index in the data array,
12618
// not the index of the scale. We need to change that.
12619
if (!isNullOrUndef$1(value)) {
12620
valueCategory = me.isHorizontal() ? value.x : value.y;
12621
}
12622
if (valueCategory !== undefined || (value !== undefined && isNaN(index))) {
12623
labels = me._getLabels();
12624
value = helpers$1.valueOrDefault(valueCategory, value);
12625
idx = labels.indexOf(value);
12626
index = idx !== -1 ? idx : index;
12627
if (isNaN(index)) {
12628
index = value;
12629
}
12630
}
12631
return me.getPixelForDecimal((index - me._startValue) / me._valueRange);
12632
},
12633
12634
getPixelForTick: function(index) {
12635
var ticks = this.ticks;
12636
return index < 0 || index > ticks.length - 1
12637
? null
12638
: this.getPixelForValue(ticks[index], index + this.minIndex);
12639
},
12640
12641
getValueForPixel: function(pixel) {
12642
var me = this;
12643
var value = Math.round(me._startValue + me.getDecimalForPixel(pixel) * me._valueRange);
12644
return Math.min(Math.max(value, 0), me.ticks.length - 1);
12645
},
12646
12647
getBasePixel: function() {
12648
return this.bottom;
12649
}
12650
});
12651
12652
// INTERNAL: static default options, registered in src/index.js
12653
var _defaults = defaultConfig;
12654
scale_category._defaults = _defaults;
12655
12656
var noop = helpers$1.noop;
12657
var isNullOrUndef$2 = helpers$1.isNullOrUndef;
12658
12659
/**
12660
* Generate a set of linear ticks
12661
* @param generationOptions the options used to generate the ticks
12662
* @param dataRange the range of the data
12663
* @returns {number[]} array of tick values
12664
*/
12665
function generateTicks(generationOptions, dataRange) {
12666
var ticks = [];
12667
// To get a "nice" value for the tick spacing, we will use the appropriately named
12668
// "nice number" algorithm. See https://stackoverflow.com/questions/8506881/nice-label-algorithm-for-charts-with-minimum-ticks
12669
// for details.
12670
12671
var MIN_SPACING = 1e-14;
12672
var stepSize = generationOptions.stepSize;
12673
var unit = stepSize || 1;
12674
var maxNumSpaces = generationOptions.maxTicks - 1;
12675
var min = generationOptions.min;
12676
var max = generationOptions.max;
12677
var precision = generationOptions.precision;
12678
var rmin = dataRange.min;
12679
var rmax = dataRange.max;
12680
var spacing = helpers$1.niceNum((rmax - rmin) / maxNumSpaces / unit) * unit;
12681
var factor, niceMin, niceMax, numSpaces;
12682
12683
// Beyond MIN_SPACING floating point numbers being to lose precision
12684
// such that we can't do the math necessary to generate ticks
12685
if (spacing < MIN_SPACING && isNullOrUndef$2(min) && isNullOrUndef$2(max)) {
12686
return [rmin, rmax];
12687
}
12688
12689
numSpaces = Math.ceil(rmax / spacing) - Math.floor(rmin / spacing);
12690
if (numSpaces > maxNumSpaces) {
12691
// If the calculated num of spaces exceeds maxNumSpaces, recalculate it
12692
spacing = helpers$1.niceNum(numSpaces * spacing / maxNumSpaces / unit) * unit;
12693
}
12694
12695
if (stepSize || isNullOrUndef$2(precision)) {
12696
// If a precision is not specified, calculate factor based on spacing
12697
factor = Math.pow(10, helpers$1._decimalPlaces(spacing));
12698
} else {
12699
// If the user specified a precision, round to that number of decimal places
12700
factor = Math.pow(10, precision);
12701
spacing = Math.ceil(spacing * factor) / factor;
12702
}
12703
12704
niceMin = Math.floor(rmin / spacing) * spacing;
12705
niceMax = Math.ceil(rmax / spacing) * spacing;
12706
12707
// If min, max and stepSize is set and they make an evenly spaced scale use it.
12708
if (stepSize) {
12709
// If very close to our whole number, use it.
12710
if (!isNullOrUndef$2(min) && helpers$1.almostWhole(min / spacing, spacing / 1000)) {
12711
niceMin = min;
12712
}
12713
if (!isNullOrUndef$2(max) && helpers$1.almostWhole(max / spacing, spacing / 1000)) {
12714
niceMax = max;
12715
}
12716
}
12717
12718
numSpaces = (niceMax - niceMin) / spacing;
12719
// If very close to our rounded value, use it.
12720
if (helpers$1.almostEquals(numSpaces, Math.round(numSpaces), spacing / 1000)) {
12721
numSpaces = Math.round(numSpaces);
12722
} else {
12723
numSpaces = Math.ceil(numSpaces);
12724
}
12725
12726
niceMin = Math.round(niceMin * factor) / factor;
12727
niceMax = Math.round(niceMax * factor) / factor;
12728
ticks.push(isNullOrUndef$2(min) ? niceMin : min);
12729
for (var j = 1; j < numSpaces; ++j) {
12730
ticks.push(Math.round((niceMin + j * spacing) * factor) / factor);
12731
}
12732
ticks.push(isNullOrUndef$2(max) ? niceMax : max);
12733
12734
return ticks;
12735
}
12736
12737
var scale_linearbase = core_scale.extend({
12738
getRightValue: function(value) {
12739
if (typeof value === 'string') {
12740
return +value;
12741
}
12742
return core_scale.prototype.getRightValue.call(this, value);
12743
},
12744
12745
handleTickRangeOptions: function() {
12746
var me = this;
12747
var opts = me.options;
12748
var tickOpts = opts.ticks;
12749
12750
// If we are forcing it to begin at 0, but 0 will already be rendered on the chart,
12751
// do nothing since that would make the chart weird. If the user really wants a weird chart
12752
// axis, they can manually override it
12753
if (tickOpts.beginAtZero) {
12754
var minSign = helpers$1.sign(me.min);
12755
var maxSign = helpers$1.sign(me.max);
12756
12757
if (minSign < 0 && maxSign < 0) {
12758
// move the top up to 0
12759
me.max = 0;
12760
} else if (minSign > 0 && maxSign > 0) {
12761
// move the bottom down to 0
12762
me.min = 0;
12763
}
12764
}
12765
12766
var setMin = tickOpts.min !== undefined || tickOpts.suggestedMin !== undefined;
12767
var setMax = tickOpts.max !== undefined || tickOpts.suggestedMax !== undefined;
12768
12769
if (tickOpts.min !== undefined) {
12770
me.min = tickOpts.min;
12771
} else if (tickOpts.suggestedMin !== undefined) {
12772
if (me.min === null) {
12773
me.min = tickOpts.suggestedMin;
12774
} else {
12775
me.min = Math.min(me.min, tickOpts.suggestedMin);
12776
}
12777
}
12778
12779
if (tickOpts.max !== undefined) {
12780
me.max = tickOpts.max;
12781
} else if (tickOpts.suggestedMax !== undefined) {
12782
if (me.max === null) {
12783
me.max = tickOpts.suggestedMax;
12784
} else {
12785
me.max = Math.max(me.max, tickOpts.suggestedMax);
12786
}
12787
}
12788
12789
if (setMin !== setMax) {
12790
// We set the min or the max but not both.
12791
// So ensure that our range is good
12792
// Inverted or 0 length range can happen when
12793
// ticks.min is set, and no datasets are visible
12794
if (me.min >= me.max) {
12795
if (setMin) {
12796
me.max = me.min + 1;
12797
} else {
12798
me.min = me.max - 1;
12799
}
12800
}
12801
}
12802
12803
if (me.min === me.max) {
12804
me.max++;
12805
12806
if (!tickOpts.beginAtZero) {
12807
me.min--;
12808
}
12809
}
12810
},
12811
12812
getTickLimit: function() {
12813
var me = this;
12814
var tickOpts = me.options.ticks;
12815
var stepSize = tickOpts.stepSize;
12816
var maxTicksLimit = tickOpts.maxTicksLimit;
12817
var maxTicks;
12818
12819
if (stepSize) {
12820
maxTicks = Math.ceil(me.max / stepSize) - Math.floor(me.min / stepSize) + 1;
12821
} else {
12822
maxTicks = me._computeTickLimit();
12823
maxTicksLimit = maxTicksLimit || 11;
12824
}
12825
12826
if (maxTicksLimit) {
12827
maxTicks = Math.min(maxTicksLimit, maxTicks);
12828
}
12829
12830
return maxTicks;
12831
},
12832
12833
_computeTickLimit: function() {
12834
return Number.POSITIVE_INFINITY;
12835
},
12836
12837
handleDirectionalChanges: noop,
12838
12839
buildTicks: function() {
12840
var me = this;
12841
var opts = me.options;
12842
var tickOpts = opts.ticks;
12843
12844
// Figure out what the max number of ticks we can support it is based on the size of
12845
// the axis area. For now, we say that the minimum tick spacing in pixels must be 40
12846
// We also limit the maximum number of ticks to 11 which gives a nice 10 squares on
12847
// the graph. Make sure we always have at least 2 ticks
12848
var maxTicks = me.getTickLimit();
12849
maxTicks = Math.max(2, maxTicks);
12850
12851
var numericGeneratorOptions = {
12852
maxTicks: maxTicks,
12853
min: tickOpts.min,
12854
max: tickOpts.max,
12855
precision: tickOpts.precision,
12856
stepSize: helpers$1.valueOrDefault(tickOpts.fixedStepSize, tickOpts.stepSize)
12857
};
12858
var ticks = me.ticks = generateTicks(numericGeneratorOptions, me);
12859
12860
me.handleDirectionalChanges();
12861
12862
// At this point, we need to update our max and min given the tick values since we have expanded the
12863
// range of the scale
12864
me.max = helpers$1.max(ticks);
12865
me.min = helpers$1.min(ticks);
12866
12867
if (tickOpts.reverse) {
12868
ticks.reverse();
12869
12870
me.start = me.max;
12871
me.end = me.min;
12872
} else {
12873
me.start = me.min;
12874
me.end = me.max;
12875
}
12876
},
12877
12878
convertTicksToLabels: function() {
12879
var me = this;
12880
me.ticksAsNumbers = me.ticks.slice();
12881
me.zeroLineIndex = me.ticks.indexOf(0);
12882
12883
core_scale.prototype.convertTicksToLabels.call(me);
12884
},
12885
12886
_configure: function() {
12887
var me = this;
12888
var ticks = me.getTicks();
12889
var start = me.min;
12890
var end = me.max;
12891
var offset;
12892
12893
core_scale.prototype._configure.call(me);
12894
12895
if (me.options.offset && ticks.length) {
12896
offset = (end - start) / Math.max(ticks.length - 1, 1) / 2;
12897
start -= offset;
12898
end += offset;
12899
}
12900
me._startValue = start;
12901
me._endValue = end;
12902
me._valueRange = end - start;
12903
}
12904
});
12905
12906
var defaultConfig$1 = {
12907
position: 'left',
12908
ticks: {
12909
callback: core_ticks.formatters.linear
12910
}
12911
};
12912
12913
var DEFAULT_MIN = 0;
12914
var DEFAULT_MAX = 1;
12915
12916
function getOrCreateStack(stacks, stacked, meta) {
12917
var key = [
12918
meta.type,
12919
// we have a separate stack for stack=undefined datasets when the opts.stacked is undefined
12920
stacked === undefined && meta.stack === undefined ? meta.index : '',
12921
meta.stack
12922
].join('.');
12923
12924
if (stacks[key] === undefined) {
12925
stacks[key] = {
12926
pos: [],
12927
neg: []
12928
};
12929
}
12930
12931
return stacks[key];
12932
}
12933
12934
function stackData(scale, stacks, meta, data) {
12935
var opts = scale.options;
12936
var stacked = opts.stacked;
12937
var stack = getOrCreateStack(stacks, stacked, meta);
12938
var pos = stack.pos;
12939
var neg = stack.neg;
12940
var ilen = data.length;
12941
var i, value;
12942
12943
for (i = 0; i < ilen; ++i) {
12944
value = scale._parseValue(data[i]);
12945
if (isNaN(value.min) || isNaN(value.max) || meta.data[i].hidden) {
12946
continue;
12947
}
12948
12949
pos[i] = pos[i] || 0;
12950
neg[i] = neg[i] || 0;
12951
12952
if (opts.relativePoints) {
12953
pos[i] = 100;
12954
} else if (value.min < 0 || value.max < 0) {
12955
neg[i] += value.min;
12956
} else {
12957
pos[i] += value.max;
12958
}
12959
}
12960
}
12961
12962
function updateMinMax(scale, meta, data) {
12963
var ilen = data.length;
12964
var i, value;
12965
12966
for (i = 0; i < ilen; ++i) {
12967
value = scale._parseValue(data[i]);
12968
if (isNaN(value.min) || isNaN(value.max) || meta.data[i].hidden) {
12969
continue;
12970
}
12971
12972
scale.min = Math.min(scale.min, value.min);
12973
scale.max = Math.max(scale.max, value.max);
12974
}
12975
}
12976
12977
var scale_linear = scale_linearbase.extend({
12978
determineDataLimits: function() {
12979
var me = this;
12980
var opts = me.options;
12981
var chart = me.chart;
12982
var datasets = chart.data.datasets;
12983
var metasets = me._getMatchingVisibleMetas();
12984
var hasStacks = opts.stacked;
12985
var stacks = {};
12986
var ilen = metasets.length;
12987
var i, meta, data, values;
12988
12989
me.min = Number.POSITIVE_INFINITY;
12990
me.max = Number.NEGATIVE_INFINITY;
12991
12992
if (hasStacks === undefined) {
12993
for (i = 0; !hasStacks && i < ilen; ++i) {
12994
meta = metasets[i];
12995
hasStacks = meta.stack !== undefined;
12996
}
12997
}
12998
12999
for (i = 0; i < ilen; ++i) {
13000
meta = metasets[i];
13001
data = datasets[meta.index].data;
13002
if (hasStacks) {
13003
stackData(me, stacks, meta, data);
13004
} else {
13005
updateMinMax(me, meta, data);
13006
}
13007
}
13008
13009
helpers$1.each(stacks, function(stackValues) {
13010
values = stackValues.pos.concat(stackValues.neg);
13011
me.min = Math.min(me.min, helpers$1.min(values));
13012
me.max = Math.max(me.max, helpers$1.max(values));
13013
});
13014
13015
me.min = helpers$1.isFinite(me.min) && !isNaN(me.min) ? me.min : DEFAULT_MIN;
13016
me.max = helpers$1.isFinite(me.max) && !isNaN(me.max) ? me.max : DEFAULT_MAX;
13017
13018
// Common base implementation to handle ticks.min, ticks.max, ticks.beginAtZero
13019
me.handleTickRangeOptions();
13020
},
13021
13022
// Returns the maximum number of ticks based on the scale dimension
13023
_computeTickLimit: function() {
13024
var me = this;
13025
var tickFont;
13026
13027
if (me.isHorizontal()) {
13028
return Math.ceil(me.width / 40);
13029
}
13030
tickFont = helpers$1.options._parseFont(me.options.ticks);
13031
return Math.ceil(me.height / tickFont.lineHeight);
13032
},
13033
13034
// Called after the ticks are built. We need
13035
handleDirectionalChanges: function() {
13036
if (!this.isHorizontal()) {
13037
// We are in a vertical orientation. The top value is the highest. So reverse the array
13038
this.ticks.reverse();
13039
}
13040
},
13041
13042
getLabelForIndex: function(index, datasetIndex) {
13043
return this._getScaleLabel(this.chart.data.datasets[datasetIndex].data[index]);
13044
},
13045
13046
// Utils
13047
getPixelForValue: function(value) {
13048
var me = this;
13049
return me.getPixelForDecimal((+me.getRightValue(value) - me._startValue) / me._valueRange);
13050
},
13051
13052
getValueForPixel: function(pixel) {
13053
return this._startValue + this.getDecimalForPixel(pixel) * this._valueRange;
13054
},
13055
13056
getPixelForTick: function(index) {
13057
var ticks = this.ticksAsNumbers;
13058
if (index < 0 || index > ticks.length - 1) {
13059
return null;
13060
}
13061
return this.getPixelForValue(ticks[index]);
13062
}
13063
});
13064
13065
// INTERNAL: static default options, registered in src/index.js
13066
var _defaults$1 = defaultConfig$1;
13067
scale_linear._defaults = _defaults$1;
13068
13069
var valueOrDefault$b = helpers$1.valueOrDefault;
13070
var log10 = helpers$1.math.log10;
13071
13072
/**
13073
* Generate a set of logarithmic ticks
13074
* @param generationOptions the options used to generate the ticks
13075
* @param dataRange the range of the data
13076
* @returns {number[]} array of tick values
13077
*/
13078
function generateTicks$1(generationOptions, dataRange) {
13079
var ticks = [];
13080
13081
var tickVal = valueOrDefault$b(generationOptions.min, Math.pow(10, Math.floor(log10(dataRange.min))));
13082
13083
var endExp = Math.floor(log10(dataRange.max));
13084
var endSignificand = Math.ceil(dataRange.max / Math.pow(10, endExp));
13085
var exp, significand;
13086
13087
if (tickVal === 0) {
13088
exp = Math.floor(log10(dataRange.minNotZero));
13089
significand = Math.floor(dataRange.minNotZero / Math.pow(10, exp));
13090
13091
ticks.push(tickVal);
13092
tickVal = significand * Math.pow(10, exp);
13093
} else {
13094
exp = Math.floor(log10(tickVal));
13095
significand = Math.floor(tickVal / Math.pow(10, exp));
13096
}
13097
var precision = exp < 0 ? Math.pow(10, Math.abs(exp)) : 1;
13098
13099
do {
13100
ticks.push(tickVal);
13101
13102
++significand;
13103
if (significand === 10) {
13104
significand = 1;
13105
++exp;
13106
precision = exp >= 0 ? 1 : precision;
13107
}
13108
13109
tickVal = Math.round(significand * Math.pow(10, exp) * precision) / precision;
13110
} while (exp < endExp || (exp === endExp && significand < endSignificand));
13111
13112
var lastTick = valueOrDefault$b(generationOptions.max, tickVal);
13113
ticks.push(lastTick);
13114
13115
return ticks;
13116
}
13117
13118
var defaultConfig$2 = {
13119
position: 'left',
13120
13121
// label settings
13122
ticks: {
13123
callback: core_ticks.formatters.logarithmic
13124
}
13125
};
13126
13127
// TODO(v3): change this to positiveOrDefault
13128
function nonNegativeOrDefault(value, defaultValue) {
13129
return helpers$1.isFinite(value) && value >= 0 ? value : defaultValue;
13130
}
13131
13132
var scale_logarithmic = core_scale.extend({
13133
determineDataLimits: function() {
13134
var me = this;
13135
var opts = me.options;
13136
var chart = me.chart;
13137
var datasets = chart.data.datasets;
13138
var isHorizontal = me.isHorizontal();
13139
function IDMatches(meta) {
13140
return isHorizontal ? meta.xAxisID === me.id : meta.yAxisID === me.id;
13141
}
13142
var datasetIndex, meta, value, data, i, ilen;
13143
13144
// Calculate Range
13145
me.min = Number.POSITIVE_INFINITY;
13146
me.max = Number.NEGATIVE_INFINITY;
13147
me.minNotZero = Number.POSITIVE_INFINITY;
13148
13149
var hasStacks = opts.stacked;
13150
if (hasStacks === undefined) {
13151
for (datasetIndex = 0; datasetIndex < datasets.length; datasetIndex++) {
13152
meta = chart.getDatasetMeta(datasetIndex);
13153
if (chart.isDatasetVisible(datasetIndex) && IDMatches(meta) &&
13154
meta.stack !== undefined) {
13155
hasStacks = true;
13156
break;
13157
}
13158
}
13159
}
13160
13161
if (opts.stacked || hasStacks) {
13162
var valuesPerStack = {};
13163
13164
for (datasetIndex = 0; datasetIndex < datasets.length; datasetIndex++) {
13165
meta = chart.getDatasetMeta(datasetIndex);
13166
var key = [
13167
meta.type,
13168
// we have a separate stack for stack=undefined datasets when the opts.stacked is undefined
13169
((opts.stacked === undefined && meta.stack === undefined) ? datasetIndex : ''),
13170
meta.stack
13171
].join('.');
13172
13173
if (chart.isDatasetVisible(datasetIndex) && IDMatches(meta)) {
13174
if (valuesPerStack[key] === undefined) {
13175
valuesPerStack[key] = [];
13176
}
13177
13178
data = datasets[datasetIndex].data;
13179
for (i = 0, ilen = data.length; i < ilen; i++) {
13180
var values = valuesPerStack[key];
13181
value = me._parseValue(data[i]);
13182
// invalid, hidden and negative values are ignored
13183
if (isNaN(value.min) || isNaN(value.max) || meta.data[i].hidden || value.min < 0 || value.max < 0) {
13184
continue;
13185
}
13186
values[i] = values[i] || 0;
13187
values[i] += value.max;
13188
}
13189
}
13190
}
13191
13192
helpers$1.each(valuesPerStack, function(valuesForType) {
13193
if (valuesForType.length > 0) {
13194
var minVal = helpers$1.min(valuesForType);
13195
var maxVal = helpers$1.max(valuesForType);
13196
me.min = Math.min(me.min, minVal);
13197
me.max = Math.max(me.max, maxVal);
13198
}
13199
});
13200
13201
} else {
13202
for (datasetIndex = 0; datasetIndex < datasets.length; datasetIndex++) {
13203
meta = chart.getDatasetMeta(datasetIndex);
13204
if (chart.isDatasetVisible(datasetIndex) && IDMatches(meta)) {
13205
data = datasets[datasetIndex].data;
13206
for (i = 0, ilen = data.length; i < ilen; i++) {
13207
value = me._parseValue(data[i]);
13208
// invalid, hidden and negative values are ignored
13209
if (isNaN(value.min) || isNaN(value.max) || meta.data[i].hidden || value.min < 0 || value.max < 0) {
13210
continue;
13211
}
13212
13213
me.min = Math.min(value.min, me.min);
13214
me.max = Math.max(value.max, me.max);
13215
13216
if (value.min !== 0) {
13217
me.minNotZero = Math.min(value.min, me.minNotZero);
13218
}
13219
}
13220
}
13221
}
13222
}
13223
13224
me.min = helpers$1.isFinite(me.min) ? me.min : null;
13225
me.max = helpers$1.isFinite(me.max) ? me.max : null;
13226
me.minNotZero = helpers$1.isFinite(me.minNotZero) ? me.minNotZero : null;
13227
13228
// Common base implementation to handle ticks.min, ticks.max
13229
this.handleTickRangeOptions();
13230
},
13231
13232
handleTickRangeOptions: function() {
13233
var me = this;
13234
var tickOpts = me.options.ticks;
13235
var DEFAULT_MIN = 1;
13236
var DEFAULT_MAX = 10;
13237
13238
me.min = nonNegativeOrDefault(tickOpts.min, me.min);
13239
me.max = nonNegativeOrDefault(tickOpts.max, me.max);
13240
13241
if (me.min === me.max) {
13242
if (me.min !== 0 && me.min !== null) {
13243
me.min = Math.pow(10, Math.floor(log10(me.min)) - 1);
13244
me.max = Math.pow(10, Math.floor(log10(me.max)) + 1);
13245
} else {
13246
me.min = DEFAULT_MIN;
13247
me.max = DEFAULT_MAX;
13248
}
13249
}
13250
if (me.min === null) {
13251
me.min = Math.pow(10, Math.floor(log10(me.max)) - 1);
13252
}
13253
if (me.max === null) {
13254
me.max = me.min !== 0
13255
? Math.pow(10, Math.floor(log10(me.min)) + 1)
13256
: DEFAULT_MAX;
13257
}
13258
if (me.minNotZero === null) {
13259
if (me.min > 0) {
13260
me.minNotZero = me.min;
13261
} else if (me.max < 1) {
13262
me.minNotZero = Math.pow(10, Math.floor(log10(me.max)));
13263
} else {
13264
me.minNotZero = DEFAULT_MIN;
13265
}
13266
}
13267
},
13268
13269
buildTicks: function() {
13270
var me = this;
13271
var tickOpts = me.options.ticks;
13272
var reverse = !me.isHorizontal();
13273
13274
var generationOptions = {
13275
min: nonNegativeOrDefault(tickOpts.min),
13276
max: nonNegativeOrDefault(tickOpts.max)
13277
};
13278
var ticks = me.ticks = generateTicks$1(generationOptions, me);
13279
13280
// At this point, we need to update our max and min given the tick values since we have expanded the
13281
// range of the scale
13282
me.max = helpers$1.max(ticks);
13283
me.min = helpers$1.min(ticks);
13284
13285
if (tickOpts.reverse) {
13286
reverse = !reverse;
13287
me.start = me.max;
13288
me.end = me.min;
13289
} else {
13290
me.start = me.min;
13291
me.end = me.max;
13292
}
13293
if (reverse) {
13294
ticks.reverse();
13295
}
13296
},
13297
13298
convertTicksToLabels: function() {
13299
this.tickValues = this.ticks.slice();
13300
13301
core_scale.prototype.convertTicksToLabels.call(this);
13302
},
13303
13304
// Get the correct tooltip label
13305
getLabelForIndex: function(index, datasetIndex) {
13306
return this._getScaleLabel(this.chart.data.datasets[datasetIndex].data[index]);
13307
},
13308
13309
getPixelForTick: function(index) {
13310
var ticks = this.tickValues;
13311
if (index < 0 || index > ticks.length - 1) {
13312
return null;
13313
}
13314
return this.getPixelForValue(ticks[index]);
13315
},
13316
13317
/**
13318
* Returns the value of the first tick.
13319
* @param {number} value - The minimum not zero value.
13320
* @return {number} The first tick value.
13321
* @private
13322
*/
13323
_getFirstTickValue: function(value) {
13324
var exp = Math.floor(log10(value));
13325
var significand = Math.floor(value / Math.pow(10, exp));
13326
13327
return significand * Math.pow(10, exp);
13328
},
13329
13330
_configure: function() {
13331
var me = this;
13332
var start = me.min;
13333
var offset = 0;
13334
13335
core_scale.prototype._configure.call(me);
13336
13337
if (start === 0) {
13338
start = me._getFirstTickValue(me.minNotZero);
13339
offset = valueOrDefault$b(me.options.ticks.fontSize, core_defaults.global.defaultFontSize) / me._length;
13340
}
13341
13342
me._startValue = log10(start);
13343
me._valueOffset = offset;
13344
me._valueRange = (log10(me.max) - log10(start)) / (1 - offset);
13345
},
13346
13347
getPixelForValue: function(value) {
13348
var me = this;
13349
var decimal = 0;
13350
13351
value = +me.getRightValue(value);
13352
13353
if (value > me.min && value > 0) {
13354
decimal = (log10(value) - me._startValue) / me._valueRange + me._valueOffset;
13355
}
13356
return me.getPixelForDecimal(decimal);
13357
},
13358
13359
getValueForPixel: function(pixel) {
13360
var me = this;
13361
var decimal = me.getDecimalForPixel(pixel);
13362
return decimal === 0 && me.min === 0
13363
? 0
13364
: Math.pow(10, me._startValue + (decimal - me._valueOffset) * me._valueRange);
13365
}
13366
});
13367
13368
// INTERNAL: static default options, registered in src/index.js
13369
var _defaults$2 = defaultConfig$2;
13370
scale_logarithmic._defaults = _defaults$2;
13371
13372
var valueOrDefault$c = helpers$1.valueOrDefault;
13373
var valueAtIndexOrDefault$1 = helpers$1.valueAtIndexOrDefault;
13374
var resolve$4 = helpers$1.options.resolve;
13375
13376
var defaultConfig$3 = {
13377
display: true,
13378
13379
// Boolean - Whether to animate scaling the chart from the centre
13380
animate: true,
13381
position: 'chartArea',
13382
13383
angleLines: {
13384
display: true,
13385
color: 'rgba(0,0,0,0.1)',
13386
lineWidth: 1,
13387
borderDash: [],
13388
borderDashOffset: 0.0
13389
},
13390
13391
gridLines: {
13392
circular: false
13393
},
13394
13395
// label settings
13396
ticks: {
13397
// Boolean - Show a backdrop to the scale label
13398
showLabelBackdrop: true,
13399
13400
// String - The colour of the label backdrop
13401
backdropColor: 'rgba(255,255,255,0.75)',
13402
13403
// Number - The backdrop padding above & below the label in pixels
13404
backdropPaddingY: 2,
13405
13406
// Number - The backdrop padding to the side of the label in pixels
13407
backdropPaddingX: 2,
13408
13409
callback: core_ticks.formatters.linear
13410
},
13411
13412
pointLabels: {
13413
// Boolean - if true, show point labels
13414
display: true,
13415
13416
// Number - Point label font size in pixels
13417
fontSize: 10,
13418
13419
// Function - Used to convert point labels
13420
callback: function(label) {
13421
return label;
13422
}
13423
}
13424
};
13425
13426
function getTickBackdropHeight(opts) {
13427
var tickOpts = opts.ticks;
13428
13429
if (tickOpts.display && opts.display) {
13430
return valueOrDefault$c(tickOpts.fontSize, core_defaults.global.defaultFontSize) + tickOpts.backdropPaddingY * 2;
13431
}
13432
return 0;
13433
}
13434
13435
function measureLabelSize(ctx, lineHeight, label) {
13436
if (helpers$1.isArray(label)) {
13437
return {
13438
w: helpers$1.longestText(ctx, ctx.font, label),
13439
h: label.length * lineHeight
13440
};
13441
}
13442
13443
return {
13444
w: ctx.measureText(label).width,
13445
h: lineHeight
13446
};
13447
}
13448
13449
function determineLimits(angle, pos, size, min, max) {
13450
if (angle === min || angle === max) {
13451
return {
13452
start: pos - (size / 2),
13453
end: pos + (size / 2)
13454
};
13455
} else if (angle < min || angle > max) {
13456
return {
13457
start: pos - size,
13458
end: pos
13459
};
13460
}
13461
13462
return {
13463
start: pos,
13464
end: pos + size
13465
};
13466
}
13467
13468
/**
13469
* Helper function to fit a radial linear scale with point labels
13470
*/
13471
function fitWithPointLabels(scale) {
13472
13473
// Right, this is really confusing and there is a lot of maths going on here
13474
// The gist of the problem is here: https://gist.github.com/nnnick/696cc9c55f4b0beb8fe9
13475
//
13476
// Reaction: https://dl.dropboxusercontent.com/u/34601363/toomuchscience.gif
13477
//
13478
// Solution:
13479
//
13480
// We assume the radius of the polygon is half the size of the canvas at first
13481
// at each index we check if the text overlaps.
13482
//
13483
// Where it does, we store that angle and that index.
13484
//
13485
// After finding the largest index and angle we calculate how much we need to remove
13486
// from the shape radius to move the point inwards by that x.
13487
//
13488
// We average the left and right distances to get the maximum shape radius that can fit in the box
13489
// along with labels.
13490
//
13491
// Once we have that, we can find the centre point for the chart, by taking the x text protrusion
13492
// on each side, removing that from the size, halving it and adding the left x protrusion width.
13493
//
13494
// This will mean we have a shape fitted to the canvas, as large as it can be with the labels
13495
// and position it in the most space efficient manner
13496
//
13497
// https://dl.dropboxusercontent.com/u/34601363/yeahscience.gif
13498
13499
var plFont = helpers$1.options._parseFont(scale.options.pointLabels);
13500
13501
// Get maximum radius of the polygon. Either half the height (minus the text width) or half the width.
13502
// Use this to calculate the offset + change. - Make sure L/R protrusion is at least 0 to stop issues with centre points
13503
var furthestLimits = {
13504
l: 0,
13505
r: scale.width,
13506
t: 0,
13507
b: scale.height - scale.paddingTop
13508
};
13509
var furthestAngles = {};
13510
var i, textSize, pointPosition;
13511
13512
scale.ctx.font = plFont.string;
13513
scale._pointLabelSizes = [];
13514
13515
var valueCount = scale.chart.data.labels.length;
13516
for (i = 0; i < valueCount; i++) {
13517
pointPosition = scale.getPointPosition(i, scale.drawingArea + 5);
13518
textSize = measureLabelSize(scale.ctx, plFont.lineHeight, scale.pointLabels[i]);
13519
scale._pointLabelSizes[i] = textSize;
13520
13521
// Add quarter circle to make degree 0 mean top of circle
13522
var angleRadians = scale.getIndexAngle(i);
13523
var angle = helpers$1.toDegrees(angleRadians) % 360;
13524
var hLimits = determineLimits(angle, pointPosition.x, textSize.w, 0, 180);
13525
var vLimits = determineLimits(angle, pointPosition.y, textSize.h, 90, 270);
13526
13527
if (hLimits.start < furthestLimits.l) {
13528
furthestLimits.l = hLimits.start;
13529
furthestAngles.l = angleRadians;
13530
}
13531
13532
if (hLimits.end > furthestLimits.r) {
13533
furthestLimits.r = hLimits.end;
13534
furthestAngles.r = angleRadians;
13535
}
13536
13537
if (vLimits.start < furthestLimits.t) {
13538
furthestLimits.t = vLimits.start;
13539
furthestAngles.t = angleRadians;
13540
}
13541
13542
if (vLimits.end > furthestLimits.b) {
13543
furthestLimits.b = vLimits.end;
13544
furthestAngles.b = angleRadians;
13545
}
13546
}
13547
13548
scale.setReductions(scale.drawingArea, furthestLimits, furthestAngles);
13549
}
13550
13551
function getTextAlignForAngle(angle) {
13552
if (angle === 0 || angle === 180) {
13553
return 'center';
13554
} else if (angle < 180) {
13555
return 'left';
13556
}
13557
13558
return 'right';
13559
}
13560
13561
function fillText(ctx, text, position, lineHeight) {
13562
var y = position.y + lineHeight / 2;
13563
var i, ilen;
13564
13565
if (helpers$1.isArray(text)) {
13566
for (i = 0, ilen = text.length; i < ilen; ++i) {
13567
ctx.fillText(text[i], position.x, y);
13568
y += lineHeight;
13569
}
13570
} else {
13571
ctx.fillText(text, position.x, y);
13572
}
13573
}
13574
13575
function adjustPointPositionForLabelHeight(angle, textSize, position) {
13576
if (angle === 90 || angle === 270) {
13577
position.y -= (textSize.h / 2);
13578
} else if (angle > 270 || angle < 90) {
13579
position.y -= textSize.h;
13580
}
13581
}
13582
13583
function drawPointLabels(scale) {
13584
var ctx = scale.ctx;
13585
var opts = scale.options;
13586
var pointLabelOpts = opts.pointLabels;
13587
var tickBackdropHeight = getTickBackdropHeight(opts);
13588
var outerDistance = scale.getDistanceFromCenterForValue(opts.ticks.reverse ? scale.min : scale.max);
13589
var plFont = helpers$1.options._parseFont(pointLabelOpts);
13590
13591
ctx.save();
13592
13593
ctx.font = plFont.string;
13594
ctx.textBaseline = 'middle';
13595
13596
for (var i = scale.chart.data.labels.length - 1; i >= 0; i--) {
13597
// Extra pixels out for some label spacing
13598
var extra = (i === 0 ? tickBackdropHeight / 2 : 0);
13599
var pointLabelPosition = scale.getPointPosition(i, outerDistance + extra + 5);
13600
13601
// Keep this in loop since we may support array properties here
13602
var pointLabelFontColor = valueAtIndexOrDefault$1(pointLabelOpts.fontColor, i, core_defaults.global.defaultFontColor);
13603
ctx.fillStyle = pointLabelFontColor;
13604
13605
var angleRadians = scale.getIndexAngle(i);
13606
var angle = helpers$1.toDegrees(angleRadians);
13607
ctx.textAlign = getTextAlignForAngle(angle);
13608
adjustPointPositionForLabelHeight(angle, scale._pointLabelSizes[i], pointLabelPosition);
13609
fillText(ctx, scale.pointLabels[i], pointLabelPosition, plFont.lineHeight);
13610
}
13611
ctx.restore();
13612
}
13613
13614
function drawRadiusLine(scale, gridLineOpts, radius, index) {
13615
var ctx = scale.ctx;
13616
var circular = gridLineOpts.circular;
13617
var valueCount = scale.chart.data.labels.length;
13618
var lineColor = valueAtIndexOrDefault$1(gridLineOpts.color, index - 1);
13619
var lineWidth = valueAtIndexOrDefault$1(gridLineOpts.lineWidth, index - 1);
13620
var pointPosition;
13621
13622
if ((!circular && !valueCount) || !lineColor || !lineWidth) {
13623
return;
13624
}
13625
13626
ctx.save();
13627
ctx.strokeStyle = lineColor;
13628
ctx.lineWidth = lineWidth;
13629
if (ctx.setLineDash) {
13630
ctx.setLineDash(gridLineOpts.borderDash || []);
13631
ctx.lineDashOffset = gridLineOpts.borderDashOffset || 0.0;
13632
}
13633
13634
ctx.beginPath();
13635
if (circular) {
13636
// Draw circular arcs between the points
13637
ctx.arc(scale.xCenter, scale.yCenter, radius, 0, Math.PI * 2);
13638
} else {
13639
// Draw straight lines connecting each index
13640
pointPosition = scale.getPointPosition(0, radius);
13641
ctx.moveTo(pointPosition.x, pointPosition.y);
13642
13643
for (var i = 1; i < valueCount; i++) {
13644
pointPosition = scale.getPointPosition(i, radius);
13645
ctx.lineTo(pointPosition.x, pointPosition.y);
13646
}
13647
}
13648
ctx.closePath();
13649
ctx.stroke();
13650
ctx.restore();
13651
}
13652
13653
function numberOrZero(param) {
13654
return helpers$1.isNumber(param) ? param : 0;
13655
}
13656
13657
var scale_radialLinear = scale_linearbase.extend({
13658
setDimensions: function() {
13659
var me = this;
13660
13661
// Set the unconstrained dimension before label rotation
13662
me.width = me.maxWidth;
13663
me.height = me.maxHeight;
13664
me.paddingTop = getTickBackdropHeight(me.options) / 2;
13665
me.xCenter = Math.floor(me.width / 2);
13666
me.yCenter = Math.floor((me.height - me.paddingTop) / 2);
13667
me.drawingArea = Math.min(me.height - me.paddingTop, me.width) / 2;
13668
},
13669
13670
determineDataLimits: function() {
13671
var me = this;
13672
var chart = me.chart;
13673
var min = Number.POSITIVE_INFINITY;
13674
var max = Number.NEGATIVE_INFINITY;
13675
13676
helpers$1.each(chart.data.datasets, function(dataset, datasetIndex) {
13677
if (chart.isDatasetVisible(datasetIndex)) {
13678
var meta = chart.getDatasetMeta(datasetIndex);
13679
13680
helpers$1.each(dataset.data, function(rawValue, index) {
13681
var value = +me.getRightValue(rawValue);
13682
if (isNaN(value) || meta.data[index].hidden) {
13683
return;
13684
}
13685
13686
min = Math.min(value, min);
13687
max = Math.max(value, max);
13688
});
13689
}
13690
});
13691
13692
me.min = (min === Number.POSITIVE_INFINITY ? 0 : min);
13693
me.max = (max === Number.NEGATIVE_INFINITY ? 0 : max);
13694
13695
// Common base implementation to handle ticks.min, ticks.max, ticks.beginAtZero
13696
me.handleTickRangeOptions();
13697
},
13698
13699
// Returns the maximum number of ticks based on the scale dimension
13700
_computeTickLimit: function() {
13701
return Math.ceil(this.drawingArea / getTickBackdropHeight(this.options));
13702
},
13703
13704
convertTicksToLabels: function() {
13705
var me = this;
13706
13707
scale_linearbase.prototype.convertTicksToLabels.call(me);
13708
13709
// Point labels
13710
me.pointLabels = me.chart.data.labels.map(function() {
13711
var label = helpers$1.callback(me.options.pointLabels.callback, arguments, me);
13712
return label || label === 0 ? label : '';
13713
});
13714
},
13715
13716
getLabelForIndex: function(index, datasetIndex) {
13717
return +this.getRightValue(this.chart.data.datasets[datasetIndex].data[index]);
13718
},
13719
13720
fit: function() {
13721
var me = this;
13722
var opts = me.options;
13723
13724
if (opts.display && opts.pointLabels.display) {
13725
fitWithPointLabels(me);
13726
} else {
13727
me.setCenterPoint(0, 0, 0, 0);
13728
}
13729
},
13730
13731
/**
13732
* Set radius reductions and determine new radius and center point
13733
* @private
13734
*/
13735
setReductions: function(largestPossibleRadius, furthestLimits, furthestAngles) {
13736
var me = this;
13737
var radiusReductionLeft = furthestLimits.l / Math.sin(furthestAngles.l);
13738
var radiusReductionRight = Math.max(furthestLimits.r - me.width, 0) / Math.sin(furthestAngles.r);
13739
var radiusReductionTop = -furthestLimits.t / Math.cos(furthestAngles.t);
13740
var radiusReductionBottom = -Math.max(furthestLimits.b - (me.height - me.paddingTop), 0) / Math.cos(furthestAngles.b);
13741
13742
radiusReductionLeft = numberOrZero(radiusReductionLeft);
13743
radiusReductionRight = numberOrZero(radiusReductionRight);
13744
radiusReductionTop = numberOrZero(radiusReductionTop);
13745
radiusReductionBottom = numberOrZero(radiusReductionBottom);
13746
13747
me.drawingArea = Math.min(
13748
Math.floor(largestPossibleRadius - (radiusReductionLeft + radiusReductionRight) / 2),
13749
Math.floor(largestPossibleRadius - (radiusReductionTop + radiusReductionBottom) / 2));
13750
me.setCenterPoint(radiusReductionLeft, radiusReductionRight, radiusReductionTop, radiusReductionBottom);
13751
},
13752
13753
setCenterPoint: function(leftMovement, rightMovement, topMovement, bottomMovement) {
13754
var me = this;
13755
var maxRight = me.width - rightMovement - me.drawingArea;
13756
var maxLeft = leftMovement + me.drawingArea;
13757
var maxTop = topMovement + me.drawingArea;
13758
var maxBottom = (me.height - me.paddingTop) - bottomMovement - me.drawingArea;
13759
13760
me.xCenter = Math.floor(((maxLeft + maxRight) / 2) + me.left);
13761
me.yCenter = Math.floor(((maxTop + maxBottom) / 2) + me.top + me.paddingTop);
13762
},
13763
13764
getIndexAngle: function(index) {
13765
var chart = this.chart;
13766
var angleMultiplier = 360 / chart.data.labels.length;
13767
var options = chart.options || {};
13768
var startAngle = options.startAngle || 0;
13769
13770
// Start from the top instead of right, so remove a quarter of the circle
13771
var angle = (index * angleMultiplier + startAngle) % 360;
13772
13773
return (angle < 0 ? angle + 360 : angle) * Math.PI * 2 / 360;
13774
},
13775
13776
getDistanceFromCenterForValue: function(value) {
13777
var me = this;
13778
13779
if (helpers$1.isNullOrUndef(value)) {
13780
return NaN;
13781
}
13782
13783
// Take into account half font size + the yPadding of the top value
13784
var scalingFactor = me.drawingArea / (me.max - me.min);
13785
if (me.options.ticks.reverse) {
13786
return (me.max - value) * scalingFactor;
13787
}
13788
return (value - me.min) * scalingFactor;
13789
},
13790
13791
getPointPosition: function(index, distanceFromCenter) {
13792
var me = this;
13793
var thisAngle = me.getIndexAngle(index) - (Math.PI / 2);
13794
return {
13795
x: Math.cos(thisAngle) * distanceFromCenter + me.xCenter,
13796
y: Math.sin(thisAngle) * distanceFromCenter + me.yCenter
13797
};
13798
},
13799
13800
getPointPositionForValue: function(index, value) {
13801
return this.getPointPosition(index, this.getDistanceFromCenterForValue(value));
13802
},
13803
13804
getBasePosition: function(index) {
13805
var me = this;
13806
var min = me.min;
13807
var max = me.max;
13808
13809
return me.getPointPositionForValue(index || 0,
13810
me.beginAtZero ? 0 :
13811
min < 0 && max < 0 ? max :
13812
min > 0 && max > 0 ? min :
13813
0);
13814
},
13815
13816
/**
13817
* @private
13818
*/
13819
_drawGrid: function() {
13820
var me = this;
13821
var ctx = me.ctx;
13822
var opts = me.options;
13823
var gridLineOpts = opts.gridLines;
13824
var angleLineOpts = opts.angleLines;
13825
var lineWidth = valueOrDefault$c(angleLineOpts.lineWidth, gridLineOpts.lineWidth);
13826
var lineColor = valueOrDefault$c(angleLineOpts.color, gridLineOpts.color);
13827
var i, offset, position;
13828
13829
if (opts.pointLabels.display) {
13830
drawPointLabels(me);
13831
}
13832
13833
if (gridLineOpts.display) {
13834
helpers$1.each(me.ticks, function(label, index) {
13835
if (index !== 0) {
13836
offset = me.getDistanceFromCenterForValue(me.ticksAsNumbers[index]);
13837
drawRadiusLine(me, gridLineOpts, offset, index);
13838
}
13839
});
13840
}
13841
13842
if (angleLineOpts.display && lineWidth && lineColor) {
13843
ctx.save();
13844
ctx.lineWidth = lineWidth;
13845
ctx.strokeStyle = lineColor;
13846
if (ctx.setLineDash) {
13847
ctx.setLineDash(resolve$4([angleLineOpts.borderDash, gridLineOpts.borderDash, []]));
13848
ctx.lineDashOffset = resolve$4([angleLineOpts.borderDashOffset, gridLineOpts.borderDashOffset, 0.0]);
13849
}
13850
13851
for (i = me.chart.data.labels.length - 1; i >= 0; i--) {
13852
offset = me.getDistanceFromCenterForValue(opts.ticks.reverse ? me.min : me.max);
13853
position = me.getPointPosition(i, offset);
13854
ctx.beginPath();
13855
ctx.moveTo(me.xCenter, me.yCenter);
13856
ctx.lineTo(position.x, position.y);
13857
ctx.stroke();
13858
}
13859
13860
ctx.restore();
13861
}
13862
},
13863
13864
/**
13865
* @private
13866
*/
13867
_drawLabels: function() {
13868
var me = this;
13869
var ctx = me.ctx;
13870
var opts = me.options;
13871
var tickOpts = opts.ticks;
13872
13873
if (!tickOpts.display) {
13874
return;
13875
}
13876
13877
var startAngle = me.getIndexAngle(0);
13878
var tickFont = helpers$1.options._parseFont(tickOpts);
13879
var tickFontColor = valueOrDefault$c(tickOpts.fontColor, core_defaults.global.defaultFontColor);
13880
var offset, width;
13881
13882
ctx.save();
13883
ctx.font = tickFont.string;
13884
ctx.translate(me.xCenter, me.yCenter);
13885
ctx.rotate(startAngle);
13886
ctx.textAlign = 'center';
13887
ctx.textBaseline = 'middle';
13888
13889
helpers$1.each(me.ticks, function(label, index) {
13890
if (index === 0 && !tickOpts.reverse) {
13891
return;
13892
}
13893
13894
offset = me.getDistanceFromCenterForValue(me.ticksAsNumbers[index]);
13895
13896
if (tickOpts.showLabelBackdrop) {
13897
width = ctx.measureText(label).width;
13898
ctx.fillStyle = tickOpts.backdropColor;
13899
13900
ctx.fillRect(
13901
-width / 2 - tickOpts.backdropPaddingX,
13902
-offset - tickFont.size / 2 - tickOpts.backdropPaddingY,
13903
width + tickOpts.backdropPaddingX * 2,
13904
tickFont.size + tickOpts.backdropPaddingY * 2
13905
);
13906
}
13907
13908
ctx.fillStyle = tickFontColor;
13909
ctx.fillText(label, 0, -offset);
13910
});
13911
13912
ctx.restore();
13913
},
13914
13915
/**
13916
* @private
13917
*/
13918
_drawTitle: helpers$1.noop
13919
});
13920
13921
// INTERNAL: static default options, registered in src/index.js
13922
var _defaults$3 = defaultConfig$3;
13923
scale_radialLinear._defaults = _defaults$3;
13924
13925
var deprecated$1 = helpers$1._deprecated;
13926
var resolve$5 = helpers$1.options.resolve;
13927
var valueOrDefault$d = helpers$1.valueOrDefault;
13928
13929
// Integer constants are from the ES6 spec.
13930
var MIN_INTEGER = Number.MIN_SAFE_INTEGER || -9007199254740991;
13931
var MAX_INTEGER = Number.MAX_SAFE_INTEGER || 9007199254740991;
13932
13933
var INTERVALS = {
13934
millisecond: {
13935
common: true,
13936
size: 1,
13937
steps: 1000
13938
},
13939
second: {
13940
common: true,
13941
size: 1000,
13942
steps: 60
13943
},
13944
minute: {
13945
common: true,
13946
size: 60000,
13947
steps: 60
13948
},
13949
hour: {
13950
common: true,
13951
size: 3600000,
13952
steps: 24
13953
},
13954
day: {
13955
common: true,
13956
size: 86400000,
13957
steps: 30
13958
},
13959
week: {
13960
common: false,
13961
size: 604800000,
13962
steps: 4
13963
},
13964
month: {
13965
common: true,
13966
size: 2.628e9,
13967
steps: 12
13968
},
13969
quarter: {
13970
common: false,
13971
size: 7.884e9,
13972
steps: 4
13973
},
13974
year: {
13975
common: true,
13976
size: 3.154e10
13977
}
13978
};
13979
13980
var UNITS = Object.keys(INTERVALS);
13981
13982
function sorter(a, b) {
13983
return a - b;
13984
}
13985
13986
function arrayUnique(items) {
13987
var hash = {};
13988
var out = [];
13989
var i, ilen, item;
13990
13991
for (i = 0, ilen = items.length; i < ilen; ++i) {
13992
item = items[i];
13993
if (!hash[item]) {
13994
hash[item] = true;
13995
out.push(item);
13996
}
13997
}
13998
13999
return out;
14000
}
14001
14002
function getMin(options) {
14003
return helpers$1.valueOrDefault(options.time.min, options.ticks.min);
14004
}
14005
14006
function getMax(options) {
14007
return helpers$1.valueOrDefault(options.time.max, options.ticks.max);
14008
}
14009
14010
/**
14011
* Returns an array of {time, pos} objects used to interpolate a specific `time` or position
14012
* (`pos`) on the scale, by searching entries before and after the requested value. `pos` is
14013
* a decimal between 0 and 1: 0 being the start of the scale (left or top) and 1 the other
14014
* extremity (left + width or top + height). Note that it would be more optimized to directly
14015
* store pre-computed pixels, but the scale dimensions are not guaranteed at the time we need
14016
* to create the lookup table. The table ALWAYS contains at least two items: min and max.
14017
*
14018
* @param {number[]} timestamps - timestamps sorted from lowest to highest.
14019
* @param {string} distribution - If 'linear', timestamps will be spread linearly along the min
14020
* and max range, so basically, the table will contains only two items: {min, 0} and {max, 1}.
14021
* If 'series', timestamps will be positioned at the same distance from each other. In this
14022
* case, only timestamps that break the time linearity are registered, meaning that in the
14023
* best case, all timestamps are linear, the table contains only min and max.
14024
*/
14025
function buildLookupTable(timestamps, min, max, distribution) {
14026
if (distribution === 'linear' || !timestamps.length) {
14027
return [
14028
{time: min, pos: 0},
14029
{time: max, pos: 1}
14030
];
14031
}
14032
14033
var table = [];
14034
var items = [min];
14035
var i, ilen, prev, curr, next;
14036
14037
for (i = 0, ilen = timestamps.length; i < ilen; ++i) {
14038
curr = timestamps[i];
14039
if (curr > min && curr < max) {
14040
items.push(curr);
14041
}
14042
}
14043
14044
items.push(max);
14045
14046
for (i = 0, ilen = items.length; i < ilen; ++i) {
14047
next = items[i + 1];
14048
prev = items[i - 1];
14049
curr = items[i];
14050
14051
// only add points that breaks the scale linearity
14052
if (prev === undefined || next === undefined || Math.round((next + prev) / 2) !== curr) {
14053
table.push({time: curr, pos: i / (ilen - 1)});
14054
}
14055
}
14056
14057
return table;
14058
}
14059
14060
// @see adapted from https://www.anujgakhar.com/2014/03/01/binary-search-in-javascript/
14061
function lookup(table, key, value) {
14062
var lo = 0;
14063
var hi = table.length - 1;
14064
var mid, i0, i1;
14065
14066
while (lo >= 0 && lo <= hi) {
14067
mid = (lo + hi) >> 1;
14068
i0 = table[mid - 1] || null;
14069
i1 = table[mid];
14070
14071
if (!i0) {
14072
// given value is outside table (before first item)
14073
return {lo: null, hi: i1};
14074
} else if (i1[key] < value) {
14075
lo = mid + 1;
14076
} else if (i0[key] > value) {
14077
hi = mid - 1;
14078
} else {
14079
return {lo: i0, hi: i1};
14080
}
14081
}
14082
14083
// given value is outside table (after last item)
14084
return {lo: i1, hi: null};
14085
}
14086
14087
/**
14088
* Linearly interpolates the given source `value` using the table items `skey` values and
14089
* returns the associated `tkey` value. For example, interpolate(table, 'time', 42, 'pos')
14090
* returns the position for a timestamp equal to 42. If value is out of bounds, values at
14091
* index [0, 1] or [n - 1, n] are used for the interpolation.
14092
*/
14093
function interpolate$1(table, skey, sval, tkey) {
14094
var range = lookup(table, skey, sval);
14095
14096
// Note: the lookup table ALWAYS contains at least 2 items (min and max)
14097
var prev = !range.lo ? table[0] : !range.hi ? table[table.length - 2] : range.lo;
14098
var next = !range.lo ? table[1] : !range.hi ? table[table.length - 1] : range.hi;
14099
14100
var span = next[skey] - prev[skey];
14101
var ratio = span ? (sval - prev[skey]) / span : 0;
14102
var offset = (next[tkey] - prev[tkey]) * ratio;
14103
14104
return prev[tkey] + offset;
14105
}
14106
14107
function toTimestamp(scale, input) {
14108
var adapter = scale._adapter;
14109
var options = scale.options.time;
14110
var parser = options.parser;
14111
var format = parser || options.format;
14112
var value = input;
14113
14114
if (typeof parser === 'function') {
14115
value = parser(value);
14116
}
14117
14118
// Only parse if its not a timestamp already
14119
if (!helpers$1.isFinite(value)) {
14120
value = typeof format === 'string'
14121
? adapter.parse(value, format)
14122
: adapter.parse(value);
14123
}
14124
14125
if (value !== null) {
14126
return +value;
14127
}
14128
14129
// Labels are in an incompatible format and no `parser` has been provided.
14130
// The user might still use the deprecated `format` option for parsing.
14131
if (!parser && typeof format === 'function') {
14132
value = format(input);
14133
14134
// `format` could return something else than a timestamp, if so, parse it
14135
if (!helpers$1.isFinite(value)) {
14136
value = adapter.parse(value);
14137
}
14138
}
14139
14140
return value;
14141
}
14142
14143
function parse(scale, input) {
14144
if (helpers$1.isNullOrUndef(input)) {
14145
return null;
14146
}
14147
14148
var options = scale.options.time;
14149
var value = toTimestamp(scale, scale.getRightValue(input));
14150
if (value === null) {
14151
return value;
14152
}
14153
14154
if (options.round) {
14155
value = +scale._adapter.startOf(value, options.round);
14156
}
14157
14158
return value;
14159
}
14160
14161
/**
14162
* Figures out what unit results in an appropriate number of auto-generated ticks
14163
*/
14164
function determineUnitForAutoTicks(minUnit, min, max, capacity) {
14165
var ilen = UNITS.length;
14166
var i, interval, factor;
14167
14168
for (i = UNITS.indexOf(minUnit); i < ilen - 1; ++i) {
14169
interval = INTERVALS[UNITS[i]];
14170
factor = interval.steps ? interval.steps : MAX_INTEGER;
14171
14172
if (interval.common && Math.ceil((max - min) / (factor * interval.size)) <= capacity) {
14173
return UNITS[i];
14174
}
14175
}
14176
14177
return UNITS[ilen - 1];
14178
}
14179
14180
/**
14181
* Figures out what unit to format a set of ticks with
14182
*/
14183
function determineUnitForFormatting(scale, numTicks, minUnit, min, max) {
14184
var i, unit;
14185
14186
for (i = UNITS.length - 1; i >= UNITS.indexOf(minUnit); i--) {
14187
unit = UNITS[i];
14188
if (INTERVALS[unit].common && scale._adapter.diff(max, min, unit) >= numTicks - 1) {
14189
return unit;
14190
}
14191
}
14192
14193
return UNITS[minUnit ? UNITS.indexOf(minUnit) : 0];
14194
}
14195
14196
function determineMajorUnit(unit) {
14197
for (var i = UNITS.indexOf(unit) + 1, ilen = UNITS.length; i < ilen; ++i) {
14198
if (INTERVALS[UNITS[i]].common) {
14199
return UNITS[i];
14200
}
14201
}
14202
}
14203
14204
/**
14205
* Generates a maximum of `capacity` timestamps between min and max, rounded to the
14206
* `minor` unit using the given scale time `options`.
14207
* Important: this method can return ticks outside the min and max range, it's the
14208
* responsibility of the calling code to clamp values if needed.
14209
*/
14210
function generate(scale, min, max, capacity) {
14211
var adapter = scale._adapter;
14212
var options = scale.options;
14213
var timeOpts = options.time;
14214
var minor = timeOpts.unit || determineUnitForAutoTicks(timeOpts.minUnit, min, max, capacity);
14215
var stepSize = resolve$5([timeOpts.stepSize, timeOpts.unitStepSize, 1]);
14216
var weekday = minor === 'week' ? timeOpts.isoWeekday : false;
14217
var first = min;
14218
var ticks = [];
14219
var time;
14220
14221
// For 'week' unit, handle the first day of week option
14222
if (weekday) {
14223
first = +adapter.startOf(first, 'isoWeek', weekday);
14224
}
14225
14226
// Align first ticks on unit
14227
first = +adapter.startOf(first, weekday ? 'day' : minor);
14228
14229
// Prevent browser from freezing in case user options request millions of milliseconds
14230
if (adapter.diff(max, min, minor) > 100000 * stepSize) {
14231
throw min + ' and ' + max + ' are too far apart with stepSize of ' + stepSize + ' ' + minor;
14232
}
14233
14234
for (time = first; time < max; time = +adapter.add(time, stepSize, minor)) {
14235
ticks.push(time);
14236
}
14237
14238
if (time === max || options.bounds === 'ticks') {
14239
ticks.push(time);
14240
}
14241
14242
return ticks;
14243
}
14244
14245
/**
14246
* Returns the start and end offsets from edges in the form of {start, end}
14247
* where each value is a relative width to the scale and ranges between 0 and 1.
14248
* They add extra margins on the both sides by scaling down the original scale.
14249
* Offsets are added when the `offset` option is true.
14250
*/
14251
function computeOffsets(table, ticks, min, max, options) {
14252
var start = 0;
14253
var end = 0;
14254
var first, last;
14255
14256
if (options.offset && ticks.length) {
14257
first = interpolate$1(table, 'time', ticks[0], 'pos');
14258
if (ticks.length === 1) {
14259
start = 1 - first;
14260
} else {
14261
start = (interpolate$1(table, 'time', ticks[1], 'pos') - first) / 2;
14262
}
14263
last = interpolate$1(table, 'time', ticks[ticks.length - 1], 'pos');
14264
if (ticks.length === 1) {
14265
end = last;
14266
} else {
14267
end = (last - interpolate$1(table, 'time', ticks[ticks.length - 2], 'pos')) / 2;
14268
}
14269
}
14270
14271
return {start: start, end: end, factor: 1 / (start + 1 + end)};
14272
}
14273
14274
function setMajorTicks(scale, ticks, map, majorUnit) {
14275
var adapter = scale._adapter;
14276
var first = +adapter.startOf(ticks[0].value, majorUnit);
14277
var last = ticks[ticks.length - 1].value;
14278
var major, index;
14279
14280
for (major = first; major <= last; major = +adapter.add(major, 1, majorUnit)) {
14281
index = map[major];
14282
if (index >= 0) {
14283
ticks[index].major = true;
14284
}
14285
}
14286
return ticks;
14287
}
14288
14289
function ticksFromTimestamps(scale, values, majorUnit) {
14290
var ticks = [];
14291
var map = {};
14292
var ilen = values.length;
14293
var i, value;
14294
14295
for (i = 0; i < ilen; ++i) {
14296
value = values[i];
14297
map[value] = i;
14298
14299
ticks.push({
14300
value: value,
14301
major: false
14302
});
14303
}
14304
14305
// We set the major ticks separately from the above loop because calling startOf for every tick
14306
// is expensive when there is a large number of ticks
14307
return (ilen === 0 || !majorUnit) ? ticks : setMajorTicks(scale, ticks, map, majorUnit);
14308
}
14309
14310
var defaultConfig$4 = {
14311
position: 'bottom',
14312
14313
/**
14314
* Data distribution along the scale:
14315
* - 'linear': data are spread according to their time (distances can vary),
14316
* - 'series': data are spread at the same distance from each other.
14317
* @see https://github.com/chartjs/Chart.js/pull/4507
14318
* @since 2.7.0
14319
*/
14320
distribution: 'linear',
14321
14322
/**
14323
* Scale boundary strategy (bypassed by min/max time options)
14324
* - `data`: make sure data are fully visible, ticks outside are removed
14325
* - `ticks`: make sure ticks are fully visible, data outside are truncated
14326
* @see https://github.com/chartjs/Chart.js/pull/4556
14327
* @since 2.7.0
14328
*/
14329
bounds: 'data',
14330
14331
adapters: {},
14332
time: {
14333
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
14334
unit: false, // false == automatic or override with week, month, year, etc.
14335
round: false, // none, or override with week, month, year, etc.
14336
displayFormat: false, // DEPRECATED
14337
isoWeekday: false, // override week start day - see https://momentjs.com/docs/#/get-set/iso-weekday/
14338
minUnit: 'millisecond',
14339
displayFormats: {}
14340
},
14341
ticks: {
14342
autoSkip: false,
14343
14344
/**
14345
* Ticks generation input values:
14346
* - 'auto': generates "optimal" ticks based on scale size and time options.
14347
* - 'data': generates ticks from data (including labels from data {t|x|y} objects).
14348
* - 'labels': generates ticks from user given `data.labels` values ONLY.
14349
* @see https://github.com/chartjs/Chart.js/pull/4507
14350
* @since 2.7.0
14351
*/
14352
source: 'auto',
14353
14354
major: {
14355
enabled: false
14356
}
14357
}
14358
};
14359
14360
var scale_time = core_scale.extend({
14361
initialize: function() {
14362
this.mergeTicksOptions();
14363
core_scale.prototype.initialize.call(this);
14364
},
14365
14366
update: function() {
14367
var me = this;
14368
var options = me.options;
14369
var time = options.time || (options.time = {});
14370
var adapter = me._adapter = new core_adapters._date(options.adapters.date);
14371
14372
// DEPRECATIONS: output a message only one time per update
14373
deprecated$1('time scale', time.format, 'time.format', 'time.parser');
14374
deprecated$1('time scale', time.min, 'time.min', 'ticks.min');
14375
deprecated$1('time scale', time.max, 'time.max', 'ticks.max');
14376
14377
// Backward compatibility: before introducing adapter, `displayFormats` was
14378
// supposed to contain *all* unit/string pairs but this can't be resolved
14379
// when loading the scale (adapters are loaded afterward), so let's populate
14380
// missing formats on update
14381
helpers$1.mergeIf(time.displayFormats, adapter.formats());
14382
14383
return core_scale.prototype.update.apply(me, arguments);
14384
},
14385
14386
/**
14387
* Allows data to be referenced via 't' attribute
14388
*/
14389
getRightValue: function(rawValue) {
14390
if (rawValue && rawValue.t !== undefined) {
14391
rawValue = rawValue.t;
14392
}
14393
return core_scale.prototype.getRightValue.call(this, rawValue);
14394
},
14395
14396
determineDataLimits: function() {
14397
var me = this;
14398
var chart = me.chart;
14399
var adapter = me._adapter;
14400
var options = me.options;
14401
var unit = options.time.unit || 'day';
14402
var min = MAX_INTEGER;
14403
var max = MIN_INTEGER;
14404
var timestamps = [];
14405
var datasets = [];
14406
var labels = [];
14407
var i, j, ilen, jlen, data, timestamp, labelsAdded;
14408
var dataLabels = me._getLabels();
14409
14410
for (i = 0, ilen = dataLabels.length; i < ilen; ++i) {
14411
labels.push(parse(me, dataLabels[i]));
14412
}
14413
14414
for (i = 0, ilen = (chart.data.datasets || []).length; i < ilen; ++i) {
14415
if (chart.isDatasetVisible(i)) {
14416
data = chart.data.datasets[i].data;
14417
14418
// Let's consider that all data have the same format.
14419
if (helpers$1.isObject(data[0])) {
14420
datasets[i] = [];
14421
14422
for (j = 0, jlen = data.length; j < jlen; ++j) {
14423
timestamp = parse(me, data[j]);
14424
timestamps.push(timestamp);
14425
datasets[i][j] = timestamp;
14426
}
14427
} else {
14428
datasets[i] = labels.slice(0);
14429
if (!labelsAdded) {
14430
timestamps = timestamps.concat(labels);
14431
labelsAdded = true;
14432
}
14433
}
14434
} else {
14435
datasets[i] = [];
14436
}
14437
}
14438
14439
if (labels.length) {
14440
min = Math.min(min, labels[0]);
14441
max = Math.max(max, labels[labels.length - 1]);
14442
}
14443
14444
if (timestamps.length) {
14445
timestamps = ilen > 1 ? arrayUnique(timestamps).sort(sorter) : timestamps.sort(sorter);
14446
min = Math.min(min, timestamps[0]);
14447
max = Math.max(max, timestamps[timestamps.length - 1]);
14448
}
14449
14450
min = parse(me, getMin(options)) || min;
14451
max = parse(me, getMax(options)) || max;
14452
14453
// In case there is no valid min/max, set limits based on unit time option
14454
min = min === MAX_INTEGER ? +adapter.startOf(Date.now(), unit) : min;
14455
max = max === MIN_INTEGER ? +adapter.endOf(Date.now(), unit) + 1 : max;
14456
14457
// Make sure that max is strictly higher than min (required by the lookup table)
14458
me.min = Math.min(min, max);
14459
me.max = Math.max(min + 1, max);
14460
14461
// PRIVATE
14462
me._table = [];
14463
me._timestamps = {
14464
data: timestamps,
14465
datasets: datasets,
14466
labels: labels
14467
};
14468
},
14469
14470
buildTicks: function() {
14471
var me = this;
14472
var min = me.min;
14473
var max = me.max;
14474
var options = me.options;
14475
var tickOpts = options.ticks;
14476
var timeOpts = options.time;
14477
var timestamps = me._timestamps;
14478
var ticks = [];
14479
var capacity = me.getLabelCapacity(min);
14480
var source = tickOpts.source;
14481
var distribution = options.distribution;
14482
var i, ilen, timestamp;
14483
14484
if (source === 'data' || (source === 'auto' && distribution === 'series')) {
14485
timestamps = timestamps.data;
14486
} else if (source === 'labels') {
14487
timestamps = timestamps.labels;
14488
} else {
14489
timestamps = generate(me, min, max, capacity);
14490
}
14491
14492
if (options.bounds === 'ticks' && timestamps.length) {
14493
min = timestamps[0];
14494
max = timestamps[timestamps.length - 1];
14495
}
14496
14497
// Enforce limits with user min/max options
14498
min = parse(me, getMin(options)) || min;
14499
max = parse(me, getMax(options)) || max;
14500
14501
// Remove ticks outside the min/max range
14502
for (i = 0, ilen = timestamps.length; i < ilen; ++i) {
14503
timestamp = timestamps[i];
14504
if (timestamp >= min && timestamp <= max) {
14505
ticks.push(timestamp);
14506
}
14507
}
14508
14509
me.min = min;
14510
me.max = max;
14511
14512
// PRIVATE
14513
// determineUnitForFormatting relies on the number of ticks so we don't use it when
14514
// autoSkip is enabled because we don't yet know what the final number of ticks will be
14515
me._unit = timeOpts.unit || (tickOpts.autoSkip
14516
? determineUnitForAutoTicks(timeOpts.minUnit, me.min, me.max, capacity)
14517
: determineUnitForFormatting(me, ticks.length, timeOpts.minUnit, me.min, me.max));
14518
me._majorUnit = !tickOpts.major.enabled || me._unit === 'year' ? undefined
14519
: determineMajorUnit(me._unit);
14520
me._table = buildLookupTable(me._timestamps.data, min, max, distribution);
14521
me._offsets = computeOffsets(me._table, ticks, min, max, options);
14522
14523
if (tickOpts.reverse) {
14524
ticks.reverse();
14525
}
14526
14527
return ticksFromTimestamps(me, ticks, me._majorUnit);
14528
},
14529
14530
getLabelForIndex: function(index, datasetIndex) {
14531
var me = this;
14532
var adapter = me._adapter;
14533
var data = me.chart.data;
14534
var timeOpts = me.options.time;
14535
var label = data.labels && index < data.labels.length ? data.labels[index] : '';
14536
var value = data.datasets[datasetIndex].data[index];
14537
14538
if (helpers$1.isObject(value)) {
14539
label = me.getRightValue(value);
14540
}
14541
if (timeOpts.tooltipFormat) {
14542
return adapter.format(toTimestamp(me, label), timeOpts.tooltipFormat);
14543
}
14544
if (typeof label === 'string') {
14545
return label;
14546
}
14547
return adapter.format(toTimestamp(me, label), timeOpts.displayFormats.datetime);
14548
},
14549
14550
/**
14551
* Function to format an individual tick mark
14552
* @private
14553
*/
14554
tickFormatFunction: function(time, index, ticks, format) {
14555
var me = this;
14556
var adapter = me._adapter;
14557
var options = me.options;
14558
var formats = options.time.displayFormats;
14559
var minorFormat = formats[me._unit];
14560
var majorUnit = me._majorUnit;
14561
var majorFormat = formats[majorUnit];
14562
var tick = ticks[index];
14563
var tickOpts = options.ticks;
14564
var major = majorUnit && majorFormat && tick && tick.major;
14565
var label = adapter.format(time, format ? format : major ? majorFormat : minorFormat);
14566
var nestedTickOpts = major ? tickOpts.major : tickOpts.minor;
14567
var formatter = resolve$5([
14568
nestedTickOpts.callback,
14569
nestedTickOpts.userCallback,
14570
tickOpts.callback,
14571
tickOpts.userCallback
14572
]);
14573
14574
return formatter ? formatter(label, index, ticks) : label;
14575
},
14576
14577
convertTicksToLabels: function(ticks) {
14578
var labels = [];
14579
var i, ilen;
14580
14581
for (i = 0, ilen = ticks.length; i < ilen; ++i) {
14582
labels.push(this.tickFormatFunction(ticks[i].value, i, ticks));
14583
}
14584
14585
return labels;
14586
},
14587
14588
/**
14589
* @private
14590
*/
14591
getPixelForOffset: function(time) {
14592
var me = this;
14593
var offsets = me._offsets;
14594
var pos = interpolate$1(me._table, 'time', time, 'pos');
14595
return me.getPixelForDecimal((offsets.start + pos) * offsets.factor);
14596
},
14597
14598
getPixelForValue: function(value, index, datasetIndex) {
14599
var me = this;
14600
var time = null;
14601
14602
if (index !== undefined && datasetIndex !== undefined) {
14603
time = me._timestamps.datasets[datasetIndex][index];
14604
}
14605
14606
if (time === null) {
14607
time = parse(me, value);
14608
}
14609
14610
if (time !== null) {
14611
return me.getPixelForOffset(time);
14612
}
14613
},
14614
14615
getPixelForTick: function(index) {
14616
var ticks = this.getTicks();
14617
return index >= 0 && index < ticks.length ?
14618
this.getPixelForOffset(ticks[index].value) :
14619
null;
14620
},
14621
14622
getValueForPixel: function(pixel) {
14623
var me = this;
14624
var offsets = me._offsets;
14625
var pos = me.getDecimalForPixel(pixel) / offsets.factor - offsets.end;
14626
var time = interpolate$1(me._table, 'pos', pos, 'time');
14627
14628
// DEPRECATION, we should return time directly
14629
return me._adapter._create(time);
14630
},
14631
14632
/**
14633
* @private
14634
*/
14635
_getLabelSize: function(label) {
14636
var me = this;
14637
var ticksOpts = me.options.ticks;
14638
var tickLabelWidth = me.ctx.measureText(label).width;
14639
var angle = helpers$1.toRadians(me.isHorizontal() ? ticksOpts.maxRotation : ticksOpts.minRotation);
14640
var cosRotation = Math.cos(angle);
14641
var sinRotation = Math.sin(angle);
14642
var tickFontSize = valueOrDefault$d(ticksOpts.fontSize, core_defaults.global.defaultFontSize);
14643
14644
return {
14645
w: (tickLabelWidth * cosRotation) + (tickFontSize * sinRotation),
14646
h: (tickLabelWidth * sinRotation) + (tickFontSize * cosRotation)
14647
};
14648
},
14649
14650
/**
14651
* Crude approximation of what the label width might be
14652
* @private
14653
*/
14654
getLabelWidth: function(label) {
14655
return this._getLabelSize(label).w;
14656
},
14657
14658
/**
14659
* @private
14660
*/
14661
getLabelCapacity: function(exampleTime) {
14662
var me = this;
14663
var timeOpts = me.options.time;
14664
var displayFormats = timeOpts.displayFormats;
14665
14666
// pick the longest format (milliseconds) for guestimation
14667
var format = displayFormats[timeOpts.unit] || displayFormats.millisecond;
14668
var exampleLabel = me.tickFormatFunction(exampleTime, 0, ticksFromTimestamps(me, [exampleTime], me._majorUnit), format);
14669
var size = me._getLabelSize(exampleLabel);
14670
var capacity = Math.floor(me.isHorizontal() ? me.width / size.w : me.height / size.h);
14671
14672
if (me.options.offset) {
14673
capacity--;
14674
}
14675
14676
return capacity > 0 ? capacity : 1;
14677
}
14678
});
14679
14680
// INTERNAL: static default options, registered in src/index.js
14681
var _defaults$4 = defaultConfig$4;
14682
scale_time._defaults = _defaults$4;
14683
14684
var scales = {
14685
category: scale_category,
14686
linear: scale_linear,
14687
logarithmic: scale_logarithmic,
14688
radialLinear: scale_radialLinear,
14689
time: scale_time
14690
};
14691
14692
var moment = createCommonjsModule(function (module, exports) {
14693
(function (global, factory) {
14694
module.exports = factory() ;
14695
}(commonjsGlobal, (function () {
14696
var hookCallback;
14697
14698
function hooks () {
14699
return hookCallback.apply(null, arguments);
14700
}
14701
14702
// This is done to register the method called with moment()
14703
// without creating circular dependencies.
14704
function setHookCallback (callback) {
14705
hookCallback = callback;
14706
}
14707
14708
function isArray(input) {
14709
return input instanceof Array || Object.prototype.toString.call(input) === '[object Array]';
14710
}
14711
14712
function isObject(input) {
14713
// IE8 will treat undefined and null as object if it wasn't for
14714
// input != null
14715
return input != null && Object.prototype.toString.call(input) === '[object Object]';
14716
}
14717
14718
function isObjectEmpty(obj) {
14719
if (Object.getOwnPropertyNames) {
14720
return (Object.getOwnPropertyNames(obj).length === 0);
14721
} else {
14722
var k;
14723
for (k in obj) {
14724
if (obj.hasOwnProperty(k)) {
14725
return false;
14726
}
14727
}
14728
return true;
14729
}
14730
}
14731
14732
function isUndefined(input) {
14733
return input === void 0;
14734
}
14735
14736
function isNumber(input) {
14737
return typeof input === 'number' || Object.prototype.toString.call(input) === '[object Number]';
14738
}
14739
14740
function isDate(input) {
14741
return input instanceof Date || Object.prototype.toString.call(input) === '[object Date]';
14742
}
14743
14744
function map(arr, fn) {
14745
var res = [], i;
14746
for (i = 0; i < arr.length; ++i) {
14747
res.push(fn(arr[i], i));
14748
}
14749
return res;
14750
}
14751
14752
function hasOwnProp(a, b) {
14753
return Object.prototype.hasOwnProperty.call(a, b);
14754
}
14755
14756
function extend(a, b) {
14757
for (var i in b) {
14758
if (hasOwnProp(b, i)) {
14759
a[i] = b[i];
14760
}
14761
}
14762
14763
if (hasOwnProp(b, 'toString')) {
14764
a.toString = b.toString;
14765
}
14766
14767
if (hasOwnProp(b, 'valueOf')) {
14768
a.valueOf = b.valueOf;
14769
}
14770
14771
return a;
14772
}
14773
14774
function createUTC (input, format, locale, strict) {
14775
return createLocalOrUTC(input, format, locale, strict, true).utc();
14776
}
14777
14778
function defaultParsingFlags() {
14779
// We need to deep clone this object.
14780
return {
14781
empty : false,
14782
unusedTokens : [],
14783
unusedInput : [],
14784
overflow : -2,
14785
charsLeftOver : 0,
14786
nullInput : false,
14787
invalidMonth : null,
14788
invalidFormat : false,
14789
userInvalidated : false,
14790
iso : false,
14791
parsedDateParts : [],
14792
meridiem : null,
14793
rfc2822 : false,
14794
weekdayMismatch : false
14795
};
14796
}
14797
14798
function getParsingFlags(m) {
14799
if (m._pf == null) {
14800
m._pf = defaultParsingFlags();
14801
}
14802
return m._pf;
14803
}
14804
14805
var some;
14806
if (Array.prototype.some) {
14807
some = Array.prototype.some;
14808
} else {
14809
some = function (fun) {
14810
var t = Object(this);
14811
var len = t.length >>> 0;
14812
14813
for (var i = 0; i < len; i++) {
14814
if (i in t && fun.call(this, t[i], i, t)) {
14815
return true;
14816
}
14817
}
14818
14819
return false;
14820
};
14821
}
14822
14823
function isValid(m) {
14824
if (m._isValid == null) {
14825
var flags = getParsingFlags(m);
14826
var parsedParts = some.call(flags.parsedDateParts, function (i) {
14827
return i != null;
14828
});
14829
var isNowValid = !isNaN(m._d.getTime()) &&
14830
flags.overflow < 0 &&
14831
!flags.empty &&
14832
!flags.invalidMonth &&
14833
!flags.invalidWeekday &&
14834
!flags.weekdayMismatch &&
14835
!flags.nullInput &&
14836
!flags.invalidFormat &&
14837
!flags.userInvalidated &&
14838
(!flags.meridiem || (flags.meridiem && parsedParts));
14839
14840
if (m._strict) {
14841
isNowValid = isNowValid &&
14842
flags.charsLeftOver === 0 &&
14843
flags.unusedTokens.length === 0 &&
14844
flags.bigHour === undefined;
14845
}
14846
14847
if (Object.isFrozen == null || !Object.isFrozen(m)) {
14848
m._isValid = isNowValid;
14849
}
14850
else {
14851
return isNowValid;
14852
}
14853
}
14854
return m._isValid;
14855
}
14856
14857
function createInvalid (flags) {
14858
var m = createUTC(NaN);
14859
if (flags != null) {
14860
extend(getParsingFlags(m), flags);
14861
}
14862
else {
14863
getParsingFlags(m).userInvalidated = true;
14864
}
14865
14866
return m;
14867
}
14868
14869
// Plugins that add properties should also add the key here (null value),
14870
// so we can properly clone ourselves.
14871
var momentProperties = hooks.momentProperties = [];
14872
14873
function copyConfig(to, from) {
14874
var i, prop, val;
14875
14876
if (!isUndefined(from._isAMomentObject)) {
14877
to._isAMomentObject = from._isAMomentObject;
14878
}
14879
if (!isUndefined(from._i)) {
14880
to._i = from._i;
14881
}
14882
if (!isUndefined(from._f)) {
14883
to._f = from._f;
14884
}
14885
if (!isUndefined(from._l)) {
14886
to._l = from._l;
14887
}
14888
if (!isUndefined(from._strict)) {
14889
to._strict = from._strict;
14890
}
14891
if (!isUndefined(from._tzm)) {
14892
to._tzm = from._tzm;
14893
}
14894
if (!isUndefined(from._isUTC)) {
14895
to._isUTC = from._isUTC;
14896
}
14897
if (!isUndefined(from._offset)) {
14898
to._offset = from._offset;
14899
}
14900
if (!isUndefined(from._pf)) {
14901
to._pf = getParsingFlags(from);
14902
}
14903
if (!isUndefined(from._locale)) {
14904
to._locale = from._locale;
14905
}
14906
14907
if (momentProperties.length > 0) {
14908
for (i = 0; i < momentProperties.length; i++) {
14909
prop = momentProperties[i];
14910
val = from[prop];
14911
if (!isUndefined(val)) {
14912
to[prop] = val;
14913
}
14914
}
14915
}
14916
14917
return to;
14918
}
14919
14920
var updateInProgress = false;
14921
14922
// Moment prototype object
14923
function Moment(config) {
14924
copyConfig(this, config);
14925
this._d = new Date(config._d != null ? config._d.getTime() : NaN);
14926
if (!this.isValid()) {
14927
this._d = new Date(NaN);
14928
}
14929
// Prevent infinite loop in case updateOffset creates new moment
14930
// objects.
14931
if (updateInProgress === false) {
14932
updateInProgress = true;
14933
hooks.updateOffset(this);
14934
updateInProgress = false;
14935
}
14936
}
14937
14938
function isMoment (obj) {
14939
return obj instanceof Moment || (obj != null && obj._isAMomentObject != null);
14940
}
14941
14942
function absFloor (number) {
14943
if (number < 0) {
14944
// -0 -> 0
14945
return Math.ceil(number) || 0;
14946
} else {
14947
return Math.floor(number);
14948
}
14949
}
14950
14951
function toInt(argumentForCoercion) {
14952
var coercedNumber = +argumentForCoercion,
14953
value = 0;
14954
14955
if (coercedNumber !== 0 && isFinite(coercedNumber)) {
14956
value = absFloor(coercedNumber);
14957
}
14958
14959
return value;
14960
}
14961
14962
// compare two arrays, return the number of differences
14963
function compareArrays(array1, array2, dontConvert) {
14964
var len = Math.min(array1.length, array2.length),
14965
lengthDiff = Math.abs(array1.length - array2.length),
14966
diffs = 0,
14967
i;
14968
for (i = 0; i < len; i++) {
14969
if ((dontConvert && array1[i] !== array2[i]) ||
14970
(!dontConvert && toInt(array1[i]) !== toInt(array2[i]))) {
14971
diffs++;
14972
}
14973
}
14974
return diffs + lengthDiff;
14975
}
14976
14977
function warn(msg) {
14978
if (hooks.suppressDeprecationWarnings === false &&
14979
(typeof console !== 'undefined') && console.warn) {
14980
console.warn('Deprecation warning: ' + msg);
14981
}
14982
}
14983
14984
function deprecate(msg, fn) {
14985
var firstTime = true;
14986
14987
return extend(function () {
14988
if (hooks.deprecationHandler != null) {
14989
hooks.deprecationHandler(null, msg);
14990
}
14991
if (firstTime) {
14992
var args = [];
14993
var arg;
14994
for (var i = 0; i < arguments.length; i++) {
14995
arg = '';
14996
if (typeof arguments[i] === 'object') {
14997
arg += '\n[' + i + '] ';
14998
for (var key in arguments[0]) {
14999
arg += key + ': ' + arguments[0][key] + ', ';
15000
}
15001
arg = arg.slice(0, -2); // Remove trailing comma and space
15002
} else {
15003
arg = arguments[i];
15004
}
15005
args.push(arg);
15006
}
15007
warn(msg + '\nArguments: ' + Array.prototype.slice.call(args).join('') + '\n' + (new Error()).stack);
15008
firstTime = false;
15009
}
15010
return fn.apply(this, arguments);
15011
}, fn);
15012
}
15013
15014
var deprecations = {};
15015
15016
function deprecateSimple(name, msg) {
15017
if (hooks.deprecationHandler != null) {
15018
hooks.deprecationHandler(name, msg);
15019
}
15020
if (!deprecations[name]) {
15021
warn(msg);
15022
deprecations[name] = true;
15023
}
15024
}
15025
15026
hooks.suppressDeprecationWarnings = false;
15027
hooks.deprecationHandler = null;
15028
15029
function isFunction(input) {
15030
return input instanceof Function || Object.prototype.toString.call(input) === '[object Function]';
15031
}
15032
15033
function set (config) {
15034
var prop, i;
15035
for (i in config) {
15036
prop = config[i];
15037
if (isFunction(prop)) {
15038
this[i] = prop;
15039
} else {
15040
this['_' + i] = prop;
15041
}
15042
}
15043
this._config = config;
15044
// Lenient ordinal parsing accepts just a number in addition to
15045
// number + (possibly) stuff coming from _dayOfMonthOrdinalParse.
15046
// TODO: Remove "ordinalParse" fallback in next major release.
15047
this._dayOfMonthOrdinalParseLenient = new RegExp(
15048
(this._dayOfMonthOrdinalParse.source || this._ordinalParse.source) +
15049
'|' + (/\d{1,2}/).source);
15050
}
15051
15052
function mergeConfigs(parentConfig, childConfig) {
15053
var res = extend({}, parentConfig), prop;
15054
for (prop in childConfig) {
15055
if (hasOwnProp(childConfig, prop)) {
15056
if (isObject(parentConfig[prop]) && isObject(childConfig[prop])) {
15057
res[prop] = {};
15058
extend(res[prop], parentConfig[prop]);
15059
extend(res[prop], childConfig[prop]);
15060
} else if (childConfig[prop] != null) {
15061
res[prop] = childConfig[prop];
15062
} else {
15063
delete res[prop];
15064
}
15065
}
15066
}
15067
for (prop in parentConfig) {
15068
if (hasOwnProp(parentConfig, prop) &&
15069
!hasOwnProp(childConfig, prop) &&
15070
isObject(parentConfig[prop])) {
15071
// make sure changes to properties don't modify parent config
15072
res[prop] = extend({}, res[prop]);
15073
}
15074
}
15075
return res;
15076
}
15077
15078
function Locale(config) {
15079
if (config != null) {
15080
this.set(config);
15081
}
15082
}
15083
15084
var keys;
15085
15086
if (Object.keys) {
15087
keys = Object.keys;
15088
} else {
15089
keys = function (obj) {
15090
var i, res = [];
15091
for (i in obj) {
15092
if (hasOwnProp(obj, i)) {
15093
res.push(i);
15094
}
15095
}
15096
return res;
15097
};
15098
}
15099
15100
var defaultCalendar = {
15101
sameDay : '[Today at] LT',
15102
nextDay : '[Tomorrow at] LT',
15103
nextWeek : 'dddd [at] LT',
15104
lastDay : '[Yesterday at] LT',
15105
lastWeek : '[Last] dddd [at] LT',
15106
sameElse : 'L'
15107
};
15108
15109
function calendar (key, mom, now) {
15110
var output = this._calendar[key] || this._calendar['sameElse'];
15111
return isFunction(output) ? output.call(mom, now) : output;
15112
}
15113
15114
var defaultLongDateFormat = {
15115
LTS : 'h:mm:ss A',
15116
LT : 'h:mm A',
15117
L : 'MM/DD/YYYY',
15118
LL : 'MMMM D, YYYY',
15119
LLL : 'MMMM D, YYYY h:mm A',
15120
LLLL : 'dddd, MMMM D, YYYY h:mm A'
15121
};
15122
15123
function longDateFormat (key) {
15124
var format = this._longDateFormat[key],
15125
formatUpper = this._longDateFormat[key.toUpperCase()];
15126
15127
if (format || !formatUpper) {
15128
return format;
15129
}
15130
15131
this._longDateFormat[key] = formatUpper.replace(/MMMM|MM|DD|dddd/g, function (val) {
15132
return val.slice(1);
15133
});
15134
15135
return this._longDateFormat[key];
15136
}
15137
15138
var defaultInvalidDate = 'Invalid date';
15139
15140
function invalidDate () {
15141
return this._invalidDate;
15142
}
15143
15144
var defaultOrdinal = '%d';
15145
var defaultDayOfMonthOrdinalParse = /\d{1,2}/;
15146
15147
function ordinal (number) {
15148
return this._ordinal.replace('%d', number);
15149
}
15150
15151
var defaultRelativeTime = {
15152
future : 'in %s',
15153
past : '%s ago',
15154
s : 'a few seconds',
15155
ss : '%d seconds',
15156
m : 'a minute',
15157
mm : '%d minutes',
15158
h : 'an hour',
15159
hh : '%d hours',
15160
d : 'a day',
15161
dd : '%d days',
15162
M : 'a month',
15163
MM : '%d months',
15164
y : 'a year',
15165
yy : '%d years'
15166
};
15167
15168
function relativeTime (number, withoutSuffix, string, isFuture) {
15169
var output = this._relativeTime[string];
15170
return (isFunction(output)) ?
15171
output(number, withoutSuffix, string, isFuture) :
15172
output.replace(/%d/i, number);
15173
}
15174
15175
function pastFuture (diff, output) {
15176
var format = this._relativeTime[diff > 0 ? 'future' : 'past'];
15177
return isFunction(format) ? format(output) : format.replace(/%s/i, output);
15178
}
15179
15180
var aliases = {};
15181
15182
function addUnitAlias (unit, shorthand) {
15183
var lowerCase = unit.toLowerCase();
15184
aliases[lowerCase] = aliases[lowerCase + 's'] = aliases[shorthand] = unit;
15185
}
15186
15187
function normalizeUnits(units) {
15188
return typeof units === 'string' ? aliases[units] || aliases[units.toLowerCase()] : undefined;
15189
}
15190
15191
function normalizeObjectUnits(inputObject) {
15192
var normalizedInput = {},
15193
normalizedProp,
15194
prop;
15195
15196
for (prop in inputObject) {
15197
if (hasOwnProp(inputObject, prop)) {
15198
normalizedProp = normalizeUnits(prop);
15199
if (normalizedProp) {
15200
normalizedInput[normalizedProp] = inputObject[prop];
15201
}
15202
}
15203
}
15204
15205
return normalizedInput;
15206
}
15207
15208
var priorities = {};
15209
15210
function addUnitPriority(unit, priority) {
15211
priorities[unit] = priority;
15212
}
15213
15214
function getPrioritizedUnits(unitsObj) {
15215
var units = [];
15216
for (var u in unitsObj) {
15217
units.push({unit: u, priority: priorities[u]});
15218
}
15219
units.sort(function (a, b) {
15220
return a.priority - b.priority;
15221
});
15222
return units;
15223
}
15224
15225
function zeroFill(number, targetLength, forceSign) {
15226
var absNumber = '' + Math.abs(number),
15227
zerosToFill = targetLength - absNumber.length,
15228
sign = number >= 0;
15229
return (sign ? (forceSign ? '+' : '') : '-') +
15230
Math.pow(10, Math.max(0, zerosToFill)).toString().substr(1) + absNumber;
15231
}
15232
15233
var formattingTokens = /(\[[^\[]*\])|(\\)?([Hh]mm(ss)?|Mo|MM?M?M?|Do|DDDo|DD?D?D?|ddd?d?|do?|w[o|w]?|W[o|W]?|Qo?|YYYYYY|YYYYY|YYYY|YY|gg(ggg?)?|GG(GGG?)?|e|E|a|A|hh?|HH?|kk?|mm?|ss?|S{1,9}|x|X|zz?|ZZ?|.)/g;
15234
15235
var localFormattingTokens = /(\[[^\[]*\])|(\\)?(LTS|LT|LL?L?L?|l{1,4})/g;
15236
15237
var formatFunctions = {};
15238
15239
var formatTokenFunctions = {};
15240
15241
// token: 'M'
15242
// padded: ['MM', 2]
15243
// ordinal: 'Mo'
15244
// callback: function () { this.month() + 1 }
15245
function addFormatToken (token, padded, ordinal, callback) {
15246
var func = callback;
15247
if (typeof callback === 'string') {
15248
func = function () {
15249
return this[callback]();
15250
};
15251
}
15252
if (token) {
15253
formatTokenFunctions[token] = func;
15254
}
15255
if (padded) {
15256
formatTokenFunctions[padded[0]] = function () {
15257
return zeroFill(func.apply(this, arguments), padded[1], padded[2]);
15258
};
15259
}
15260
if (ordinal) {
15261
formatTokenFunctions[ordinal] = function () {
15262
return this.localeData().ordinal(func.apply(this, arguments), token);
15263
};
15264
}
15265
}
15266
15267
function removeFormattingTokens(input) {
15268
if (input.match(/\[[\s\S]/)) {
15269
return input.replace(/^\[|\]$/g, '');
15270
}
15271
return input.replace(/\\/g, '');
15272
}
15273
15274
function makeFormatFunction(format) {
15275
var array = format.match(formattingTokens), i, length;
15276
15277
for (i = 0, length = array.length; i < length; i++) {
15278
if (formatTokenFunctions[array[i]]) {
15279
array[i] = formatTokenFunctions[array[i]];
15280
} else {
15281
array[i] = removeFormattingTokens(array[i]);
15282
}
15283
}
15284
15285
return function (mom) {
15286
var output = '', i;
15287
for (i = 0; i < length; i++) {
15288
output += isFunction(array[i]) ? array[i].call(mom, format) : array[i];
15289
}
15290
return output;
15291
};
15292
}
15293
15294
// format date using native date object
15295
function formatMoment(m, format) {
15296
if (!m.isValid()) {
15297
return m.localeData().invalidDate();
15298
}
15299
15300
format = expandFormat(format, m.localeData());
15301
formatFunctions[format] = formatFunctions[format] || makeFormatFunction(format);
15302
15303
return formatFunctions[format](m);
15304
}
15305
15306
function expandFormat(format, locale) {
15307
var i = 5;
15308
15309
function replaceLongDateFormatTokens(input) {
15310
return locale.longDateFormat(input) || input;
15311
}
15312
15313
localFormattingTokens.lastIndex = 0;
15314
while (i >= 0 && localFormattingTokens.test(format)) {
15315
format = format.replace(localFormattingTokens, replaceLongDateFormatTokens);
15316
localFormattingTokens.lastIndex = 0;
15317
i -= 1;
15318
}
15319
15320
return format;
15321
}
15322
15323
var match1 = /\d/; // 0 - 9
15324
var match2 = /\d\d/; // 00 - 99
15325
var match3 = /\d{3}/; // 000 - 999
15326
var match4 = /\d{4}/; // 0000 - 9999
15327
var match6 = /[+-]?\d{6}/; // -999999 - 999999
15328
var match1to2 = /\d\d?/; // 0 - 99
15329
var match3to4 = /\d\d\d\d?/; // 999 - 9999
15330
var match5to6 = /\d\d\d\d\d\d?/; // 99999 - 999999
15331
var match1to3 = /\d{1,3}/; // 0 - 999
15332
var match1to4 = /\d{1,4}/; // 0 - 9999
15333
var match1to6 = /[+-]?\d{1,6}/; // -999999 - 999999
15334
15335
var matchUnsigned = /\d+/; // 0 - inf
15336
var matchSigned = /[+-]?\d+/; // -inf - inf
15337
15338
var matchOffset = /Z|[+-]\d\d:?\d\d/gi; // +00:00 -00:00 +0000 -0000 or Z
15339
var matchShortOffset = /Z|[+-]\d\d(?::?\d\d)?/gi; // +00 -00 +00:00 -00:00 +0000 -0000 or Z
15340
15341
var matchTimestamp = /[+-]?\d+(\.\d{1,3})?/; // 123456789 123456789.123
15342
15343
// any word (or two) characters or numbers including two/three word month in arabic.
15344
// includes scottish gaelic two word and hyphenated months
15345
var matchWord = /[0-9]{0,256}['a-z\u00A0-\u05FF\u0700-\uD7FF\uF900-\uFDCF\uFDF0-\uFF07\uFF10-\uFFEF]{1,256}|[\u0600-\u06FF\/]{1,256}(\s*?[\u0600-\u06FF]{1,256}){1,2}/i;
15346
15347
var regexes = {};
15348
15349
function addRegexToken (token, regex, strictRegex) {
15350
regexes[token] = isFunction(regex) ? regex : function (isStrict, localeData) {
15351
return (isStrict && strictRegex) ? strictRegex : regex;
15352
};
15353
}
15354
15355
function getParseRegexForToken (token, config) {
15356
if (!hasOwnProp(regexes, token)) {
15357
return new RegExp(unescapeFormat(token));
15358
}
15359
15360
return regexes[token](config._strict, config._locale);
15361
}
15362
15363
// Code from http://stackoverflow.com/questions/3561493/is-there-a-regexp-escape-function-in-javascript
15364
function unescapeFormat(s) {
15365
return regexEscape(s.replace('\\', '').replace(/\\(\[)|\\(\])|\[([^\]\[]*)\]|\\(.)/g, function (matched, p1, p2, p3, p4) {
15366
return p1 || p2 || p3 || p4;
15367
}));
15368
}
15369
15370
function regexEscape(s) {
15371
return s.replace(/[-\/\\^$*+?.()|[\]{}]/g, '\\$&');
15372
}
15373
15374
var tokens = {};
15375
15376
function addParseToken (token, callback) {
15377
var i, func = callback;
15378
if (typeof token === 'string') {
15379
token = [token];
15380
}
15381
if (isNumber(callback)) {
15382
func = function (input, array) {
15383
array[callback] = toInt(input);
15384
};
15385
}
15386
for (i = 0; i < token.length; i++) {
15387
tokens[token[i]] = func;
15388
}
15389
}
15390
15391
function addWeekParseToken (token, callback) {
15392
addParseToken(token, function (input, array, config, token) {
15393
config._w = config._w || {};
15394
callback(input, config._w, config, token);
15395
});
15396
}
15397
15398
function addTimeToArrayFromToken(token, input, config) {
15399
if (input != null && hasOwnProp(tokens, token)) {
15400
tokens[token](input, config._a, config, token);
15401
}
15402
}
15403
15404
var YEAR = 0;
15405
var MONTH = 1;
15406
var DATE = 2;
15407
var HOUR = 3;
15408
var MINUTE = 4;
15409
var SECOND = 5;
15410
var MILLISECOND = 6;
15411
var WEEK = 7;
15412
var WEEKDAY = 8;
15413
15414
// FORMATTING
15415
15416
addFormatToken('Y', 0, 0, function () {
15417
var y = this.year();
15418
return y <= 9999 ? '' + y : '+' + y;
15419
});
15420
15421
addFormatToken(0, ['YY', 2], 0, function () {
15422
return this.year() % 100;
15423
});
15424
15425
addFormatToken(0, ['YYYY', 4], 0, 'year');
15426
addFormatToken(0, ['YYYYY', 5], 0, 'year');
15427
addFormatToken(0, ['YYYYYY', 6, true], 0, 'year');
15428
15429
// ALIASES
15430
15431
addUnitAlias('year', 'y');
15432
15433
// PRIORITIES
15434
15435
addUnitPriority('year', 1);
15436
15437
// PARSING
15438
15439
addRegexToken('Y', matchSigned);
15440
addRegexToken('YY', match1to2, match2);
15441
addRegexToken('YYYY', match1to4, match4);
15442
addRegexToken('YYYYY', match1to6, match6);
15443
addRegexToken('YYYYYY', match1to6, match6);
15444
15445
addParseToken(['YYYYY', 'YYYYYY'], YEAR);
15446
addParseToken('YYYY', function (input, array) {
15447
array[YEAR] = input.length === 2 ? hooks.parseTwoDigitYear(input) : toInt(input);
15448
});
15449
addParseToken('YY', function (input, array) {
15450
array[YEAR] = hooks.parseTwoDigitYear(input);
15451
});
15452
addParseToken('Y', function (input, array) {
15453
array[YEAR] = parseInt(input, 10);
15454
});
15455
15456
// HELPERS
15457
15458
function daysInYear(year) {
15459
return isLeapYear(year) ? 366 : 365;
15460
}
15461
15462
function isLeapYear(year) {
15463
return (year % 4 === 0 && year % 100 !== 0) || year % 400 === 0;
15464
}
15465
15466
// HOOKS
15467
15468
hooks.parseTwoDigitYear = function (input) {
15469
return toInt(input) + (toInt(input) > 68 ? 1900 : 2000);
15470
};
15471
15472
// MOMENTS
15473
15474
var getSetYear = makeGetSet('FullYear', true);
15475
15476
function getIsLeapYear () {
15477
return isLeapYear(this.year());
15478
}
15479
15480
function makeGetSet (unit, keepTime) {
15481
return function (value) {
15482
if (value != null) {
15483
set$1(this, unit, value);
15484
hooks.updateOffset(this, keepTime);
15485
return this;
15486
} else {
15487
return get(this, unit);
15488
}
15489
};
15490
}
15491
15492
function get (mom, unit) {
15493
return mom.isValid() ?
15494
mom._d['get' + (mom._isUTC ? 'UTC' : '') + unit]() : NaN;
15495
}
15496
15497
function set$1 (mom, unit, value) {
15498
if (mom.isValid() && !isNaN(value)) {
15499
if (unit === 'FullYear' && isLeapYear(mom.year()) && mom.month() === 1 && mom.date() === 29) {
15500
mom._d['set' + (mom._isUTC ? 'UTC' : '') + unit](value, mom.month(), daysInMonth(value, mom.month()));
15501
}
15502
else {
15503
mom._d['set' + (mom._isUTC ? 'UTC' : '') + unit](value);
15504
}
15505
}
15506
}
15507
15508
// MOMENTS
15509
15510
function stringGet (units) {
15511
units = normalizeUnits(units);
15512
if (isFunction(this[units])) {
15513
return this[units]();
15514
}
15515
return this;
15516
}
15517
15518
15519
function stringSet (units, value) {
15520
if (typeof units === 'object') {
15521
units = normalizeObjectUnits(units);
15522
var prioritized = getPrioritizedUnits(units);
15523
for (var i = 0; i < prioritized.length; i++) {
15524
this[prioritized[i].unit](units[prioritized[i].unit]);
15525
}
15526
} else {
15527
units = normalizeUnits(units);
15528
if (isFunction(this[units])) {
15529
return this[units](value);
15530
}
15531
}
15532
return this;
15533
}
15534
15535
function mod(n, x) {
15536
return ((n % x) + x) % x;
15537
}
15538
15539
var indexOf;
15540
15541
if (Array.prototype.indexOf) {
15542
indexOf = Array.prototype.indexOf;
15543
} else {
15544
indexOf = function (o) {
15545
// I know
15546
var i;
15547
for (i = 0; i < this.length; ++i) {
15548
if (this[i] === o) {
15549
return i;
15550
}
15551
}
15552
return -1;
15553
};
15554
}
15555
15556
function daysInMonth(year, month) {
15557
if (isNaN(year) || isNaN(month)) {
15558
return NaN;
15559
}
15560
var modMonth = mod(month, 12);
15561
year += (month - modMonth) / 12;
15562
return modMonth === 1 ? (isLeapYear(year) ? 29 : 28) : (31 - modMonth % 7 % 2);
15563
}
15564
15565
// FORMATTING
15566
15567
addFormatToken('M', ['MM', 2], 'Mo', function () {
15568
return this.month() + 1;
15569
});
15570
15571
addFormatToken('MMM', 0, 0, function (format) {
15572
return this.localeData().monthsShort(this, format);
15573
});
15574
15575
addFormatToken('MMMM', 0, 0, function (format) {
15576
return this.localeData().months(this, format);
15577
});
15578
15579
// ALIASES
15580
15581
addUnitAlias('month', 'M');
15582
15583
// PRIORITY
15584
15585
addUnitPriority('month', 8);
15586
15587
// PARSING
15588
15589
addRegexToken('M', match1to2);
15590
addRegexToken('MM', match1to2, match2);
15591
addRegexToken('MMM', function (isStrict, locale) {
15592
return locale.monthsShortRegex(isStrict);
15593
});
15594
addRegexToken('MMMM', function (isStrict, locale) {
15595
return locale.monthsRegex(isStrict);
15596
});
15597
15598
addParseToken(['M', 'MM'], function (input, array) {
15599
array[MONTH] = toInt(input) - 1;
15600
});
15601
15602
addParseToken(['MMM', 'MMMM'], function (input, array, config, token) {
15603
var month = config._locale.monthsParse(input, token, config._strict);
15604
// if we didn't find a month name, mark the date as invalid.
15605
if (month != null) {
15606
array[MONTH] = month;
15607
} else {
15608
getParsingFlags(config).invalidMonth = input;
15609
}
15610
});
15611
15612
// LOCALES
15613
15614
var MONTHS_IN_FORMAT = /D[oD]?(\[[^\[\]]*\]|\s)+MMMM?/;
15615
var defaultLocaleMonths = 'January_February_March_April_May_June_July_August_September_October_November_December'.split('_');
15616
function localeMonths (m, format) {
15617
if (!m) {
15618
return isArray(this._months) ? this._months :
15619
this._months['standalone'];
15620
}
15621
return isArray(this._months) ? this._months[m.month()] :
15622
this._months[(this._months.isFormat || MONTHS_IN_FORMAT).test(format) ? 'format' : 'standalone'][m.month()];
15623
}
15624
15625
var defaultLocaleMonthsShort = 'Jan_Feb_Mar_Apr_May_Jun_Jul_Aug_Sep_Oct_Nov_Dec'.split('_');
15626
function localeMonthsShort (m, format) {
15627
if (!m) {
15628
return isArray(this._monthsShort) ? this._monthsShort :
15629
this._monthsShort['standalone'];
15630
}
15631
return isArray(this._monthsShort) ? this._monthsShort[m.month()] :
15632
this._monthsShort[MONTHS_IN_FORMAT.test(format) ? 'format' : 'standalone'][m.month()];
15633
}
15634
15635
function handleStrictParse(monthName, format, strict) {
15636
var i, ii, mom, llc = monthName.toLocaleLowerCase();
15637
if (!this._monthsParse) {
15638
// this is not used
15639
this._monthsParse = [];
15640
this._longMonthsParse = [];
15641
this._shortMonthsParse = [];
15642
for (i = 0; i < 12; ++i) {
15643
mom = createUTC([2000, i]);
15644
this._shortMonthsParse[i] = this.monthsShort(mom, '').toLocaleLowerCase();
15645
this._longMonthsParse[i] = this.months(mom, '').toLocaleLowerCase();
15646
}
15647
}
15648
15649
if (strict) {
15650
if (format === 'MMM') {
15651
ii = indexOf.call(this._shortMonthsParse, llc);
15652
return ii !== -1 ? ii : null;
15653
} else {
15654
ii = indexOf.call(this._longMonthsParse, llc);
15655
return ii !== -1 ? ii : null;
15656
}
15657
} else {
15658
if (format === 'MMM') {
15659
ii = indexOf.call(this._shortMonthsParse, llc);
15660
if (ii !== -1) {
15661
return ii;
15662
}
15663
ii = indexOf.call(this._longMonthsParse, llc);
15664
return ii !== -1 ? ii : null;
15665
} else {
15666
ii = indexOf.call(this._longMonthsParse, llc);
15667
if (ii !== -1) {
15668
return ii;
15669
}
15670
ii = indexOf.call(this._shortMonthsParse, llc);
15671
return ii !== -1 ? ii : null;
15672
}
15673
}
15674
}
15675
15676
function localeMonthsParse (monthName, format, strict) {
15677
var i, mom, regex;
15678
15679
if (this._monthsParseExact) {
15680
return handleStrictParse.call(this, monthName, format, strict);
15681
}
15682
15683
if (!this._monthsParse) {
15684
this._monthsParse = [];
15685
this._longMonthsParse = [];
15686
this._shortMonthsParse = [];
15687
}
15688
15689
// TODO: add sorting
15690
// Sorting makes sure if one month (or abbr) is a prefix of another
15691
// see sorting in computeMonthsParse
15692
for (i = 0; i < 12; i++) {
15693
// make the regex if we don't have it already
15694
mom = createUTC([2000, i]);
15695
if (strict && !this._longMonthsParse[i]) {
15696
this._longMonthsParse[i] = new RegExp('^' + this.months(mom, '').replace('.', '') + '$', 'i');
15697
this._shortMonthsParse[i] = new RegExp('^' + this.monthsShort(mom, '').replace('.', '') + '$', 'i');
15698
}
15699
if (!strict && !this._monthsParse[i]) {
15700
regex = '^' + this.months(mom, '') + '|^' + this.monthsShort(mom, '');
15701
this._monthsParse[i] = new RegExp(regex.replace('.', ''), 'i');
15702
}
15703
// test the regex
15704
if (strict && format === 'MMMM' && this._longMonthsParse[i].test(monthName)) {
15705
return i;
15706
} else if (strict && format === 'MMM' && this._shortMonthsParse[i].test(monthName)) {
15707
return i;
15708
} else if (!strict && this._monthsParse[i].test(monthName)) {
15709
return i;
15710
}
15711
}
15712
}
15713
15714
// MOMENTS
15715
15716
function setMonth (mom, value) {
15717
var dayOfMonth;
15718
15719
if (!mom.isValid()) {
15720
// No op
15721
return mom;
15722
}
15723
15724
if (typeof value === 'string') {
15725
if (/^\d+$/.test(value)) {
15726
value = toInt(value);
15727
} else {
15728
value = mom.localeData().monthsParse(value);
15729
// TODO: Another silent failure?
15730
if (!isNumber(value)) {
15731
return mom;
15732
}
15733
}
15734
}
15735
15736
dayOfMonth = Math.min(mom.date(), daysInMonth(mom.year(), value));
15737
mom._d['set' + (mom._isUTC ? 'UTC' : '') + 'Month'](value, dayOfMonth);
15738
return mom;
15739
}
15740
15741
function getSetMonth (value) {
15742
if (value != null) {
15743
setMonth(this, value);
15744
hooks.updateOffset(this, true);
15745
return this;
15746
} else {
15747
return get(this, 'Month');
15748
}
15749
}
15750
15751
function getDaysInMonth () {
15752
return daysInMonth(this.year(), this.month());
15753
}
15754
15755
var defaultMonthsShortRegex = matchWord;
15756
function monthsShortRegex (isStrict) {
15757
if (this._monthsParseExact) {
15758
if (!hasOwnProp(this, '_monthsRegex')) {
15759
computeMonthsParse.call(this);
15760
}
15761
if (isStrict) {
15762
return this._monthsShortStrictRegex;
15763
} else {
15764
return this._monthsShortRegex;
15765
}
15766
} else {
15767
if (!hasOwnProp(this, '_monthsShortRegex')) {
15768
this._monthsShortRegex = defaultMonthsShortRegex;
15769
}
15770
return this._monthsShortStrictRegex && isStrict ?
15771
this._monthsShortStrictRegex : this._monthsShortRegex;
15772
}
15773
}
15774
15775
var defaultMonthsRegex = matchWord;
15776
function monthsRegex (isStrict) {
15777
if (this._monthsParseExact) {
15778
if (!hasOwnProp(this, '_monthsRegex')) {
15779
computeMonthsParse.call(this);
15780
}
15781
if (isStrict) {
15782
return this._monthsStrictRegex;
15783
} else {
15784
return this._monthsRegex;
15785
}
15786
} else {
15787
if (!hasOwnProp(this, '_monthsRegex')) {
15788
this._monthsRegex = defaultMonthsRegex;
15789
}
15790
return this._monthsStrictRegex && isStrict ?
15791
this._monthsStrictRegex : this._monthsRegex;
15792
}
15793
}
15794
15795
function computeMonthsParse () {
15796
function cmpLenRev(a, b) {
15797
return b.length - a.length;
15798
}
15799
15800
var shortPieces = [], longPieces = [], mixedPieces = [],
15801
i, mom;
15802
for (i = 0; i < 12; i++) {
15803
// make the regex if we don't have it already
15804
mom = createUTC([2000, i]);
15805
shortPieces.push(this.monthsShort(mom, ''));
15806
longPieces.push(this.months(mom, ''));
15807
mixedPieces.push(this.months(mom, ''));
15808
mixedPieces.push(this.monthsShort(mom, ''));
15809
}
15810
// Sorting makes sure if one month (or abbr) is a prefix of another it
15811
// will match the longer piece.
15812
shortPieces.sort(cmpLenRev);
15813
longPieces.sort(cmpLenRev);
15814
mixedPieces.sort(cmpLenRev);
15815
for (i = 0; i < 12; i++) {
15816
shortPieces[i] = regexEscape(shortPieces[i]);
15817
longPieces[i] = regexEscape(longPieces[i]);
15818
}
15819
for (i = 0; i < 24; i++) {
15820
mixedPieces[i] = regexEscape(mixedPieces[i]);
15821
}
15822
15823
this._monthsRegex = new RegExp('^(' + mixedPieces.join('|') + ')', 'i');
15824
this._monthsShortRegex = this._monthsRegex;
15825
this._monthsStrictRegex = new RegExp('^(' + longPieces.join('|') + ')', 'i');
15826
this._monthsShortStrictRegex = new RegExp('^(' + shortPieces.join('|') + ')', 'i');
15827
}
15828
15829
function createDate (y, m, d, h, M, s, ms) {
15830
// can't just apply() to create a date:
15831
// https://stackoverflow.com/q/181348
15832
var date;
15833
// the date constructor remaps years 0-99 to 1900-1999
15834
if (y < 100 && y >= 0) {
15835
// preserve leap years using a full 400 year cycle, then reset
15836
date = new Date(y + 400, m, d, h, M, s, ms);
15837
if (isFinite(date.getFullYear())) {
15838
date.setFullYear(y);
15839
}
15840
} else {
15841
date = new Date(y, m, d, h, M, s, ms);
15842
}
15843
15844
return date;
15845
}
15846
15847
function createUTCDate (y) {
15848
var date;
15849
// the Date.UTC function remaps years 0-99 to 1900-1999
15850
if (y < 100 && y >= 0) {
15851
var args = Array.prototype.slice.call(arguments);
15852
// preserve leap years using a full 400 year cycle, then reset
15853
args[0] = y + 400;
15854
date = new Date(Date.UTC.apply(null, args));
15855
if (isFinite(date.getUTCFullYear())) {
15856
date.setUTCFullYear(y);
15857
}
15858
} else {
15859
date = new Date(Date.UTC.apply(null, arguments));
15860
}
15861
15862
return date;
15863
}
15864
15865
// start-of-first-week - start-of-year
15866
function firstWeekOffset(year, dow, doy) {
15867
var // first-week day -- which january is always in the first week (4 for iso, 1 for other)
15868
fwd = 7 + dow - doy,
15869
// first-week day local weekday -- which local weekday is fwd
15870
fwdlw = (7 + createUTCDate(year, 0, fwd).getUTCDay() - dow) % 7;
15871
15872
return -fwdlw + fwd - 1;
15873
}
15874
15875
// https://en.wikipedia.org/wiki/ISO_week_date#Calculating_a_date_given_the_year.2C_week_number_and_weekday
15876
function dayOfYearFromWeeks(year, week, weekday, dow, doy) {
15877
var localWeekday = (7 + weekday - dow) % 7,
15878
weekOffset = firstWeekOffset(year, dow, doy),
15879
dayOfYear = 1 + 7 * (week - 1) + localWeekday + weekOffset,
15880
resYear, resDayOfYear;
15881
15882
if (dayOfYear <= 0) {
15883
resYear = year - 1;
15884
resDayOfYear = daysInYear(resYear) + dayOfYear;
15885
} else if (dayOfYear > daysInYear(year)) {
15886
resYear = year + 1;
15887
resDayOfYear = dayOfYear - daysInYear(year);
15888
} else {
15889
resYear = year;
15890
resDayOfYear = dayOfYear;
15891
}
15892
15893
return {
15894
year: resYear,
15895
dayOfYear: resDayOfYear
15896
};
15897
}
15898
15899
function weekOfYear(mom, dow, doy) {
15900
var weekOffset = firstWeekOffset(mom.year(), dow, doy),
15901
week = Math.floor((mom.dayOfYear() - weekOffset - 1) / 7) + 1,
15902
resWeek, resYear;
15903
15904
if (week < 1) {
15905
resYear = mom.year() - 1;
15906
resWeek = week + weeksInYear(resYear, dow, doy);
15907
} else if (week > weeksInYear(mom.year(), dow, doy)) {
15908
resWeek = week - weeksInYear(mom.year(), dow, doy);
15909
resYear = mom.year() + 1;
15910
} else {
15911
resYear = mom.year();
15912
resWeek = week;
15913
}
15914
15915
return {
15916
week: resWeek,
15917
year: resYear
15918
};
15919
}
15920
15921
function weeksInYear(year, dow, doy) {
15922
var weekOffset = firstWeekOffset(year, dow, doy),
15923
weekOffsetNext = firstWeekOffset(year + 1, dow, doy);
15924
return (daysInYear(year) - weekOffset + weekOffsetNext) / 7;
15925
}
15926
15927
// FORMATTING
15928
15929
addFormatToken('w', ['ww', 2], 'wo', 'week');
15930
addFormatToken('W', ['WW', 2], 'Wo', 'isoWeek');
15931
15932
// ALIASES
15933
15934
addUnitAlias('week', 'w');
15935
addUnitAlias('isoWeek', 'W');
15936
15937
// PRIORITIES
15938
15939
addUnitPriority('week', 5);
15940
addUnitPriority('isoWeek', 5);
15941
15942
// PARSING
15943
15944
addRegexToken('w', match1to2);
15945
addRegexToken('ww', match1to2, match2);
15946
addRegexToken('W', match1to2);
15947
addRegexToken('WW', match1to2, match2);
15948
15949
addWeekParseToken(['w', 'ww', 'W', 'WW'], function (input, week, config, token) {
15950
week[token.substr(0, 1)] = toInt(input);
15951
});
15952
15953
// HELPERS
15954
15955
// LOCALES
15956
15957
function localeWeek (mom) {
15958
return weekOfYear(mom, this._week.dow, this._week.doy).week;
15959
}
15960
15961
var defaultLocaleWeek = {
15962
dow : 0, // Sunday is the first day of the week.
15963
doy : 6 // The week that contains Jan 6th is the first week of the year.
15964
};
15965
15966
function localeFirstDayOfWeek () {
15967
return this._week.dow;
15968
}
15969
15970
function localeFirstDayOfYear () {
15971
return this._week.doy;
15972
}
15973
15974
// MOMENTS
15975
15976
function getSetWeek (input) {
15977
var week = this.localeData().week(this);
15978
return input == null ? week : this.add((input - week) * 7, 'd');
15979
}
15980
15981
function getSetISOWeek (input) {
15982
var week = weekOfYear(this, 1, 4).week;
15983
return input == null ? week : this.add((input - week) * 7, 'd');
15984
}
15985
15986
// FORMATTING
15987
15988
addFormatToken('d', 0, 'do', 'day');
15989
15990
addFormatToken('dd', 0, 0, function (format) {
15991
return this.localeData().weekdaysMin(this, format);
15992
});
15993
15994
addFormatToken('ddd', 0, 0, function (format) {
15995
return this.localeData().weekdaysShort(this, format);
15996
});
15997
15998
addFormatToken('dddd', 0, 0, function (format) {
15999
return this.localeData().weekdays(this, format);
16000
});
16001
16002
addFormatToken('e', 0, 0, 'weekday');
16003
addFormatToken('E', 0, 0, 'isoWeekday');
16004
16005
// ALIASES
16006
16007
addUnitAlias('day', 'd');
16008
addUnitAlias('weekday', 'e');
16009
addUnitAlias('isoWeekday', 'E');
16010
16011
// PRIORITY
16012
addUnitPriority('day', 11);
16013
addUnitPriority('weekday', 11);
16014
addUnitPriority('isoWeekday', 11);
16015
16016
// PARSING
16017
16018
addRegexToken('d', match1to2);
16019
addRegexToken('e', match1to2);
16020
addRegexToken('E', match1to2);
16021
addRegexToken('dd', function (isStrict, locale) {
16022
return locale.weekdaysMinRegex(isStrict);
16023
});
16024
addRegexToken('ddd', function (isStrict, locale) {
16025
return locale.weekdaysShortRegex(isStrict);
16026
});
16027
addRegexToken('dddd', function (isStrict, locale) {
16028
return locale.weekdaysRegex(isStrict);
16029
});
16030
16031
addWeekParseToken(['dd', 'ddd', 'dddd'], function (input, week, config, token) {
16032
var weekday = config._locale.weekdaysParse(input, token, config._strict);
16033
// if we didn't get a weekday name, mark the date as invalid
16034
if (weekday != null) {
16035
week.d = weekday;
16036
} else {
16037
getParsingFlags(config).invalidWeekday = input;
16038
}
16039
});
16040
16041
addWeekParseToken(['d', 'e', 'E'], function (input, week, config, token) {
16042
week[token] = toInt(input);
16043
});
16044
16045
// HELPERS
16046
16047
function parseWeekday(input, locale) {
16048
if (typeof input !== 'string') {
16049
return input;
16050
}
16051
16052
if (!isNaN(input)) {
16053
return parseInt(input, 10);
16054
}
16055
16056
input = locale.weekdaysParse(input);
16057
if (typeof input === 'number') {
16058
return input;
16059
}
16060
16061
return null;
16062
}
16063
16064
function parseIsoWeekday(input, locale) {
16065
if (typeof input === 'string') {
16066
return locale.weekdaysParse(input) % 7 || 7;
16067
}
16068
return isNaN(input) ? null : input;
16069
}
16070
16071
// LOCALES
16072
function shiftWeekdays (ws, n) {
16073
return ws.slice(n, 7).concat(ws.slice(0, n));
16074
}
16075
16076
var defaultLocaleWeekdays = 'Sunday_Monday_Tuesday_Wednesday_Thursday_Friday_Saturday'.split('_');
16077
function localeWeekdays (m, format) {
16078
var weekdays = isArray(this._weekdays) ? this._weekdays :
16079
this._weekdays[(m && m !== true && this._weekdays.isFormat.test(format)) ? 'format' : 'standalone'];
16080
return (m === true) ? shiftWeekdays(weekdays, this._week.dow)
16081
: (m) ? weekdays[m.day()] : weekdays;
16082
}
16083
16084
var defaultLocaleWeekdaysShort = 'Sun_Mon_Tue_Wed_Thu_Fri_Sat'.split('_');
16085
function localeWeekdaysShort (m) {
16086
return (m === true) ? shiftWeekdays(this._weekdaysShort, this._week.dow)
16087
: (m) ? this._weekdaysShort[m.day()] : this._weekdaysShort;
16088
}
16089
16090
var defaultLocaleWeekdaysMin = 'Su_Mo_Tu_We_Th_Fr_Sa'.split('_');
16091
function localeWeekdaysMin (m) {
16092
return (m === true) ? shiftWeekdays(this._weekdaysMin, this._week.dow)
16093
: (m) ? this._weekdaysMin[m.day()] : this._weekdaysMin;
16094
}
16095
16096
function handleStrictParse$1(weekdayName, format, strict) {
16097
var i, ii, mom, llc = weekdayName.toLocaleLowerCase();
16098
if (!this._weekdaysParse) {
16099
this._weekdaysParse = [];
16100
this._shortWeekdaysParse = [];
16101
this._minWeekdaysParse = [];
16102
16103
for (i = 0; i < 7; ++i) {
16104
mom = createUTC([2000, 1]).day(i);
16105
this._minWeekdaysParse[i] = this.weekdaysMin(mom, '').toLocaleLowerCase();
16106
this._shortWeekdaysParse[i] = this.weekdaysShort(mom, '').toLocaleLowerCase();
16107
this._weekdaysParse[i] = this.weekdays(mom, '').toLocaleLowerCase();
16108
}
16109
}
16110
16111
if (strict) {
16112
if (format === 'dddd') {
16113
ii = indexOf.call(this._weekdaysParse, llc);
16114
return ii !== -1 ? ii : null;
16115
} else if (format === 'ddd') {
16116
ii = indexOf.call(this._shortWeekdaysParse, llc);
16117
return ii !== -1 ? ii : null;
16118
} else {
16119
ii = indexOf.call(this._minWeekdaysParse, llc);
16120
return ii !== -1 ? ii : null;
16121
}
16122
} else {
16123
if (format === 'dddd') {
16124
ii = indexOf.call(this._weekdaysParse, llc);
16125
if (ii !== -1) {
16126
return ii;
16127
}
16128
ii = indexOf.call(this._shortWeekdaysParse, llc);
16129
if (ii !== -1) {
16130
return ii;
16131
}
16132
ii = indexOf.call(this._minWeekdaysParse, llc);
16133
return ii !== -1 ? ii : null;
16134
} else if (format === 'ddd') {
16135
ii = indexOf.call(this._shortWeekdaysParse, llc);
16136
if (ii !== -1) {
16137
return ii;
16138
}
16139
ii = indexOf.call(this._weekdaysParse, llc);
16140
if (ii !== -1) {
16141
return ii;
16142
}
16143
ii = indexOf.call(this._minWeekdaysParse, llc);
16144
return ii !== -1 ? ii : null;
16145
} else {
16146
ii = indexOf.call(this._minWeekdaysParse, llc);
16147
if (ii !== -1) {
16148
return ii;
16149
}
16150
ii = indexOf.call(this._weekdaysParse, llc);
16151
if (ii !== -1) {
16152
return ii;
16153
}
16154
ii = indexOf.call(this._shortWeekdaysParse, llc);
16155
return ii !== -1 ? ii : null;
16156
}
16157
}
16158
}
16159
16160
function localeWeekdaysParse (weekdayName, format, strict) {
16161
var i, mom, regex;
16162
16163
if (this._weekdaysParseExact) {
16164
return handleStrictParse$1.call(this, weekdayName, format, strict);
16165
}
16166
16167
if (!this._weekdaysParse) {
16168
this._weekdaysParse = [];
16169
this._minWeekdaysParse = [];
16170
this._shortWeekdaysParse = [];
16171
this._fullWeekdaysParse = [];
16172
}
16173
16174
for (i = 0; i < 7; i++) {
16175
// make the regex if we don't have it already
16176
16177
mom = createUTC([2000, 1]).day(i);
16178
if (strict && !this._fullWeekdaysParse[i]) {
16179
this._fullWeekdaysParse[i] = new RegExp('^' + this.weekdays(mom, '').replace('.', '\\.?') + '$', 'i');
16180
this._shortWeekdaysParse[i] = new RegExp('^' + this.weekdaysShort(mom, '').replace('.', '\\.?') + '$', 'i');
16181
this._minWeekdaysParse[i] = new RegExp('^' + this.weekdaysMin(mom, '').replace('.', '\\.?') + '$', 'i');
16182
}
16183
if (!this._weekdaysParse[i]) {
16184
regex = '^' + this.weekdays(mom, '') + '|^' + this.weekdaysShort(mom, '') + '|^' + this.weekdaysMin(mom, '');
16185
this._weekdaysParse[i] = new RegExp(regex.replace('.', ''), 'i');
16186
}
16187
// test the regex
16188
if (strict && format === 'dddd' && this._fullWeekdaysParse[i].test(weekdayName)) {
16189
return i;
16190
} else if (strict && format === 'ddd' && this._shortWeekdaysParse[i].test(weekdayName)) {
16191
return i;
16192
} else if (strict && format === 'dd' && this._minWeekdaysParse[i].test(weekdayName)) {
16193
return i;
16194
} else if (!strict && this._weekdaysParse[i].test(weekdayName)) {
16195
return i;
16196
}
16197
}
16198
}
16199
16200
// MOMENTS
16201
16202
function getSetDayOfWeek (input) {
16203
if (!this.isValid()) {
16204
return input != null ? this : NaN;
16205
}
16206
var day = this._isUTC ? this._d.getUTCDay() : this._d.getDay();
16207
if (input != null) {
16208
input = parseWeekday(input, this.localeData());
16209
return this.add(input - day, 'd');
16210
} else {
16211
return day;
16212
}
16213
}
16214
16215
function getSetLocaleDayOfWeek (input) {
16216
if (!this.isValid()) {
16217
return input != null ? this : NaN;
16218
}
16219
var weekday = (this.day() + 7 - this.localeData()._week.dow) % 7;
16220
return input == null ? weekday : this.add(input - weekday, 'd');
16221
}
16222
16223
function getSetISODayOfWeek (input) {
16224
if (!this.isValid()) {
16225
return input != null ? this : NaN;
16226
}
16227
16228
// behaves the same as moment#day except
16229
// as a getter, returns 7 instead of 0 (1-7 range instead of 0-6)
16230
// as a setter, sunday should belong to the previous week.
16231
16232
if (input != null) {
16233
var weekday = parseIsoWeekday(input, this.localeData());
16234
return this.day(this.day() % 7 ? weekday : weekday - 7);
16235
} else {
16236
return this.day() || 7;
16237
}
16238
}
16239
16240
var defaultWeekdaysRegex = matchWord;
16241
function weekdaysRegex (isStrict) {
16242
if (this._weekdaysParseExact) {
16243
if (!hasOwnProp(this, '_weekdaysRegex')) {
16244
computeWeekdaysParse.call(this);
16245
}
16246
if (isStrict) {
16247
return this._weekdaysStrictRegex;
16248
} else {
16249
return this._weekdaysRegex;
16250
}
16251
} else {
16252
if (!hasOwnProp(this, '_weekdaysRegex')) {
16253
this._weekdaysRegex = defaultWeekdaysRegex;
16254
}
16255
return this._weekdaysStrictRegex && isStrict ?
16256
this._weekdaysStrictRegex : this._weekdaysRegex;
16257
}
16258
}
16259
16260
var defaultWeekdaysShortRegex = matchWord;
16261
function weekdaysShortRegex (isStrict) {
16262
if (this._weekdaysParseExact) {
16263
if (!hasOwnProp(this, '_weekdaysRegex')) {
16264
computeWeekdaysParse.call(this);
16265
}
16266
if (isStrict) {
16267
return this._weekdaysShortStrictRegex;
16268
} else {
16269
return this._weekdaysShortRegex;
16270
}
16271
} else {
16272
if (!hasOwnProp(this, '_weekdaysShortRegex')) {
16273
this._weekdaysShortRegex = defaultWeekdaysShortRegex;
16274
}
16275
return this._weekdaysShortStrictRegex && isStrict ?
16276
this._weekdaysShortStrictRegex : this._weekdaysShortRegex;
16277
}
16278
}
16279
16280
var defaultWeekdaysMinRegex = matchWord;
16281
function weekdaysMinRegex (isStrict) {
16282
if (this._weekdaysParseExact) {
16283
if (!hasOwnProp(this, '_weekdaysRegex')) {
16284
computeWeekdaysParse.call(this);
16285
}
16286
if (isStrict) {
16287
return this._weekdaysMinStrictRegex;
16288
} else {
16289
return this._weekdaysMinRegex;
16290
}
16291
} else {
16292
if (!hasOwnProp(this, '_weekdaysMinRegex')) {
16293
this._weekdaysMinRegex = defaultWeekdaysMinRegex;
16294
}
16295
return this._weekdaysMinStrictRegex && isStrict ?
16296
this._weekdaysMinStrictRegex : this._weekdaysMinRegex;
16297
}
16298
}
16299
16300
16301
function computeWeekdaysParse () {
16302
function cmpLenRev(a, b) {
16303
return b.length - a.length;
16304
}
16305
16306
var minPieces = [], shortPieces = [], longPieces = [], mixedPieces = [],
16307
i, mom, minp, shortp, longp;
16308
for (i = 0; i < 7; i++) {
16309
// make the regex if we don't have it already
16310
mom = createUTC([2000, 1]).day(i);
16311
minp = this.weekdaysMin(mom, '');
16312
shortp = this.weekdaysShort(mom, '');
16313
longp = this.weekdays(mom, '');
16314
minPieces.push(minp);
16315
shortPieces.push(shortp);
16316
longPieces.push(longp);
16317
mixedPieces.push(minp);
16318
mixedPieces.push(shortp);
16319
mixedPieces.push(longp);
16320
}
16321
// Sorting makes sure if one weekday (or abbr) is a prefix of another it
16322
// will match the longer piece.
16323
minPieces.sort(cmpLenRev);
16324
shortPieces.sort(cmpLenRev);
16325
longPieces.sort(cmpLenRev);
16326
mixedPieces.sort(cmpLenRev);
16327
for (i = 0; i < 7; i++) {
16328
shortPieces[i] = regexEscape(shortPieces[i]);
16329
longPieces[i] = regexEscape(longPieces[i]);
16330
mixedPieces[i] = regexEscape(mixedPieces[i]);
16331
}
16332
16333
this._weekdaysRegex = new RegExp('^(' + mixedPieces.join('|') + ')', 'i');
16334
this._weekdaysShortRegex = this._weekdaysRegex;
16335
this._weekdaysMinRegex = this._weekdaysRegex;
16336
16337
this._weekdaysStrictRegex = new RegExp('^(' + longPieces.join('|') + ')', 'i');
16338
this._weekdaysShortStrictRegex = new RegExp('^(' + shortPieces.join('|') + ')', 'i');
16339
this._weekdaysMinStrictRegex = new RegExp('^(' + minPieces.join('|') + ')', 'i');
16340
}
16341
16342
// FORMATTING
16343
16344
function hFormat() {
16345
return this.hours() % 12 || 12;
16346
}
16347
16348
function kFormat() {
16349
return this.hours() || 24;
16350
}
16351
16352
addFormatToken('H', ['HH', 2], 0, 'hour');
16353
addFormatToken('h', ['hh', 2], 0, hFormat);
16354
addFormatToken('k', ['kk', 2], 0, kFormat);
16355
16356
addFormatToken('hmm', 0, 0, function () {
16357
return '' + hFormat.apply(this) + zeroFill(this.minutes(), 2);
16358
});
16359
16360
addFormatToken('hmmss', 0, 0, function () {
16361
return '' + hFormat.apply(this) + zeroFill(this.minutes(), 2) +
16362
zeroFill(this.seconds(), 2);
16363
});
16364
16365
addFormatToken('Hmm', 0, 0, function () {
16366
return '' + this.hours() + zeroFill(this.minutes(), 2);
16367
});
16368
16369
addFormatToken('Hmmss', 0, 0, function () {
16370
return '' + this.hours() + zeroFill(this.minutes(), 2) +
16371
zeroFill(this.seconds(), 2);
16372
});
16373
16374
function meridiem (token, lowercase) {
16375
addFormatToken(token, 0, 0, function () {
16376
return this.localeData().meridiem(this.hours(), this.minutes(), lowercase);
16377
});
16378
}
16379
16380
meridiem('a', true);
16381
meridiem('A', false);
16382
16383
// ALIASES
16384
16385
addUnitAlias('hour', 'h');
16386
16387
// PRIORITY
16388
addUnitPriority('hour', 13);
16389
16390
// PARSING
16391
16392
function matchMeridiem (isStrict, locale) {
16393
return locale._meridiemParse;
16394
}
16395
16396
addRegexToken('a', matchMeridiem);
16397
addRegexToken('A', matchMeridiem);
16398
addRegexToken('H', match1to2);
16399
addRegexToken('h', match1to2);
16400
addRegexToken('k', match1to2);
16401
addRegexToken('HH', match1to2, match2);
16402
addRegexToken('hh', match1to2, match2);
16403
addRegexToken('kk', match1to2, match2);
16404
16405
addRegexToken('hmm', match3to4);
16406
addRegexToken('hmmss', match5to6);
16407
addRegexToken('Hmm', match3to4);
16408
addRegexToken('Hmmss', match5to6);
16409
16410
addParseToken(['H', 'HH'], HOUR);
16411
addParseToken(['k', 'kk'], function (input, array, config) {
16412
var kInput = toInt(input);
16413
array[HOUR] = kInput === 24 ? 0 : kInput;
16414
});
16415
addParseToken(['a', 'A'], function (input, array, config) {
16416
config._isPm = config._locale.isPM(input);
16417
config._meridiem = input;
16418
});
16419
addParseToken(['h', 'hh'], function (input, array, config) {
16420
array[HOUR] = toInt(input);
16421
getParsingFlags(config).bigHour = true;
16422
});
16423
addParseToken('hmm', function (input, array, config) {
16424
var pos = input.length - 2;
16425
array[HOUR] = toInt(input.substr(0, pos));
16426
array[MINUTE] = toInt(input.substr(pos));
16427
getParsingFlags(config).bigHour = true;
16428
});
16429
addParseToken('hmmss', function (input, array, config) {
16430
var pos1 = input.length - 4;
16431
var pos2 = input.length - 2;
16432
array[HOUR] = toInt(input.substr(0, pos1));
16433
array[MINUTE] = toInt(input.substr(pos1, 2));
16434
array[SECOND] = toInt(input.substr(pos2));
16435
getParsingFlags(config).bigHour = true;
16436
});
16437
addParseToken('Hmm', function (input, array, config) {
16438
var pos = input.length - 2;
16439
array[HOUR] = toInt(input.substr(0, pos));
16440
array[MINUTE] = toInt(input.substr(pos));
16441
});
16442
addParseToken('Hmmss', function (input, array, config) {
16443
var pos1 = input.length - 4;
16444
var pos2 = input.length - 2;
16445
array[HOUR] = toInt(input.substr(0, pos1));
16446
array[MINUTE] = toInt(input.substr(pos1, 2));
16447
array[SECOND] = toInt(input.substr(pos2));
16448
});
16449
16450
// LOCALES
16451
16452
function localeIsPM (input) {
16453
// IE8 Quirks Mode & IE7 Standards Mode do not allow accessing strings like arrays
16454
// Using charAt should be more compatible.
16455
return ((input + '').toLowerCase().charAt(0) === 'p');
16456
}
16457
16458
var defaultLocaleMeridiemParse = /[ap]\.?m?\.?/i;
16459
function localeMeridiem (hours, minutes, isLower) {
16460
if (hours > 11) {
16461
return isLower ? 'pm' : 'PM';
16462
} else {
16463
return isLower ? 'am' : 'AM';
16464
}
16465
}
16466
16467
16468
// MOMENTS
16469
16470
// Setting the hour should keep the time, because the user explicitly
16471
// specified which hour they want. So trying to maintain the same hour (in
16472
// a new timezone) makes sense. Adding/subtracting hours does not follow
16473
// this rule.
16474
var getSetHour = makeGetSet('Hours', true);
16475
16476
var baseConfig = {
16477
calendar: defaultCalendar,
16478
longDateFormat: defaultLongDateFormat,
16479
invalidDate: defaultInvalidDate,
16480
ordinal: defaultOrdinal,
16481
dayOfMonthOrdinalParse: defaultDayOfMonthOrdinalParse,
16482
relativeTime: defaultRelativeTime,
16483
16484
months: defaultLocaleMonths,
16485
monthsShort: defaultLocaleMonthsShort,
16486
16487
week: defaultLocaleWeek,
16488
16489
weekdays: defaultLocaleWeekdays,
16490
weekdaysMin: defaultLocaleWeekdaysMin,
16491
weekdaysShort: defaultLocaleWeekdaysShort,
16492
16493
meridiemParse: defaultLocaleMeridiemParse
16494
};
16495
16496
// internal storage for locale config files
16497
var locales = {};
16498
var localeFamilies = {};
16499
var globalLocale;
16500
16501
function normalizeLocale(key) {
16502
return key ? key.toLowerCase().replace('_', '-') : key;
16503
}
16504
16505
// pick the locale from the array
16506
// try ['en-au', 'en-gb'] as 'en-au', 'en-gb', 'en', as in move through the list trying each
16507
// substring from most specific to least, but move to the next array item if it's a more specific variant than the current root
16508
function chooseLocale(names) {
16509
var i = 0, j, next, locale, split;
16510
16511
while (i < names.length) {
16512
split = normalizeLocale(names[i]).split('-');
16513
j = split.length;
16514
next = normalizeLocale(names[i + 1]);
16515
next = next ? next.split('-') : null;
16516
while (j > 0) {
16517
locale = loadLocale(split.slice(0, j).join('-'));
16518
if (locale) {
16519
return locale;
16520
}
16521
if (next && next.length >= j && compareArrays(split, next, true) >= j - 1) {
16522
//the next array item is better than a shallower substring of this one
16523
break;
16524
}
16525
j--;
16526
}
16527
i++;
16528
}
16529
return globalLocale;
16530
}
16531
16532
function loadLocale(name) {
16533
var oldLocale = null;
16534
// TODO: Find a better way to register and load all the locales in Node
16535
if (!locales[name] && ('object' !== 'undefined') &&
16536
module && module.exports) {
16537
try {
16538
oldLocale = globalLocale._abbr;
16539
var aliasedRequire = commonjsRequire;
16540
aliasedRequire('./locale/' + name);
16541
getSetGlobalLocale(oldLocale);
16542
} catch (e) {}
16543
}
16544
return locales[name];
16545
}
16546
16547
// This function will load locale and then set the global locale. If
16548
// no arguments are passed in, it will simply return the current global
16549
// locale key.
16550
function getSetGlobalLocale (key, values) {
16551
var data;
16552
if (key) {
16553
if (isUndefined(values)) {
16554
data = getLocale(key);
16555
}
16556
else {
16557
data = defineLocale(key, values);
16558
}
16559
16560
if (data) {
16561
// moment.duration._locale = moment._locale = data;
16562
globalLocale = data;
16563
}
16564
else {
16565
if ((typeof console !== 'undefined') && console.warn) {
16566
//warn user if arguments are passed but the locale could not be set
16567
console.warn('Locale ' + key + ' not found. Did you forget to load it?');
16568
}
16569
}
16570
}
16571
16572
return globalLocale._abbr;
16573
}
16574
16575
function defineLocale (name, config) {
16576
if (config !== null) {
16577
var locale, parentConfig = baseConfig;
16578
config.abbr = name;
16579
if (locales[name] != null) {
16580
deprecateSimple('defineLocaleOverride',
16581
'use moment.updateLocale(localeName, config) to change ' +
16582
'an existing locale. moment.defineLocale(localeName, ' +
16583
'config) should only be used for creating a new locale ' +
16584
'See http://momentjs.com/guides/#/warnings/define-locale/ for more info.');
16585
parentConfig = locales[name]._config;
16586
} else if (config.parentLocale != null) {
16587
if (locales[config.parentLocale] != null) {
16588
parentConfig = locales[config.parentLocale]._config;
16589
} else {
16590
locale = loadLocale(config.parentLocale);
16591
if (locale != null) {
16592
parentConfig = locale._config;
16593
} else {
16594
if (!localeFamilies[config.parentLocale]) {
16595
localeFamilies[config.parentLocale] = [];
16596
}
16597
localeFamilies[config.parentLocale].push({
16598
name: name,
16599
config: config
16600
});
16601
return null;
16602
}
16603
}
16604
}
16605
locales[name] = new Locale(mergeConfigs(parentConfig, config));
16606
16607
if (localeFamilies[name]) {
16608
localeFamilies[name].forEach(function (x) {
16609
defineLocale(x.name, x.config);
16610
});
16611
}
16612
16613
// backwards compat for now: also set the locale
16614
// make sure we set the locale AFTER all child locales have been
16615
// created, so we won't end up with the child locale set.
16616
getSetGlobalLocale(name);
16617
16618
16619
return locales[name];
16620
} else {
16621
// useful for testing
16622
delete locales[name];
16623
return null;
16624
}
16625
}
16626
16627
function updateLocale(name, config) {
16628
if (config != null) {
16629
var locale, tmpLocale, parentConfig = baseConfig;
16630
// MERGE
16631
tmpLocale = loadLocale(name);
16632
if (tmpLocale != null) {
16633
parentConfig = tmpLocale._config;
16634
}
16635
config = mergeConfigs(parentConfig, config);
16636
locale = new Locale(config);
16637
locale.parentLocale = locales[name];
16638
locales[name] = locale;
16639
16640
// backwards compat for now: also set the locale
16641
getSetGlobalLocale(name);
16642
} else {
16643
// pass null for config to unupdate, useful for tests
16644
if (locales[name] != null) {
16645
if (locales[name].parentLocale != null) {
16646
locales[name] = locales[name].parentLocale;
16647
} else if (locales[name] != null) {
16648
delete locales[name];
16649
}
16650
}
16651
}
16652
return locales[name];
16653
}
16654
16655
// returns locale data
16656
function getLocale (key) {
16657
var locale;
16658
16659
if (key && key._locale && key._locale._abbr) {
16660
key = key._locale._abbr;
16661
}
16662
16663
if (!key) {
16664
return globalLocale;
16665
}
16666
16667
if (!isArray(key)) {
16668
//short-circuit everything else
16669
locale = loadLocale(key);
16670
if (locale) {
16671
return locale;
16672
}
16673
key = [key];
16674
}
16675
16676
return chooseLocale(key);
16677
}
16678
16679
function listLocales() {
16680
return keys(locales);
16681
}
16682
16683
function checkOverflow (m) {
16684
var overflow;
16685
var a = m._a;
16686
16687
if (a && getParsingFlags(m).overflow === -2) {
16688
overflow =
16689
a[MONTH] < 0 || a[MONTH] > 11 ? MONTH :
16690
a[DATE] < 1 || a[DATE] > daysInMonth(a[YEAR], a[MONTH]) ? DATE :
16691
a[HOUR] < 0 || a[HOUR] > 24 || (a[HOUR] === 24 && (a[MINUTE] !== 0 || a[SECOND] !== 0 || a[MILLISECOND] !== 0)) ? HOUR :
16692
a[MINUTE] < 0 || a[MINUTE] > 59 ? MINUTE :
16693
a[SECOND] < 0 || a[SECOND] > 59 ? SECOND :
16694
a[MILLISECOND] < 0 || a[MILLISECOND] > 999 ? MILLISECOND :
16695
-1;
16696
16697
if (getParsingFlags(m)._overflowDayOfYear && (overflow < YEAR || overflow > DATE)) {
16698
overflow = DATE;
16699
}
16700
if (getParsingFlags(m)._overflowWeeks && overflow === -1) {
16701
overflow = WEEK;
16702
}
16703
if (getParsingFlags(m)._overflowWeekday && overflow === -1) {
16704
overflow = WEEKDAY;
16705
}
16706
16707
getParsingFlags(m).overflow = overflow;
16708
}
16709
16710
return m;
16711
}
16712
16713
// Pick the first defined of two or three arguments.
16714
function defaults(a, b, c) {
16715
if (a != null) {
16716
return a;
16717
}
16718
if (b != null) {
16719
return b;
16720
}
16721
return c;
16722
}
16723
16724
function currentDateArray(config) {
16725
// hooks is actually the exported moment object
16726
var nowValue = new Date(hooks.now());
16727
if (config._useUTC) {
16728
return [nowValue.getUTCFullYear(), nowValue.getUTCMonth(), nowValue.getUTCDate()];
16729
}
16730
return [nowValue.getFullYear(), nowValue.getMonth(), nowValue.getDate()];
16731
}
16732
16733
// convert an array to a date.
16734
// the array should mirror the parameters below
16735
// note: all values past the year are optional and will default to the lowest possible value.
16736
// [year, month, day , hour, minute, second, millisecond]
16737
function configFromArray (config) {
16738
var i, date, input = [], currentDate, expectedWeekday, yearToUse;
16739
16740
if (config._d) {
16741
return;
16742
}
16743
16744
currentDate = currentDateArray(config);
16745
16746
//compute day of the year from weeks and weekdays
16747
if (config._w && config._a[DATE] == null && config._a[MONTH] == null) {
16748
dayOfYearFromWeekInfo(config);
16749
}
16750
16751
//if the day of the year is set, figure out what it is
16752
if (config._dayOfYear != null) {
16753
yearToUse = defaults(config._a[YEAR], currentDate[YEAR]);
16754
16755
if (config._dayOfYear > daysInYear(yearToUse) || config._dayOfYear === 0) {
16756
getParsingFlags(config)._overflowDayOfYear = true;
16757
}
16758
16759
date = createUTCDate(yearToUse, 0, config._dayOfYear);
16760
config._a[MONTH] = date.getUTCMonth();
16761
config._a[DATE] = date.getUTCDate();
16762
}
16763
16764
// Default to current date.
16765
// * if no year, month, day of month are given, default to today
16766
// * if day of month is given, default month and year
16767
// * if month is given, default only year
16768
// * if year is given, don't default anything
16769
for (i = 0; i < 3 && config._a[i] == null; ++i) {
16770
config._a[i] = input[i] = currentDate[i];
16771
}
16772
16773
// Zero out whatever was not defaulted, including time
16774
for (; i < 7; i++) {
16775
config._a[i] = input[i] = (config._a[i] == null) ? (i === 2 ? 1 : 0) : config._a[i];
16776
}
16777
16778
// Check for 24:00:00.000
16779
if (config._a[HOUR] === 24 &&
16780
config._a[MINUTE] === 0 &&
16781
config._a[SECOND] === 0 &&
16782
config._a[MILLISECOND] === 0) {
16783
config._nextDay = true;
16784
config._a[HOUR] = 0;
16785
}
16786
16787
config._d = (config._useUTC ? createUTCDate : createDate).apply(null, input);
16788
expectedWeekday = config._useUTC ? config._d.getUTCDay() : config._d.getDay();
16789
16790
// Apply timezone offset from input. The actual utcOffset can be changed
16791
// with parseZone.
16792
if (config._tzm != null) {
16793
config._d.setUTCMinutes(config._d.getUTCMinutes() - config._tzm);
16794
}
16795
16796
if (config._nextDay) {
16797
config._a[HOUR] = 24;
16798
}
16799
16800
// check for mismatching day of week
16801
if (config._w && typeof config._w.d !== 'undefined' && config._w.d !== expectedWeekday) {
16802
getParsingFlags(config).weekdayMismatch = true;
16803
}
16804
}
16805
16806
function dayOfYearFromWeekInfo(config) {
16807
var w, weekYear, week, weekday, dow, doy, temp, weekdayOverflow;
16808
16809
w = config._w;
16810
if (w.GG != null || w.W != null || w.E != null) {
16811
dow = 1;
16812
doy = 4;
16813
16814
// TODO: We need to take the current isoWeekYear, but that depends on
16815
// how we interpret now (local, utc, fixed offset). So create
16816
// a now version of current config (take local/utc/offset flags, and
16817
// create now).
16818
weekYear = defaults(w.GG, config._a[YEAR], weekOfYear(createLocal(), 1, 4).year);
16819
week = defaults(w.W, 1);
16820
weekday = defaults(w.E, 1);
16821
if (weekday < 1 || weekday > 7) {
16822
weekdayOverflow = true;
16823
}
16824
} else {
16825
dow = config._locale._week.dow;
16826
doy = config._locale._week.doy;
16827
16828
var curWeek = weekOfYear(createLocal(), dow, doy);
16829
16830
weekYear = defaults(w.gg, config._a[YEAR], curWeek.year);
16831
16832
// Default to current week.
16833
week = defaults(w.w, curWeek.week);
16834
16835
if (w.d != null) {
16836
// weekday -- low day numbers are considered next week
16837
weekday = w.d;
16838
if (weekday < 0 || weekday > 6) {
16839
weekdayOverflow = true;
16840
}
16841
} else if (w.e != null) {
16842
// local weekday -- counting starts from beginning of week
16843
weekday = w.e + dow;
16844
if (w.e < 0 || w.e > 6) {
16845
weekdayOverflow = true;
16846
}
16847
} else {
16848
// default to beginning of week
16849
weekday = dow;
16850
}
16851
}
16852
if (week < 1 || week > weeksInYear(weekYear, dow, doy)) {
16853
getParsingFlags(config)._overflowWeeks = true;
16854
} else if (weekdayOverflow != null) {
16855
getParsingFlags(config)._overflowWeekday = true;
16856
} else {
16857
temp = dayOfYearFromWeeks(weekYear, week, weekday, dow, doy);
16858
config._a[YEAR] = temp.year;
16859
config._dayOfYear = temp.dayOfYear;
16860
}
16861
}
16862
16863
// iso 8601 regex
16864
// 0000-00-00 0000-W00 or 0000-W00-0 + T + 00 or 00:00 or 00:00:00 or 00:00:00.000 + +00:00 or +0000 or +00)
16865
var extendedIsoRegex = /^\s*((?:[+-]\d{6}|\d{4})-(?:\d\d-\d\d|W\d\d-\d|W\d\d|\d\d\d|\d\d))(?:(T| )(\d\d(?::\d\d(?::\d\d(?:[.,]\d+)?)?)?)([\+\-]\d\d(?::?\d\d)?|\s*Z)?)?$/;
16866
var basicIsoRegex = /^\s*((?:[+-]\d{6}|\d{4})(?:\d\d\d\d|W\d\d\d|W\d\d|\d\d\d|\d\d))(?:(T| )(\d\d(?:\d\d(?:\d\d(?:[.,]\d+)?)?)?)([\+\-]\d\d(?::?\d\d)?|\s*Z)?)?$/;
16867
16868
var tzRegex = /Z|[+-]\d\d(?::?\d\d)?/;
16869
16870
var isoDates = [
16871
['YYYYYY-MM-DD', /[+-]\d{6}-\d\d-\d\d/],
16872
['YYYY-MM-DD', /\d{4}-\d\d-\d\d/],
16873
['GGGG-[W]WW-E', /\d{4}-W\d\d-\d/],
16874
['GGGG-[W]WW', /\d{4}-W\d\d/, false],
16875
['YYYY-DDD', /\d{4}-\d{3}/],
16876
['YYYY-MM', /\d{4}-\d\d/, false],
16877
['YYYYYYMMDD', /[+-]\d{10}/],
16878
['YYYYMMDD', /\d{8}/],
16879
// YYYYMM is NOT allowed by the standard
16880
['GGGG[W]WWE', /\d{4}W\d{3}/],
16881
['GGGG[W]WW', /\d{4}W\d{2}/, false],
16882
['YYYYDDD', /\d{7}/]
16883
];
16884
16885
// iso time formats and regexes
16886
var isoTimes = [
16887
['HH:mm:ss.SSSS', /\d\d:\d\d:\d\d\.\d+/],
16888
['HH:mm:ss,SSSS', /\d\d:\d\d:\d\d,\d+/],
16889
['HH:mm:ss', /\d\d:\d\d:\d\d/],
16890
['HH:mm', /\d\d:\d\d/],
16891
['HHmmss.SSSS', /\d\d\d\d\d\d\.\d+/],
16892
['HHmmss,SSSS', /\d\d\d\d\d\d,\d+/],
16893
['HHmmss', /\d\d\d\d\d\d/],
16894
['HHmm', /\d\d\d\d/],
16895
['HH', /\d\d/]
16896
];
16897
16898
var aspNetJsonRegex = /^\/?Date\((\-?\d+)/i;
16899
16900
// date from iso format
16901
function configFromISO(config) {
16902
var i, l,
16903
string = config._i,
16904
match = extendedIsoRegex.exec(string) || basicIsoRegex.exec(string),
16905
allowTime, dateFormat, timeFormat, tzFormat;
16906
16907
if (match) {
16908
getParsingFlags(config).iso = true;
16909
16910
for (i = 0, l = isoDates.length; i < l; i++) {
16911
if (isoDates[i][1].exec(match[1])) {
16912
dateFormat = isoDates[i][0];
16913
allowTime = isoDates[i][2] !== false;
16914
break;
16915
}
16916
}
16917
if (dateFormat == null) {
16918
config._isValid = false;
16919
return;
16920
}
16921
if (match[3]) {
16922
for (i = 0, l = isoTimes.length; i < l; i++) {
16923
if (isoTimes[i][1].exec(match[3])) {
16924
// match[2] should be 'T' or space
16925
timeFormat = (match[2] || ' ') + isoTimes[i][0];
16926
break;
16927
}
16928
}
16929
if (timeFormat == null) {
16930
config._isValid = false;
16931
return;
16932
}
16933
}
16934
if (!allowTime && timeFormat != null) {
16935
config._isValid = false;
16936
return;
16937
}
16938
if (match[4]) {
16939
if (tzRegex.exec(match[4])) {
16940
tzFormat = 'Z';
16941
} else {
16942
config._isValid = false;
16943
return;
16944
}
16945
}
16946
config._f = dateFormat + (timeFormat || '') + (tzFormat || '');
16947
configFromStringAndFormat(config);
16948
} else {
16949
config._isValid = false;
16950
}
16951
}
16952
16953
// RFC 2822 regex: For details see https://tools.ietf.org/html/rfc2822#section-3.3
16954
var rfc2822 = /^(?:(Mon|Tue|Wed|Thu|Fri|Sat|Sun),?\s)?(\d{1,2})\s(Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec)\s(\d{2,4})\s(\d\d):(\d\d)(?::(\d\d))?\s(?:(UT|GMT|[ECMP][SD]T)|([Zz])|([+-]\d{4}))$/;
16955
16956
function extractFromRFC2822Strings(yearStr, monthStr, dayStr, hourStr, minuteStr, secondStr) {
16957
var result = [
16958
untruncateYear(yearStr),
16959
defaultLocaleMonthsShort.indexOf(monthStr),
16960
parseInt(dayStr, 10),
16961
parseInt(hourStr, 10),
16962
parseInt(minuteStr, 10)
16963
];
16964
16965
if (secondStr) {
16966
result.push(parseInt(secondStr, 10));
16967
}
16968
16969
return result;
16970
}
16971
16972
function untruncateYear(yearStr) {
16973
var year = parseInt(yearStr, 10);
16974
if (year <= 49) {
16975
return 2000 + year;
16976
} else if (year <= 999) {
16977
return 1900 + year;
16978
}
16979
return year;
16980
}
16981
16982
function preprocessRFC2822(s) {
16983
// Remove comments and folding whitespace and replace multiple-spaces with a single space
16984
return s.replace(/\([^)]*\)|[\n\t]/g, ' ').replace(/(\s\s+)/g, ' ').replace(/^\s\s*/, '').replace(/\s\s*$/, '');
16985
}
16986
16987
function checkWeekday(weekdayStr, parsedInput, config) {
16988
if (weekdayStr) {
16989
// TODO: Replace the vanilla JS Date object with an indepentent day-of-week check.
16990
var weekdayProvided = defaultLocaleWeekdaysShort.indexOf(weekdayStr),
16991
weekdayActual = new Date(parsedInput[0], parsedInput[1], parsedInput[2]).getDay();
16992
if (weekdayProvided !== weekdayActual) {
16993
getParsingFlags(config).weekdayMismatch = true;
16994
config._isValid = false;
16995
return false;
16996
}
16997
}
16998
return true;
16999
}
17000
17001
var obsOffsets = {
17002
UT: 0,
17003
GMT: 0,
17004
EDT: -4 * 60,
17005
EST: -5 * 60,
17006
CDT: -5 * 60,
17007
CST: -6 * 60,
17008
MDT: -6 * 60,
17009
MST: -7 * 60,
17010
PDT: -7 * 60,
17011
PST: -8 * 60
17012
};
17013
17014
function calculateOffset(obsOffset, militaryOffset, numOffset) {
17015
if (obsOffset) {
17016
return obsOffsets[obsOffset];
17017
} else if (militaryOffset) {
17018
// the only allowed military tz is Z
17019
return 0;
17020
} else {
17021
var hm = parseInt(numOffset, 10);
17022
var m = hm % 100, h = (hm - m) / 100;
17023
return h * 60 + m;
17024
}
17025
}
17026
17027
// date and time from ref 2822 format
17028
function configFromRFC2822(config) {
17029
var match = rfc2822.exec(preprocessRFC2822(config._i));
17030
if (match) {
17031
var parsedArray = extractFromRFC2822Strings(match[4], match[3], match[2], match[5], match[6], match[7]);
17032
if (!checkWeekday(match[1], parsedArray, config)) {
17033
return;
17034
}
17035
17036
config._a = parsedArray;
17037
config._tzm = calculateOffset(match[8], match[9], match[10]);
17038
17039
config._d = createUTCDate.apply(null, config._a);
17040
config._d.setUTCMinutes(config._d.getUTCMinutes() - config._tzm);
17041
17042
getParsingFlags(config).rfc2822 = true;
17043
} else {
17044
config._isValid = false;
17045
}
17046
}
17047
17048
// date from iso format or fallback
17049
function configFromString(config) {
17050
var matched = aspNetJsonRegex.exec(config._i);
17051
17052
if (matched !== null) {
17053
config._d = new Date(+matched[1]);
17054
return;
17055
}
17056
17057
configFromISO(config);
17058
if (config._isValid === false) {
17059
delete config._isValid;
17060
} else {
17061
return;
17062
}
17063
17064
configFromRFC2822(config);
17065
if (config._isValid === false) {
17066
delete config._isValid;
17067
} else {
17068
return;
17069
}
17070
17071
// Final attempt, use Input Fallback
17072
hooks.createFromInputFallback(config);
17073
}
17074
17075
hooks.createFromInputFallback = deprecate(
17076
'value provided is not in a recognized RFC2822 or ISO format. moment construction falls back to js Date(), ' +
17077
'which is not reliable across all browsers and versions. Non RFC2822/ISO date formats are ' +
17078
'discouraged and will be removed in an upcoming major release. Please refer to ' +
17079
'http://momentjs.com/guides/#/warnings/js-date/ for more info.',
17080
function (config) {
17081
config._d = new Date(config._i + (config._useUTC ? ' UTC' : ''));
17082
}
17083
);
17084
17085
// constant that refers to the ISO standard
17086
hooks.ISO_8601 = function () {};
17087
17088
// constant that refers to the RFC 2822 form
17089
hooks.RFC_2822 = function () {};
17090
17091
// date from string and format string
17092
function configFromStringAndFormat(config) {
17093
// TODO: Move this to another part of the creation flow to prevent circular deps
17094
if (config._f === hooks.ISO_8601) {
17095
configFromISO(config);
17096
return;
17097
}
17098
if (config._f === hooks.RFC_2822) {
17099
configFromRFC2822(config);
17100
return;
17101
}
17102
config._a = [];
17103
getParsingFlags(config).empty = true;
17104
17105
// This array is used to make a Date, either with `new Date` or `Date.UTC`
17106
var string = '' + config._i,
17107
i, parsedInput, tokens, token, skipped,
17108
stringLength = string.length,
17109
totalParsedInputLength = 0;
17110
17111
tokens = expandFormat(config._f, config._locale).match(formattingTokens) || [];
17112
17113
for (i = 0; i < tokens.length; i++) {
17114
token = tokens[i];
17115
parsedInput = (string.match(getParseRegexForToken(token, config)) || [])[0];
17116
// console.log('token', token, 'parsedInput', parsedInput,
17117
// 'regex', getParseRegexForToken(token, config));
17118
if (parsedInput) {
17119
skipped = string.substr(0, string.indexOf(parsedInput));
17120
if (skipped.length > 0) {
17121
getParsingFlags(config).unusedInput.push(skipped);
17122
}
17123
string = string.slice(string.indexOf(parsedInput) + parsedInput.length);
17124
totalParsedInputLength += parsedInput.length;
17125
}
17126
// don't parse if it's not a known token
17127
if (formatTokenFunctions[token]) {
17128
if (parsedInput) {
17129
getParsingFlags(config).empty = false;
17130
}
17131
else {
17132
getParsingFlags(config).unusedTokens.push(token);
17133
}
17134
addTimeToArrayFromToken(token, parsedInput, config);
17135
}
17136
else if (config._strict && !parsedInput) {
17137
getParsingFlags(config).unusedTokens.push(token);
17138
}
17139
}
17140
17141
// add remaining unparsed input length to the string
17142
getParsingFlags(config).charsLeftOver = stringLength - totalParsedInputLength;
17143
if (string.length > 0) {
17144
getParsingFlags(config).unusedInput.push(string);
17145
}
17146
17147
// clear _12h flag if hour is <= 12
17148
if (config._a[HOUR] <= 12 &&
17149
getParsingFlags(config).bigHour === true &&
17150
config._a[HOUR] > 0) {
17151
getParsingFlags(config).bigHour = undefined;
17152
}
17153
17154
getParsingFlags(config).parsedDateParts = config._a.slice(0);
17155
getParsingFlags(config).meridiem = config._meridiem;
17156
// handle meridiem
17157
config._a[HOUR] = meridiemFixWrap(config._locale, config._a[HOUR], config._meridiem);
17158
17159
configFromArray(config);
17160
checkOverflow(config);
17161
}
17162
17163
17164
function meridiemFixWrap (locale, hour, meridiem) {
17165
var isPm;
17166
17167
if (meridiem == null) {
17168
// nothing to do
17169
return hour;
17170
}
17171
if (locale.meridiemHour != null) {
17172
return locale.meridiemHour(hour, meridiem);
17173
} else if (locale.isPM != null) {
17174
// Fallback
17175
isPm = locale.isPM(meridiem);
17176
if (isPm && hour < 12) {
17177
hour += 12;
17178
}
17179
if (!isPm && hour === 12) {
17180
hour = 0;
17181
}
17182
return hour;
17183
} else {
17184
// this is not supposed to happen
17185
return hour;
17186
}
17187
}
17188
17189
// date from string and array of format strings
17190
function configFromStringAndArray(config) {
17191
var tempConfig,
17192
bestMoment,
17193
17194
scoreToBeat,
17195
i,
17196
currentScore;
17197
17198
if (config._f.length === 0) {
17199
getParsingFlags(config).invalidFormat = true;
17200
config._d = new Date(NaN);
17201
return;
17202
}
17203
17204
for (i = 0; i < config._f.length; i++) {
17205
currentScore = 0;
17206
tempConfig = copyConfig({}, config);
17207
if (config._useUTC != null) {
17208
tempConfig._useUTC = config._useUTC;
17209
}
17210
tempConfig._f = config._f[i];
17211
configFromStringAndFormat(tempConfig);
17212
17213
if (!isValid(tempConfig)) {
17214
continue;
17215
}
17216
17217
// if there is any input that was not parsed add a penalty for that format
17218
currentScore += getParsingFlags(tempConfig).charsLeftOver;
17219
17220
//or tokens
17221
currentScore += getParsingFlags(tempConfig).unusedTokens.length * 10;
17222
17223
getParsingFlags(tempConfig).score = currentScore;
17224
17225
if (scoreToBeat == null || currentScore < scoreToBeat) {
17226
scoreToBeat = currentScore;
17227
bestMoment = tempConfig;
17228
}
17229
}
17230
17231
extend(config, bestMoment || tempConfig);
17232
}
17233
17234
function configFromObject(config) {
17235
if (config._d) {
17236
return;
17237
}
17238
17239
var i = normalizeObjectUnits(config._i);
17240
config._a = map([i.year, i.month, i.day || i.date, i.hour, i.minute, i.second, i.millisecond], function (obj) {
17241
return obj && parseInt(obj, 10);
17242
});
17243
17244
configFromArray(config);
17245
}
17246
17247
function createFromConfig (config) {
17248
var res = new Moment(checkOverflow(prepareConfig(config)));
17249
if (res._nextDay) {
17250
// Adding is smart enough around DST
17251
res.add(1, 'd');
17252
res._nextDay = undefined;
17253
}
17254
17255
return res;
17256
}
17257
17258
function prepareConfig (config) {
17259
var input = config._i,
17260
format = config._f;
17261
17262
config._locale = config._locale || getLocale(config._l);
17263
17264
if (input === null || (format === undefined && input === '')) {
17265
return createInvalid({nullInput: true});
17266
}
17267
17268
if (typeof input === 'string') {
17269
config._i = input = config._locale.preparse(input);
17270
}
17271
17272
if (isMoment(input)) {
17273
return new Moment(checkOverflow(input));
17274
} else if (isDate(input)) {
17275
config._d = input;
17276
} else if (isArray(format)) {
17277
configFromStringAndArray(config);
17278
} else if (format) {
17279
configFromStringAndFormat(config);
17280
} else {
17281
configFromInput(config);
17282
}
17283
17284
if (!isValid(config)) {
17285
config._d = null;
17286
}
17287
17288
return config;
17289
}
17290
17291
function configFromInput(config) {
17292
var input = config._i;
17293
if (isUndefined(input)) {
17294
config._d = new Date(hooks.now());
17295
} else if (isDate(input)) {
17296
config._d = new Date(input.valueOf());
17297
} else if (typeof input === 'string') {
17298
configFromString(config);
17299
} else if (isArray(input)) {
17300
config._a = map(input.slice(0), function (obj) {
17301
return parseInt(obj, 10);
17302
});
17303
configFromArray(config);
17304
} else if (isObject(input)) {
17305
configFromObject(config);
17306
} else if (isNumber(input)) {
17307
// from milliseconds
17308
config._d = new Date(input);
17309
} else {
17310
hooks.createFromInputFallback(config);
17311
}
17312
}
17313
17314
function createLocalOrUTC (input, format, locale, strict, isUTC) {
17315
var c = {};
17316
17317
if (locale === true || locale === false) {
17318
strict = locale;
17319
locale = undefined;
17320
}
17321
17322
if ((isObject(input) && isObjectEmpty(input)) ||
17323
(isArray(input) && input.length === 0)) {
17324
input = undefined;
17325
}
17326
// object construction must be done this way.
17327
// https://github.com/moment/moment/issues/1423
17328
c._isAMomentObject = true;
17329
c._useUTC = c._isUTC = isUTC;
17330
c._l = locale;
17331
c._i = input;
17332
c._f = format;
17333
c._strict = strict;
17334
17335
return createFromConfig(c);
17336
}
17337
17338
function createLocal (input, format, locale, strict) {
17339
return createLocalOrUTC(input, format, locale, strict, false);
17340
}
17341
17342
var prototypeMin = deprecate(
17343
'moment().min is deprecated, use moment.max instead. http://momentjs.com/guides/#/warnings/min-max/',
17344
function () {
17345
var other = createLocal.apply(null, arguments);
17346
if (this.isValid() && other.isValid()) {
17347
return other < this ? this : other;
17348
} else {
17349
return createInvalid();
17350
}
17351
}
17352
);
17353
17354
var prototypeMax = deprecate(
17355
'moment().max is deprecated, use moment.min instead. http://momentjs.com/guides/#/warnings/min-max/',
17356
function () {
17357
var other = createLocal.apply(null, arguments);
17358
if (this.isValid() && other.isValid()) {
17359
return other > this ? this : other;
17360
} else {
17361
return createInvalid();
17362
}
17363
}
17364
);
17365
17366
// Pick a moment m from moments so that m[fn](other) is true for all
17367
// other. This relies on the function fn to be transitive.
17368
//
17369
// moments should either be an array of moment objects or an array, whose
17370
// first element is an array of moment objects.
17371
function pickBy(fn, moments) {
17372
var res, i;
17373
if (moments.length === 1 && isArray(moments[0])) {
17374
moments = moments[0];
17375
}
17376
if (!moments.length) {
17377
return createLocal();
17378
}
17379
res = moments[0];
17380
for (i = 1; i < moments.length; ++i) {
17381
if (!moments[i].isValid() || moments[i][fn](res)) {
17382
res = moments[i];
17383
}
17384
}
17385
return res;
17386
}
17387
17388
// TODO: Use [].sort instead?
17389
function min () {
17390
var args = [].slice.call(arguments, 0);
17391
17392
return pickBy('isBefore', args);
17393
}
17394
17395
function max () {
17396
var args = [].slice.call(arguments, 0);
17397
17398
return pickBy('isAfter', args);
17399
}
17400
17401
var now = function () {
17402
return Date.now ? Date.now() : +(new Date());
17403
};
17404
17405
var ordering = ['year', 'quarter', 'month', 'week', 'day', 'hour', 'minute', 'second', 'millisecond'];
17406
17407
function isDurationValid(m) {
17408
for (var key in m) {
17409
if (!(indexOf.call(ordering, key) !== -1 && (m[key] == null || !isNaN(m[key])))) {
17410
return false;
17411
}
17412
}
17413
17414
var unitHasDecimal = false;
17415
for (var i = 0; i < ordering.length; ++i) {
17416
if (m[ordering[i]]) {
17417
if (unitHasDecimal) {
17418
return false; // only allow non-integers for smallest unit
17419
}
17420
if (parseFloat(m[ordering[i]]) !== toInt(m[ordering[i]])) {
17421
unitHasDecimal = true;
17422
}
17423
}
17424
}
17425
17426
return true;
17427
}
17428
17429
function isValid$1() {
17430
return this._isValid;
17431
}
17432
17433
function createInvalid$1() {
17434
return createDuration(NaN);
17435
}
17436
17437
function Duration (duration) {
17438
var normalizedInput = normalizeObjectUnits(duration),
17439
years = normalizedInput.year || 0,
17440
quarters = normalizedInput.quarter || 0,
17441
months = normalizedInput.month || 0,
17442
weeks = normalizedInput.week || normalizedInput.isoWeek || 0,
17443
days = normalizedInput.day || 0,
17444
hours = normalizedInput.hour || 0,
17445
minutes = normalizedInput.minute || 0,
17446
seconds = normalizedInput.second || 0,
17447
milliseconds = normalizedInput.millisecond || 0;
17448
17449
this._isValid = isDurationValid(normalizedInput);
17450
17451
// representation for dateAddRemove
17452
this._milliseconds = +milliseconds +
17453
seconds * 1e3 + // 1000
17454
minutes * 6e4 + // 1000 * 60
17455
hours * 1000 * 60 * 60; //using 1000 * 60 * 60 instead of 36e5 to avoid floating point rounding errors https://github.com/moment/moment/issues/2978
17456
// Because of dateAddRemove treats 24 hours as different from a
17457
// day when working around DST, we need to store them separately
17458
this._days = +days +
17459
weeks * 7;
17460
// It is impossible to translate months into days without knowing
17461
// which months you are are talking about, so we have to store
17462
// it separately.
17463
this._months = +months +
17464
quarters * 3 +
17465
years * 12;
17466
17467
this._data = {};
17468
17469
this._locale = getLocale();
17470
17471
this._bubble();
17472
}
17473
17474
function isDuration (obj) {
17475
return obj instanceof Duration;
17476
}
17477
17478
function absRound (number) {
17479
if (number < 0) {
17480
return Math.round(-1 * number) * -1;
17481
} else {
17482
return Math.round(number);
17483
}
17484
}
17485
17486
// FORMATTING
17487
17488
function offset (token, separator) {
17489
addFormatToken(token, 0, 0, function () {
17490
var offset = this.utcOffset();
17491
var sign = '+';
17492
if (offset < 0) {
17493
offset = -offset;
17494
sign = '-';
17495
}
17496
return sign + zeroFill(~~(offset / 60), 2) + separator + zeroFill(~~(offset) % 60, 2);
17497
});
17498
}
17499
17500
offset('Z', ':');
17501
offset('ZZ', '');
17502
17503
// PARSING
17504
17505
addRegexToken('Z', matchShortOffset);
17506
addRegexToken('ZZ', matchShortOffset);
17507
addParseToken(['Z', 'ZZ'], function (input, array, config) {
17508
config._useUTC = true;
17509
config._tzm = offsetFromString(matchShortOffset, input);
17510
});
17511
17512
// HELPERS
17513
17514
// timezone chunker
17515
// '+10:00' > ['10', '00']
17516
// '-1530' > ['-15', '30']
17517
var chunkOffset = /([\+\-]|\d\d)/gi;
17518
17519
function offsetFromString(matcher, string) {
17520
var matches = (string || '').match(matcher);
17521
17522
if (matches === null) {
17523
return null;
17524
}
17525
17526
var chunk = matches[matches.length - 1] || [];
17527
var parts = (chunk + '').match(chunkOffset) || ['-', 0, 0];
17528
var minutes = +(parts[1] * 60) + toInt(parts[2]);
17529
17530
return minutes === 0 ?
17531
0 :
17532
parts[0] === '+' ? minutes : -minutes;
17533
}
17534
17535
// Return a moment from input, that is local/utc/zone equivalent to model.
17536
function cloneWithOffset(input, model) {
17537
var res, diff;
17538
if (model._isUTC) {
17539
res = model.clone();
17540
diff = (isMoment(input) || isDate(input) ? input.valueOf() : createLocal(input).valueOf()) - res.valueOf();
17541
// Use low-level api, because this fn is low-level api.
17542
res._d.setTime(res._d.valueOf() + diff);
17543
hooks.updateOffset(res, false);
17544
return res;
17545
} else {
17546
return createLocal(input).local();
17547
}
17548
}
17549
17550
function getDateOffset (m) {
17551
// On Firefox.24 Date#getTimezoneOffset returns a floating point.
17552
// https://github.com/moment/moment/pull/1871
17553
return -Math.round(m._d.getTimezoneOffset() / 15) * 15;
17554
}
17555
17556
// HOOKS
17557
17558
// This function will be called whenever a moment is mutated.
17559
// It is intended to keep the offset in sync with the timezone.
17560
hooks.updateOffset = function () {};
17561
17562
// MOMENTS
17563
17564
// keepLocalTime = true means only change the timezone, without
17565
// affecting the local hour. So 5:31:26 +0300 --[utcOffset(2, true)]-->
17566
// 5:31:26 +0200 It is possible that 5:31:26 doesn't exist with offset
17567
// +0200, so we adjust the time as needed, to be valid.
17568
//
17569
// Keeping the time actually adds/subtracts (one hour)
17570
// from the actual represented time. That is why we call updateOffset
17571
// a second time. In case it wants us to change the offset again
17572
// _changeInProgress == true case, then we have to adjust, because
17573
// there is no such time in the given timezone.
17574
function getSetOffset (input, keepLocalTime, keepMinutes) {
17575
var offset = this._offset || 0,
17576
localAdjust;
17577
if (!this.isValid()) {
17578
return input != null ? this : NaN;
17579
}
17580
if (input != null) {
17581
if (typeof input === 'string') {
17582
input = offsetFromString(matchShortOffset, input);
17583
if (input === null) {
17584
return this;
17585
}
17586
} else if (Math.abs(input) < 16 && !keepMinutes) {
17587
input = input * 60;
17588
}
17589
if (!this._isUTC && keepLocalTime) {
17590
localAdjust = getDateOffset(this);
17591
}
17592
this._offset = input;
17593
this._isUTC = true;
17594
if (localAdjust != null) {
17595
this.add(localAdjust, 'm');
17596
}
17597
if (offset !== input) {
17598
if (!keepLocalTime || this._changeInProgress) {
17599
addSubtract(this, createDuration(input - offset, 'm'), 1, false);
17600
} else if (!this._changeInProgress) {
17601
this._changeInProgress = true;
17602
hooks.updateOffset(this, true);
17603
this._changeInProgress = null;
17604
}
17605
}
17606
return this;
17607
} else {
17608
return this._isUTC ? offset : getDateOffset(this);
17609
}
17610
}
17611
17612
function getSetZone (input, keepLocalTime) {
17613
if (input != null) {
17614
if (typeof input !== 'string') {
17615
input = -input;
17616
}
17617
17618
this.utcOffset(input, keepLocalTime);
17619
17620
return this;
17621
} else {
17622
return -this.utcOffset();
17623
}
17624
}
17625
17626
function setOffsetToUTC (keepLocalTime) {
17627
return this.utcOffset(0, keepLocalTime);
17628
}
17629
17630
function setOffsetToLocal (keepLocalTime) {
17631
if (this._isUTC) {
17632
this.utcOffset(0, keepLocalTime);
17633
this._isUTC = false;
17634
17635
if (keepLocalTime) {
17636
this.subtract(getDateOffset(this), 'm');
17637
}
17638
}
17639
return this;
17640
}
17641
17642
function setOffsetToParsedOffset () {
17643
if (this._tzm != null) {
17644
this.utcOffset(this._tzm, false, true);
17645
} else if (typeof this._i === 'string') {
17646
var tZone = offsetFromString(matchOffset, this._i);
17647
if (tZone != null) {
17648
this.utcOffset(tZone);
17649
}
17650
else {
17651
this.utcOffset(0, true);
17652
}
17653
}
17654
return this;
17655
}
17656
17657
function hasAlignedHourOffset (input) {
17658
if (!this.isValid()) {
17659
return false;
17660
}
17661
input = input ? createLocal(input).utcOffset() : 0;
17662
17663
return (this.utcOffset() - input) % 60 === 0;
17664
}
17665
17666
function isDaylightSavingTime () {
17667
return (
17668
this.utcOffset() > this.clone().month(0).utcOffset() ||
17669
this.utcOffset() > this.clone().month(5).utcOffset()
17670
);
17671
}
17672
17673
function isDaylightSavingTimeShifted () {
17674
if (!isUndefined(this._isDSTShifted)) {
17675
return this._isDSTShifted;
17676
}
17677
17678
var c = {};
17679
17680
copyConfig(c, this);
17681
c = prepareConfig(c);
17682
17683
if (c._a) {
17684
var other = c._isUTC ? createUTC(c._a) : createLocal(c._a);
17685
this._isDSTShifted = this.isValid() &&
17686
compareArrays(c._a, other.toArray()) > 0;
17687
} else {
17688
this._isDSTShifted = false;
17689
}
17690
17691
return this._isDSTShifted;
17692
}
17693
17694
function isLocal () {
17695
return this.isValid() ? !this._isUTC : false;
17696
}
17697
17698
function isUtcOffset () {
17699
return this.isValid() ? this._isUTC : false;
17700
}
17701
17702
function isUtc () {
17703
return this.isValid() ? this._isUTC && this._offset === 0 : false;
17704
}
17705
17706
// ASP.NET json date format regex
17707
var aspNetRegex = /^(\-|\+)?(?:(\d*)[. ])?(\d+)\:(\d+)(?:\:(\d+)(\.\d*)?)?$/;
17708
17709
// from http://docs.closure-library.googlecode.com/git/closure_goog_date_date.js.source.html
17710
// somewhat more in line with 4.4.3.2 2004 spec, but allows decimal anywhere
17711
// and further modified to allow for strings containing both week and day
17712
var isoRegex = /^(-|\+)?P(?:([-+]?[0-9,.]*)Y)?(?:([-+]?[0-9,.]*)M)?(?:([-+]?[0-9,.]*)W)?(?:([-+]?[0-9,.]*)D)?(?:T(?:([-+]?[0-9,.]*)H)?(?:([-+]?[0-9,.]*)M)?(?:([-+]?[0-9,.]*)S)?)?$/;
17713
17714
function createDuration (input, key) {
17715
var duration = input,
17716
// matching against regexp is expensive, do it on demand
17717
match = null,
17718
sign,
17719
ret,
17720
diffRes;
17721
17722
if (isDuration(input)) {
17723
duration = {
17724
ms : input._milliseconds,
17725
d : input._days,
17726
M : input._months
17727
};
17728
} else if (isNumber(input)) {
17729
duration = {};
17730
if (key) {
17731
duration[key] = input;
17732
} else {
17733
duration.milliseconds = input;
17734
}
17735
} else if (!!(match = aspNetRegex.exec(input))) {
17736
sign = (match[1] === '-') ? -1 : 1;
17737
duration = {
17738
y : 0,
17739
d : toInt(match[DATE]) * sign,
17740
h : toInt(match[HOUR]) * sign,
17741
m : toInt(match[MINUTE]) * sign,
17742
s : toInt(match[SECOND]) * sign,
17743
ms : toInt(absRound(match[MILLISECOND] * 1000)) * sign // the millisecond decimal point is included in the match
17744
};
17745
} else if (!!(match = isoRegex.exec(input))) {
17746
sign = (match[1] === '-') ? -1 : 1;
17747
duration = {
17748
y : parseIso(match[2], sign),
17749
M : parseIso(match[3], sign),
17750
w : parseIso(match[4], sign),
17751
d : parseIso(match[5], sign),
17752
h : parseIso(match[6], sign),
17753
m : parseIso(match[7], sign),
17754
s : parseIso(match[8], sign)
17755
};
17756
} else if (duration == null) {// checks for null or undefined
17757
duration = {};
17758
} else if (typeof duration === 'object' && ('from' in duration || 'to' in duration)) {
17759
diffRes = momentsDifference(createLocal(duration.from), createLocal(duration.to));
17760
17761
duration = {};
17762
duration.ms = diffRes.milliseconds;
17763
duration.M = diffRes.months;
17764
}
17765
17766
ret = new Duration(duration);
17767
17768
if (isDuration(input) && hasOwnProp(input, '_locale')) {
17769
ret._locale = input._locale;
17770
}
17771
17772
return ret;
17773
}
17774
17775
createDuration.fn = Duration.prototype;
17776
createDuration.invalid = createInvalid$1;
17777
17778
function parseIso (inp, sign) {
17779
// We'd normally use ~~inp for this, but unfortunately it also
17780
// converts floats to ints.
17781
// inp may be undefined, so careful calling replace on it.
17782
var res = inp && parseFloat(inp.replace(',', '.'));
17783
// apply sign while we're at it
17784
return (isNaN(res) ? 0 : res) * sign;
17785
}
17786
17787
function positiveMomentsDifference(base, other) {
17788
var res = {};
17789
17790
res.months = other.month() - base.month() +
17791
(other.year() - base.year()) * 12;
17792
if (base.clone().add(res.months, 'M').isAfter(other)) {
17793
--res.months;
17794
}
17795
17796
res.milliseconds = +other - +(base.clone().add(res.months, 'M'));
17797
17798
return res;
17799
}
17800
17801
function momentsDifference(base, other) {
17802
var res;
17803
if (!(base.isValid() && other.isValid())) {
17804
return {milliseconds: 0, months: 0};
17805
}
17806
17807
other = cloneWithOffset(other, base);
17808
if (base.isBefore(other)) {
17809
res = positiveMomentsDifference(base, other);
17810
} else {
17811
res = positiveMomentsDifference(other, base);
17812
res.milliseconds = -res.milliseconds;
17813
res.months = -res.months;
17814
}
17815
17816
return res;
17817
}
17818
17819
// TODO: remove 'name' arg after deprecation is removed
17820
function createAdder(direction, name) {
17821
return function (val, period) {
17822
var dur, tmp;
17823
//invert the arguments, but complain about it
17824
if (period !== null && !isNaN(+period)) {
17825
deprecateSimple(name, 'moment().' + name + '(period, number) is deprecated. Please use moment().' + name + '(number, period). ' +
17826
'See http://momentjs.com/guides/#/warnings/add-inverted-param/ for more info.');
17827
tmp = val; val = period; period = tmp;
17828
}
17829
17830
val = typeof val === 'string' ? +val : val;
17831
dur = createDuration(val, period);
17832
addSubtract(this, dur, direction);
17833
return this;
17834
};
17835
}
17836
17837
function addSubtract (mom, duration, isAdding, updateOffset) {
17838
var milliseconds = duration._milliseconds,
17839
days = absRound(duration._days),
17840
months = absRound(duration._months);
17841
17842
if (!mom.isValid()) {
17843
// No op
17844
return;
17845
}
17846
17847
updateOffset = updateOffset == null ? true : updateOffset;
17848
17849
if (months) {
17850
setMonth(mom, get(mom, 'Month') + months * isAdding);
17851
}
17852
if (days) {
17853
set$1(mom, 'Date', get(mom, 'Date') + days * isAdding);
17854
}
17855
if (milliseconds) {
17856
mom._d.setTime(mom._d.valueOf() + milliseconds * isAdding);
17857
}
17858
if (updateOffset) {
17859
hooks.updateOffset(mom, days || months);
17860
}
17861
}
17862
17863
var add = createAdder(1, 'add');
17864
var subtract = createAdder(-1, 'subtract');
17865
17866
function getCalendarFormat(myMoment, now) {
17867
var diff = myMoment.diff(now, 'days', true);
17868
return diff < -6 ? 'sameElse' :
17869
diff < -1 ? 'lastWeek' :
17870
diff < 0 ? 'lastDay' :
17871
diff < 1 ? 'sameDay' :
17872
diff < 2 ? 'nextDay' :
17873
diff < 7 ? 'nextWeek' : 'sameElse';
17874
}
17875
17876
function calendar$1 (time, formats) {
17877
// We want to compare the start of today, vs this.
17878
// Getting start-of-today depends on whether we're local/utc/offset or not.
17879
var now = time || createLocal(),
17880
sod = cloneWithOffset(now, this).startOf('day'),
17881
format = hooks.calendarFormat(this, sod) || 'sameElse';
17882
17883
var output = formats && (isFunction(formats[format]) ? formats[format].call(this, now) : formats[format]);
17884
17885
return this.format(output || this.localeData().calendar(format, this, createLocal(now)));
17886
}
17887
17888
function clone () {
17889
return new Moment(this);
17890
}
17891
17892
function isAfter (input, units) {
17893
var localInput = isMoment(input) ? input : createLocal(input);
17894
if (!(this.isValid() && localInput.isValid())) {
17895
return false;
17896
}
17897
units = normalizeUnits(units) || 'millisecond';
17898
if (units === 'millisecond') {
17899
return this.valueOf() > localInput.valueOf();
17900
} else {
17901
return localInput.valueOf() < this.clone().startOf(units).valueOf();
17902
}
17903
}
17904
17905
function isBefore (input, units) {
17906
var localInput = isMoment(input) ? input : createLocal(input);
17907
if (!(this.isValid() && localInput.isValid())) {
17908
return false;
17909
}
17910
units = normalizeUnits(units) || 'millisecond';
17911
if (units === 'millisecond') {
17912
return this.valueOf() < localInput.valueOf();
17913
} else {
17914
return this.clone().endOf(units).valueOf() < localInput.valueOf();
17915
}
17916
}
17917
17918
function isBetween (from, to, units, inclusivity) {
17919
var localFrom = isMoment(from) ? from : createLocal(from),
17920
localTo = isMoment(to) ? to : createLocal(to);
17921
if (!(this.isValid() && localFrom.isValid() && localTo.isValid())) {
17922
return false;
17923
}
17924
inclusivity = inclusivity || '()';
17925
return (inclusivity[0] === '(' ? this.isAfter(localFrom, units) : !this.isBefore(localFrom, units)) &&
17926
(inclusivity[1] === ')' ? this.isBefore(localTo, units) : !this.isAfter(localTo, units));
17927
}
17928
17929
function isSame (input, units) {
17930
var localInput = isMoment(input) ? input : createLocal(input),
17931
inputMs;
17932
if (!(this.isValid() && localInput.isValid())) {
17933
return false;
17934
}
17935
units = normalizeUnits(units) || 'millisecond';
17936
if (units === 'millisecond') {
17937
return this.valueOf() === localInput.valueOf();
17938
} else {
17939
inputMs = localInput.valueOf();
17940
return this.clone().startOf(units).valueOf() <= inputMs && inputMs <= this.clone().endOf(units).valueOf();
17941
}
17942
}
17943
17944
function isSameOrAfter (input, units) {
17945
return this.isSame(input, units) || this.isAfter(input, units);
17946
}
17947
17948
function isSameOrBefore (input, units) {
17949
return this.isSame(input, units) || this.isBefore(input, units);
17950
}
17951
17952
function diff (input, units, asFloat) {
17953
var that,
17954
zoneDelta,
17955
output;
17956
17957
if (!this.isValid()) {
17958
return NaN;
17959
}
17960
17961
that = cloneWithOffset(input, this);
17962
17963
if (!that.isValid()) {
17964
return NaN;
17965
}
17966
17967
zoneDelta = (that.utcOffset() - this.utcOffset()) * 6e4;
17968
17969
units = normalizeUnits(units);
17970
17971
switch (units) {
17972
case 'year': output = monthDiff(this, that) / 12; break;
17973
case 'month': output = monthDiff(this, that); break;
17974
case 'quarter': output = monthDiff(this, that) / 3; break;
17975
case 'second': output = (this - that) / 1e3; break; // 1000
17976
case 'minute': output = (this - that) / 6e4; break; // 1000 * 60
17977
case 'hour': output = (this - that) / 36e5; break; // 1000 * 60 * 60
17978
case 'day': output = (this - that - zoneDelta) / 864e5; break; // 1000 * 60 * 60 * 24, negate dst
17979
case 'week': output = (this - that - zoneDelta) / 6048e5; break; // 1000 * 60 * 60 * 24 * 7, negate dst
17980
default: output = this - that;
17981
}
17982
17983
return asFloat ? output : absFloor(output);
17984
}
17985
17986
function monthDiff (a, b) {
17987
// difference in months
17988
var wholeMonthDiff = ((b.year() - a.year()) * 12) + (b.month() - a.month()),
17989
// b is in (anchor - 1 month, anchor + 1 month)
17990
anchor = a.clone().add(wholeMonthDiff, 'months'),
17991
anchor2, adjust;
17992
17993
if (b - anchor < 0) {
17994
anchor2 = a.clone().add(wholeMonthDiff - 1, 'months');
17995
// linear across the month
17996
adjust = (b - anchor) / (anchor - anchor2);
17997
} else {
17998
anchor2 = a.clone().add(wholeMonthDiff + 1, 'months');
17999
// linear across the month
18000
adjust = (b - anchor) / (anchor2 - anchor);
18001
}
18002
18003
//check for negative zero, return zero if negative zero
18004
return -(wholeMonthDiff + adjust) || 0;
18005
}
18006
18007
hooks.defaultFormat = 'YYYY-MM-DDTHH:mm:ssZ';
18008
hooks.defaultFormatUtc = 'YYYY-MM-DDTHH:mm:ss[Z]';
18009
18010
function toString () {
18011
return this.clone().locale('en').format('ddd MMM DD YYYY HH:mm:ss [GMT]ZZ');
18012
}
18013
18014
function toISOString(keepOffset) {
18015
if (!this.isValid()) {
18016
return null;
18017
}
18018
var utc = keepOffset !== true;
18019
var m = utc ? this.clone().utc() : this;
18020
if (m.year() < 0 || m.year() > 9999) {
18021
return formatMoment(m, utc ? 'YYYYYY-MM-DD[T]HH:mm:ss.SSS[Z]' : 'YYYYYY-MM-DD[T]HH:mm:ss.SSSZ');
18022
}
18023
if (isFunction(Date.prototype.toISOString)) {
18024
// native implementation is ~50x faster, use it when we can
18025
if (utc) {
18026
return this.toDate().toISOString();
18027
} else {
18028
return new Date(this.valueOf() + this.utcOffset() * 60 * 1000).toISOString().replace('Z', formatMoment(m, 'Z'));
18029
}
18030
}
18031
return formatMoment(m, utc ? 'YYYY-MM-DD[T]HH:mm:ss.SSS[Z]' : 'YYYY-MM-DD[T]HH:mm:ss.SSSZ');
18032
}
18033
18034
/**
18035
* Return a human readable representation of a moment that can
18036
* also be evaluated to get a new moment which is the same
18037
*
18038
* @link https://nodejs.org/dist/latest/docs/api/util.html#util_custom_inspect_function_on_objects
18039
*/
18040
function inspect () {
18041
if (!this.isValid()) {
18042
return 'moment.invalid(/* ' + this._i + ' */)';
18043
}
18044
var func = 'moment';
18045
var zone = '';
18046
if (!this.isLocal()) {
18047
func = this.utcOffset() === 0 ? 'moment.utc' : 'moment.parseZone';
18048
zone = 'Z';
18049
}
18050
var prefix = '[' + func + '("]';
18051
var year = (0 <= this.year() && this.year() <= 9999) ? 'YYYY' : 'YYYYYY';
18052
var datetime = '-MM-DD[T]HH:mm:ss.SSS';
18053
var suffix = zone + '[")]';
18054
18055
return this.format(prefix + year + datetime + suffix);
18056
}
18057
18058
function format (inputString) {
18059
if (!inputString) {
18060
inputString = this.isUtc() ? hooks.defaultFormatUtc : hooks.defaultFormat;
18061
}
18062
var output = formatMoment(this, inputString);
18063
return this.localeData().postformat(output);
18064
}
18065
18066
function from (time, withoutSuffix) {
18067
if (this.isValid() &&
18068
((isMoment(time) && time.isValid()) ||
18069
createLocal(time).isValid())) {
18070
return createDuration({to: this, from: time}).locale(this.locale()).humanize(!withoutSuffix);
18071
} else {
18072
return this.localeData().invalidDate();
18073
}
18074
}
18075
18076
function fromNow (withoutSuffix) {
18077
return this.from(createLocal(), withoutSuffix);
18078
}
18079
18080
function to (time, withoutSuffix) {
18081
if (this.isValid() &&
18082
((isMoment(time) && time.isValid()) ||
18083
createLocal(time).isValid())) {
18084
return createDuration({from: this, to: time}).locale(this.locale()).humanize(!withoutSuffix);
18085
} else {
18086
return this.localeData().invalidDate();
18087
}
18088
}
18089
18090
function toNow (withoutSuffix) {
18091
return this.to(createLocal(), withoutSuffix);
18092
}
18093
18094
// If passed a locale key, it will set the locale for this
18095
// instance. Otherwise, it will return the locale configuration
18096
// variables for this instance.
18097
function locale (key) {
18098
var newLocaleData;
18099
18100
if (key === undefined) {
18101
return this._locale._abbr;
18102
} else {
18103
newLocaleData = getLocale(key);
18104
if (newLocaleData != null) {
18105
this._locale = newLocaleData;
18106
}
18107
return this;
18108
}
18109
}
18110
18111
var lang = deprecate(
18112
'moment().lang() is deprecated. Instead, use moment().localeData() to get the language configuration. Use moment().locale() to change languages.',
18113
function (key) {
18114
if (key === undefined) {
18115
return this.localeData();
18116
} else {
18117
return this.locale(key);
18118
}
18119
}
18120
);
18121
18122
function localeData () {
18123
return this._locale;
18124
}
18125
18126
var MS_PER_SECOND = 1000;
18127
var MS_PER_MINUTE = 60 * MS_PER_SECOND;
18128
var MS_PER_HOUR = 60 * MS_PER_MINUTE;
18129
var MS_PER_400_YEARS = (365 * 400 + 97) * 24 * MS_PER_HOUR;
18130
18131
// actual modulo - handles negative numbers (for dates before 1970):
18132
function mod$1(dividend, divisor) {
18133
return (dividend % divisor + divisor) % divisor;
18134
}
18135
18136
function localStartOfDate(y, m, d) {
18137
// the date constructor remaps years 0-99 to 1900-1999
18138
if (y < 100 && y >= 0) {
18139
// preserve leap years using a full 400 year cycle, then reset
18140
return new Date(y + 400, m, d) - MS_PER_400_YEARS;
18141
} else {
18142
return new Date(y, m, d).valueOf();
18143
}
18144
}
18145
18146
function utcStartOfDate(y, m, d) {
18147
// Date.UTC remaps years 0-99 to 1900-1999
18148
if (y < 100 && y >= 0) {
18149
// preserve leap years using a full 400 year cycle, then reset
18150
return Date.UTC(y + 400, m, d) - MS_PER_400_YEARS;
18151
} else {
18152
return Date.UTC(y, m, d);
18153
}
18154
}
18155
18156
function startOf (units) {
18157
var time;
18158
units = normalizeUnits(units);
18159
if (units === undefined || units === 'millisecond' || !this.isValid()) {
18160
return this;
18161
}
18162
18163
var startOfDate = this._isUTC ? utcStartOfDate : localStartOfDate;
18164
18165
switch (units) {
18166
case 'year':
18167
time = startOfDate(this.year(), 0, 1);
18168
break;
18169
case 'quarter':
18170
time = startOfDate(this.year(), this.month() - this.month() % 3, 1);
18171
break;
18172
case 'month':
18173
time = startOfDate(this.year(), this.month(), 1);
18174
break;
18175
case 'week':
18176
time = startOfDate(this.year(), this.month(), this.date() - this.weekday());
18177
break;
18178
case 'isoWeek':
18179
time = startOfDate(this.year(), this.month(), this.date() - (this.isoWeekday() - 1));
18180
break;
18181
case 'day':
18182
case 'date':
18183
time = startOfDate(this.year(), this.month(), this.date());
18184
break;
18185
case 'hour':
18186
time = this._d.valueOf();
18187
time -= mod$1(time + (this._isUTC ? 0 : this.utcOffset() * MS_PER_MINUTE), MS_PER_HOUR);
18188
break;
18189
case 'minute':
18190
time = this._d.valueOf();
18191
time -= mod$1(time, MS_PER_MINUTE);
18192
break;
18193
case 'second':
18194
time = this._d.valueOf();
18195
time -= mod$1(time, MS_PER_SECOND);
18196
break;
18197
}
18198
18199
this._d.setTime(time);
18200
hooks.updateOffset(this, true);
18201
return this;
18202
}
18203
18204
function endOf (units) {
18205
var time;
18206
units = normalizeUnits(units);
18207
if (units === undefined || units === 'millisecond' || !this.isValid()) {
18208
return this;
18209
}
18210
18211
var startOfDate = this._isUTC ? utcStartOfDate : localStartOfDate;
18212
18213
switch (units) {
18214
case 'year':
18215
time = startOfDate(this.year() + 1, 0, 1) - 1;
18216
break;
18217
case 'quarter':
18218
time = startOfDate(this.year(), this.month() - this.month() % 3 + 3, 1) - 1;
18219
break;
18220
case 'month':
18221
time = startOfDate(this.year(), this.month() + 1, 1) - 1;
18222
break;
18223
case 'week':
18224
time = startOfDate(this.year(), this.month(), this.date() - this.weekday() + 7) - 1;
18225
break;
18226
case 'isoWeek':
18227
time = startOfDate(this.year(), this.month(), this.date() - (this.isoWeekday() - 1) + 7) - 1;
18228
break;
18229
case 'day':
18230
case 'date':
18231
time = startOfDate(this.year(), this.month(), this.date() + 1) - 1;
18232
break;
18233
case 'hour':
18234
time = this._d.valueOf();
18235
time += MS_PER_HOUR - mod$1(time + (this._isUTC ? 0 : this.utcOffset() * MS_PER_MINUTE), MS_PER_HOUR) - 1;
18236
break;
18237
case 'minute':
18238
time = this._d.valueOf();
18239
time += MS_PER_MINUTE - mod$1(time, MS_PER_MINUTE) - 1;
18240
break;
18241
case 'second':
18242
time = this._d.valueOf();
18243
time += MS_PER_SECOND - mod$1(time, MS_PER_SECOND) - 1;
18244
break;
18245
}
18246
18247
this._d.setTime(time);
18248
hooks.updateOffset(this, true);
18249
return this;
18250
}
18251
18252
function valueOf () {
18253
return this._d.valueOf() - ((this._offset || 0) * 60000);
18254
}
18255
18256
function unix () {
18257
return Math.floor(this.valueOf() / 1000);
18258
}
18259
18260
function toDate () {
18261
return new Date(this.valueOf());
18262
}
18263
18264
function toArray () {
18265
var m = this;
18266
return [m.year(), m.month(), m.date(), m.hour(), m.minute(), m.second(), m.millisecond()];
18267
}
18268
18269
function toObject () {
18270
var m = this;
18271
return {
18272
years: m.year(),
18273
months: m.month(),
18274
date: m.date(),
18275
hours: m.hours(),
18276
minutes: m.minutes(),
18277
seconds: m.seconds(),
18278
milliseconds: m.milliseconds()
18279
};
18280
}
18281
18282
function toJSON () {
18283
// new Date(NaN).toJSON() === null
18284
return this.isValid() ? this.toISOString() : null;
18285
}
18286
18287
function isValid$2 () {
18288
return isValid(this);
18289
}
18290
18291
function parsingFlags () {
18292
return extend({}, getParsingFlags(this));
18293
}
18294
18295
function invalidAt () {
18296
return getParsingFlags(this).overflow;
18297
}
18298
18299
function creationData() {
18300
return {
18301
input: this._i,
18302
format: this._f,
18303
locale: this._locale,
18304
isUTC: this._isUTC,
18305
strict: this._strict
18306
};
18307
}
18308
18309
// FORMATTING
18310
18311
addFormatToken(0, ['gg', 2], 0, function () {
18312
return this.weekYear() % 100;
18313
});
18314
18315
addFormatToken(0, ['GG', 2], 0, function () {
18316
return this.isoWeekYear() % 100;
18317
});
18318
18319
function addWeekYearFormatToken (token, getter) {
18320
addFormatToken(0, [token, token.length], 0, getter);
18321
}
18322
18323
addWeekYearFormatToken('gggg', 'weekYear');
18324
addWeekYearFormatToken('ggggg', 'weekYear');
18325
addWeekYearFormatToken('GGGG', 'isoWeekYear');
18326
addWeekYearFormatToken('GGGGG', 'isoWeekYear');
18327
18328
// ALIASES
18329
18330
addUnitAlias('weekYear', 'gg');
18331
addUnitAlias('isoWeekYear', 'GG');
18332
18333
// PRIORITY
18334
18335
addUnitPriority('weekYear', 1);
18336
addUnitPriority('isoWeekYear', 1);
18337
18338
18339
// PARSING
18340
18341
addRegexToken('G', matchSigned);
18342
addRegexToken('g', matchSigned);
18343
addRegexToken('GG', match1to2, match2);
18344
addRegexToken('gg', match1to2, match2);
18345
addRegexToken('GGGG', match1to4, match4);
18346
addRegexToken('gggg', match1to4, match4);
18347
addRegexToken('GGGGG', match1to6, match6);
18348
addRegexToken('ggggg', match1to6, match6);
18349
18350
addWeekParseToken(['gggg', 'ggggg', 'GGGG', 'GGGGG'], function (input, week, config, token) {
18351
week[token.substr(0, 2)] = toInt(input);
18352
});
18353
18354
addWeekParseToken(['gg', 'GG'], function (input, week, config, token) {
18355
week[token] = hooks.parseTwoDigitYear(input);
18356
});
18357
18358
// MOMENTS
18359
18360
function getSetWeekYear (input) {
18361
return getSetWeekYearHelper.call(this,
18362
input,
18363
this.week(),
18364
this.weekday(),
18365
this.localeData()._week.dow,
18366
this.localeData()._week.doy);
18367
}
18368
18369
function getSetISOWeekYear (input) {
18370
return getSetWeekYearHelper.call(this,
18371
input, this.isoWeek(), this.isoWeekday(), 1, 4);
18372
}
18373
18374
function getISOWeeksInYear () {
18375
return weeksInYear(this.year(), 1, 4);
18376
}
18377
18378
function getWeeksInYear () {
18379
var weekInfo = this.localeData()._week;
18380
return weeksInYear(this.year(), weekInfo.dow, weekInfo.doy);
18381
}
18382
18383
function getSetWeekYearHelper(input, week, weekday, dow, doy) {
18384
var weeksTarget;
18385
if (input == null) {
18386
return weekOfYear(this, dow, doy).year;
18387
} else {
18388
weeksTarget = weeksInYear(input, dow, doy);
18389
if (week > weeksTarget) {
18390
week = weeksTarget;
18391
}
18392
return setWeekAll.call(this, input, week, weekday, dow, doy);
18393
}
18394
}
18395
18396
function setWeekAll(weekYear, week, weekday, dow, doy) {
18397
var dayOfYearData = dayOfYearFromWeeks(weekYear, week, weekday, dow, doy),
18398
date = createUTCDate(dayOfYearData.year, 0, dayOfYearData.dayOfYear);
18399
18400
this.year(date.getUTCFullYear());
18401
this.month(date.getUTCMonth());
18402
this.date(date.getUTCDate());
18403
return this;
18404
}
18405
18406
// FORMATTING
18407
18408
addFormatToken('Q', 0, 'Qo', 'quarter');
18409
18410
// ALIASES
18411
18412
addUnitAlias('quarter', 'Q');
18413
18414
// PRIORITY
18415
18416
addUnitPriority('quarter', 7);
18417
18418
// PARSING
18419
18420
addRegexToken('Q', match1);
18421
addParseToken('Q', function (input, array) {
18422
array[MONTH] = (toInt(input) - 1) * 3;
18423
});
18424
18425
// MOMENTS
18426
18427
function getSetQuarter (input) {
18428
return input == null ? Math.ceil((this.month() + 1) / 3) : this.month((input - 1) * 3 + this.month() % 3);
18429
}
18430
18431
// FORMATTING
18432
18433
addFormatToken('D', ['DD', 2], 'Do', 'date');
18434
18435
// ALIASES
18436
18437
addUnitAlias('date', 'D');
18438
18439
// PRIORITY
18440
addUnitPriority('date', 9);
18441
18442
// PARSING
18443
18444
addRegexToken('D', match1to2);
18445
addRegexToken('DD', match1to2, match2);
18446
addRegexToken('Do', function (isStrict, locale) {
18447
// TODO: Remove "ordinalParse" fallback in next major release.
18448
return isStrict ?
18449
(locale._dayOfMonthOrdinalParse || locale._ordinalParse) :
18450
locale._dayOfMonthOrdinalParseLenient;
18451
});
18452
18453
addParseToken(['D', 'DD'], DATE);
18454
addParseToken('Do', function (input, array) {
18455
array[DATE] = toInt(input.match(match1to2)[0]);
18456
});
18457
18458
// MOMENTS
18459
18460
var getSetDayOfMonth = makeGetSet('Date', true);
18461
18462
// FORMATTING
18463
18464
addFormatToken('DDD', ['DDDD', 3], 'DDDo', 'dayOfYear');
18465
18466
// ALIASES
18467
18468
addUnitAlias('dayOfYear', 'DDD');
18469
18470
// PRIORITY
18471
addUnitPriority('dayOfYear', 4);
18472
18473
// PARSING
18474
18475
addRegexToken('DDD', match1to3);
18476
addRegexToken('DDDD', match3);
18477
addParseToken(['DDD', 'DDDD'], function (input, array, config) {
18478
config._dayOfYear = toInt(input);
18479
});
18480
18481
// HELPERS
18482
18483
// MOMENTS
18484
18485
function getSetDayOfYear (input) {
18486
var dayOfYear = Math.round((this.clone().startOf('day') - this.clone().startOf('year')) / 864e5) + 1;
18487
return input == null ? dayOfYear : this.add((input - dayOfYear), 'd');
18488
}
18489
18490
// FORMATTING
18491
18492
addFormatToken('m', ['mm', 2], 0, 'minute');
18493
18494
// ALIASES
18495
18496
addUnitAlias('minute', 'm');
18497
18498
// PRIORITY
18499
18500
addUnitPriority('minute', 14);
18501
18502
// PARSING
18503
18504
addRegexToken('m', match1to2);
18505
addRegexToken('mm', match1to2, match2);
18506
addParseToken(['m', 'mm'], MINUTE);
18507
18508
// MOMENTS
18509
18510
var getSetMinute = makeGetSet('Minutes', false);
18511
18512
// FORMATTING
18513
18514
addFormatToken('s', ['ss', 2], 0, 'second');
18515
18516
// ALIASES
18517
18518
addUnitAlias('second', 's');
18519
18520
// PRIORITY
18521
18522
addUnitPriority('second', 15);
18523
18524
// PARSING
18525
18526
addRegexToken('s', match1to2);
18527
addRegexToken('ss', match1to2, match2);
18528
addParseToken(['s', 'ss'], SECOND);
18529
18530
// MOMENTS
18531
18532
var getSetSecond = makeGetSet('Seconds', false);
18533
18534
// FORMATTING
18535
18536
addFormatToken('S', 0, 0, function () {
18537
return ~~(this.millisecond() / 100);
18538
});
18539
18540
addFormatToken(0, ['SS', 2], 0, function () {
18541
return ~~(this.millisecond() / 10);
18542
});
18543
18544
addFormatToken(0, ['SSS', 3], 0, 'millisecond');
18545
addFormatToken(0, ['SSSS', 4], 0, function () {
18546
return this.millisecond() * 10;
18547
});
18548
addFormatToken(0, ['SSSSS', 5], 0, function () {
18549
return this.millisecond() * 100;
18550
});
18551
addFormatToken(0, ['SSSSSS', 6], 0, function () {
18552
return this.millisecond() * 1000;
18553
});
18554
addFormatToken(0, ['SSSSSSS', 7], 0, function () {
18555
return this.millisecond() * 10000;
18556
});
18557
addFormatToken(0, ['SSSSSSSS', 8], 0, function () {
18558
return this.millisecond() * 100000;
18559
});
18560
addFormatToken(0, ['SSSSSSSSS', 9], 0, function () {
18561
return this.millisecond() * 1000000;
18562
});
18563
18564
18565
// ALIASES
18566
18567
addUnitAlias('millisecond', 'ms');
18568
18569
// PRIORITY
18570
18571
addUnitPriority('millisecond', 16);
18572
18573
// PARSING
18574
18575
addRegexToken('S', match1to3, match1);
18576
addRegexToken('SS', match1to3, match2);
18577
addRegexToken('SSS', match1to3, match3);
18578
18579
var token;
18580
for (token = 'SSSS'; token.length <= 9; token += 'S') {
18581
addRegexToken(token, matchUnsigned);
18582
}
18583
18584
function parseMs(input, array) {
18585
array[MILLISECOND] = toInt(('0.' + input) * 1000);
18586
}
18587
18588
for (token = 'S'; token.length <= 9; token += 'S') {
18589
addParseToken(token, parseMs);
18590
}
18591
// MOMENTS
18592
18593
var getSetMillisecond = makeGetSet('Milliseconds', false);
18594
18595
// FORMATTING
18596
18597
addFormatToken('z', 0, 0, 'zoneAbbr');
18598
addFormatToken('zz', 0, 0, 'zoneName');
18599
18600
// MOMENTS
18601
18602
function getZoneAbbr () {
18603
return this._isUTC ? 'UTC' : '';
18604
}
18605
18606
function getZoneName () {
18607
return this._isUTC ? 'Coordinated Universal Time' : '';
18608
}
18609
18610
var proto = Moment.prototype;
18611
18612
proto.add = add;
18613
proto.calendar = calendar$1;
18614
proto.clone = clone;
18615
proto.diff = diff;
18616
proto.endOf = endOf;
18617
proto.format = format;
18618
proto.from = from;
18619
proto.fromNow = fromNow;
18620
proto.to = to;
18621
proto.toNow = toNow;
18622
proto.get = stringGet;
18623
proto.invalidAt = invalidAt;
18624
proto.isAfter = isAfter;
18625
proto.isBefore = isBefore;
18626
proto.isBetween = isBetween;
18627
proto.isSame = isSame;
18628
proto.isSameOrAfter = isSameOrAfter;
18629
proto.isSameOrBefore = isSameOrBefore;
18630
proto.isValid = isValid$2;
18631
proto.lang = lang;
18632
proto.locale = locale;
18633
proto.localeData = localeData;
18634
proto.max = prototypeMax;
18635
proto.min = prototypeMin;
18636
proto.parsingFlags = parsingFlags;
18637
proto.set = stringSet;
18638
proto.startOf = startOf;
18639
proto.subtract = subtract;
18640
proto.toArray = toArray;
18641
proto.toObject = toObject;
18642
proto.toDate = toDate;
18643
proto.toISOString = toISOString;
18644
proto.inspect = inspect;
18645
proto.toJSON = toJSON;
18646
proto.toString = toString;
18647
proto.unix = unix;
18648
proto.valueOf = valueOf;
18649
proto.creationData = creationData;
18650
proto.year = getSetYear;
18651
proto.isLeapYear = getIsLeapYear;
18652
proto.weekYear = getSetWeekYear;
18653
proto.isoWeekYear = getSetISOWeekYear;
18654
proto.quarter = proto.quarters = getSetQuarter;
18655
proto.month = getSetMonth;
18656
proto.daysInMonth = getDaysInMonth;
18657
proto.week = proto.weeks = getSetWeek;
18658
proto.isoWeek = proto.isoWeeks = getSetISOWeek;
18659
proto.weeksInYear = getWeeksInYear;
18660
proto.isoWeeksInYear = getISOWeeksInYear;
18661
proto.date = getSetDayOfMonth;
18662
proto.day = proto.days = getSetDayOfWeek;
18663
proto.weekday = getSetLocaleDayOfWeek;
18664
proto.isoWeekday = getSetISODayOfWeek;
18665
proto.dayOfYear = getSetDayOfYear;
18666
proto.hour = proto.hours = getSetHour;
18667
proto.minute = proto.minutes = getSetMinute;
18668
proto.second = proto.seconds = getSetSecond;
18669
proto.millisecond = proto.milliseconds = getSetMillisecond;
18670
proto.utcOffset = getSetOffset;
18671
proto.utc = setOffsetToUTC;
18672
proto.local = setOffsetToLocal;
18673
proto.parseZone = setOffsetToParsedOffset;
18674
proto.hasAlignedHourOffset = hasAlignedHourOffset;
18675
proto.isDST = isDaylightSavingTime;
18676
proto.isLocal = isLocal;
18677
proto.isUtcOffset = isUtcOffset;
18678
proto.isUtc = isUtc;
18679
proto.isUTC = isUtc;
18680
proto.zoneAbbr = getZoneAbbr;
18681
proto.zoneName = getZoneName;
18682
proto.dates = deprecate('dates accessor is deprecated. Use date instead.', getSetDayOfMonth);
18683
proto.months = deprecate('months accessor is deprecated. Use month instead', getSetMonth);
18684
proto.years = deprecate('years accessor is deprecated. Use year instead', getSetYear);
18685
proto.zone = deprecate('moment().zone is deprecated, use moment().utcOffset instead. http://momentjs.com/guides/#/warnings/zone/', getSetZone);
18686
proto.isDSTShifted = deprecate('isDSTShifted is deprecated. See http://momentjs.com/guides/#/warnings/dst-shifted/ for more information', isDaylightSavingTimeShifted);
18687
18688
function createUnix (input) {
18689
return createLocal(input * 1000);
18690
}
18691
18692
function createInZone () {
18693
return createLocal.apply(null, arguments).parseZone();
18694
}
18695
18696
function preParsePostFormat (string) {
18697
return string;
18698
}
18699
18700
var proto$1 = Locale.prototype;
18701
18702
proto$1.calendar = calendar;
18703
proto$1.longDateFormat = longDateFormat;
18704
proto$1.invalidDate = invalidDate;
18705
proto$1.ordinal = ordinal;
18706
proto$1.preparse = preParsePostFormat;
18707
proto$1.postformat = preParsePostFormat;
18708
proto$1.relativeTime = relativeTime;
18709
proto$1.pastFuture = pastFuture;
18710
proto$1.set = set;
18711
18712
proto$1.months = localeMonths;
18713
proto$1.monthsShort = localeMonthsShort;
18714
proto$1.monthsParse = localeMonthsParse;
18715
proto$1.monthsRegex = monthsRegex;
18716
proto$1.monthsShortRegex = monthsShortRegex;
18717
proto$1.week = localeWeek;
18718
proto$1.firstDayOfYear = localeFirstDayOfYear;
18719
proto$1.firstDayOfWeek = localeFirstDayOfWeek;
18720
18721
proto$1.weekdays = localeWeekdays;
18722
proto$1.weekdaysMin = localeWeekdaysMin;
18723
proto$1.weekdaysShort = localeWeekdaysShort;
18724
proto$1.weekdaysParse = localeWeekdaysParse;
18725
18726
proto$1.weekdaysRegex = weekdaysRegex;
18727
proto$1.weekdaysShortRegex = weekdaysShortRegex;
18728
proto$1.weekdaysMinRegex = weekdaysMinRegex;
18729
18730
proto$1.isPM = localeIsPM;
18731
proto$1.meridiem = localeMeridiem;
18732
18733
function get$1 (format, index, field, setter) {
18734
var locale = getLocale();
18735
var utc = createUTC().set(setter, index);
18736
return locale[field](utc, format);
18737
}
18738
18739
function listMonthsImpl (format, index, field) {
18740
if (isNumber(format)) {
18741
index = format;
18742
format = undefined;
18743
}
18744
18745
format = format || '';
18746
18747
if (index != null) {
18748
return get$1(format, index, field, 'month');
18749
}
18750
18751
var i;
18752
var out = [];
18753
for (i = 0; i < 12; i++) {
18754
out[i] = get$1(format, i, field, 'month');
18755
}
18756
return out;
18757
}
18758
18759
// ()
18760
// (5)
18761
// (fmt, 5)
18762
// (fmt)
18763
// (true)
18764
// (true, 5)
18765
// (true, fmt, 5)
18766
// (true, fmt)
18767
function listWeekdaysImpl (localeSorted, format, index, field) {
18768
if (typeof localeSorted === 'boolean') {
18769
if (isNumber(format)) {
18770
index = format;
18771
format = undefined;
18772
}
18773
18774
format = format || '';
18775
} else {
18776
format = localeSorted;
18777
index = format;
18778
localeSorted = false;
18779
18780
if (isNumber(format)) {
18781
index = format;
18782
format = undefined;
18783
}
18784
18785
format = format || '';
18786
}
18787
18788
var locale = getLocale(),
18789
shift = localeSorted ? locale._week.dow : 0;
18790
18791
if (index != null) {
18792
return get$1(format, (index + shift) % 7, field, 'day');
18793
}
18794
18795
var i;
18796
var out = [];
18797
for (i = 0; i < 7; i++) {
18798
out[i] = get$1(format, (i + shift) % 7, field, 'day');
18799
}
18800
return out;
18801
}
18802
18803
function listMonths (format, index) {
18804
return listMonthsImpl(format, index, 'months');
18805
}
18806
18807
function listMonthsShort (format, index) {
18808
return listMonthsImpl(format, index, 'monthsShort');
18809
}
18810
18811
function listWeekdays (localeSorted, format, index) {
18812
return listWeekdaysImpl(localeSorted, format, index, 'weekdays');
18813
}
18814
18815
function listWeekdaysShort (localeSorted, format, index) {
18816
return listWeekdaysImpl(localeSorted, format, index, 'weekdaysShort');
18817
}
18818
18819
function listWeekdaysMin (localeSorted, format, index) {
18820
return listWeekdaysImpl(localeSorted, format, index, 'weekdaysMin');
18821
}
18822
18823
getSetGlobalLocale('en', {
18824
dayOfMonthOrdinalParse: /\d{1,2}(th|st|nd|rd)/,
18825
ordinal : function (number) {
18826
var b = number % 10,
18827
output = (toInt(number % 100 / 10) === 1) ? 'th' :
18828
(b === 1) ? 'st' :
18829
(b === 2) ? 'nd' :
18830
(b === 3) ? 'rd' : 'th';
18831
return number + output;
18832
}
18833
});
18834
18835
// Side effect imports
18836
18837
hooks.lang = deprecate('moment.lang is deprecated. Use moment.locale instead.', getSetGlobalLocale);
18838
hooks.langData = deprecate('moment.langData is deprecated. Use moment.localeData instead.', getLocale);
18839
18840
var mathAbs = Math.abs;
18841
18842
function abs () {
18843
var data = this._data;
18844
18845
this._milliseconds = mathAbs(this._milliseconds);
18846
this._days = mathAbs(this._days);
18847
this._months = mathAbs(this._months);
18848
18849
data.milliseconds = mathAbs(data.milliseconds);
18850
data.seconds = mathAbs(data.seconds);
18851
data.minutes = mathAbs(data.minutes);
18852
data.hours = mathAbs(data.hours);
18853
data.months = mathAbs(data.months);
18854
data.years = mathAbs(data.years);
18855
18856
return this;
18857
}
18858
18859
function addSubtract$1 (duration, input, value, direction) {
18860
var other = createDuration(input, value);
18861
18862
duration._milliseconds += direction * other._milliseconds;
18863
duration._days += direction * other._days;
18864
duration._months += direction * other._months;
18865
18866
return duration._bubble();
18867
}
18868
18869
// supports only 2.0-style add(1, 's') or add(duration)
18870
function add$1 (input, value) {
18871
return addSubtract$1(this, input, value, 1);
18872
}
18873
18874
// supports only 2.0-style subtract(1, 's') or subtract(duration)
18875
function subtract$1 (input, value) {
18876
return addSubtract$1(this, input, value, -1);
18877
}
18878
18879
function absCeil (number) {
18880
if (number < 0) {
18881
return Math.floor(number);
18882
} else {
18883
return Math.ceil(number);
18884
}
18885
}
18886
18887
function bubble () {
18888
var milliseconds = this._milliseconds;
18889
var days = this._days;
18890
var months = this._months;
18891
var data = this._data;
18892
var seconds, minutes, hours, years, monthsFromDays;
18893
18894
// if we have a mix of positive and negative values, bubble down first
18895
// check: https://github.com/moment/moment/issues/2166
18896
if (!((milliseconds >= 0 && days >= 0 && months >= 0) ||
18897
(milliseconds <= 0 && days <= 0 && months <= 0))) {
18898
milliseconds += absCeil(monthsToDays(months) + days) * 864e5;
18899
days = 0;
18900
months = 0;
18901
}
18902
18903
// The following code bubbles up values, see the tests for
18904
// examples of what that means.
18905
data.milliseconds = milliseconds % 1000;
18906
18907
seconds = absFloor(milliseconds / 1000);
18908
data.seconds = seconds % 60;
18909
18910
minutes = absFloor(seconds / 60);
18911
data.minutes = minutes % 60;
18912
18913
hours = absFloor(minutes / 60);
18914
data.hours = hours % 24;
18915
18916
days += absFloor(hours / 24);
18917
18918
// convert days to months
18919
monthsFromDays = absFloor(daysToMonths(days));
18920
months += monthsFromDays;
18921
days -= absCeil(monthsToDays(monthsFromDays));
18922
18923
// 12 months -> 1 year
18924
years = absFloor(months / 12);
18925
months %= 12;
18926
18927
data.days = days;
18928
data.months = months;
18929
data.years = years;
18930
18931
return this;
18932
}
18933
18934
function daysToMonths (days) {
18935
// 400 years have 146097 days (taking into account leap year rules)
18936
// 400 years have 12 months === 4800
18937
return days * 4800 / 146097;
18938
}
18939
18940
function monthsToDays (months) {
18941
// the reverse of daysToMonths
18942
return months * 146097 / 4800;
18943
}
18944
18945
function as (units) {
18946
if (!this.isValid()) {
18947
return NaN;
18948
}
18949
var days;
18950
var months;
18951
var milliseconds = this._milliseconds;
18952
18953
units = normalizeUnits(units);
18954
18955
if (units === 'month' || units === 'quarter' || units === 'year') {
18956
days = this._days + milliseconds / 864e5;
18957
months = this._months + daysToMonths(days);
18958
switch (units) {
18959
case 'month': return months;
18960
case 'quarter': return months / 3;
18961
case 'year': return months / 12;
18962
}
18963
} else {
18964
// handle milliseconds separately because of floating point math errors (issue #1867)
18965
days = this._days + Math.round(monthsToDays(this._months));
18966
switch (units) {
18967
case 'week' : return days / 7 + milliseconds / 6048e5;
18968
case 'day' : return days + milliseconds / 864e5;
18969
case 'hour' : return days * 24 + milliseconds / 36e5;
18970
case 'minute' : return days * 1440 + milliseconds / 6e4;
18971
case 'second' : return days * 86400 + milliseconds / 1000;
18972
// Math.floor prevents floating point math errors here
18973
case 'millisecond': return Math.floor(days * 864e5) + milliseconds;
18974
default: throw new Error('Unknown unit ' + units);
18975
}
18976
}
18977
}
18978
18979
// TODO: Use this.as('ms')?
18980
function valueOf$1 () {
18981
if (!this.isValid()) {
18982
return NaN;
18983
}
18984
return (
18985
this._milliseconds +
18986
this._days * 864e5 +
18987
(this._months % 12) * 2592e6 +
18988
toInt(this._months / 12) * 31536e6
18989
);
18990
}
18991
18992
function makeAs (alias) {
18993
return function () {
18994
return this.as(alias);
18995
};
18996
}
18997
18998
var asMilliseconds = makeAs('ms');
18999
var asSeconds = makeAs('s');
19000
var asMinutes = makeAs('m');
19001
var asHours = makeAs('h');
19002
var asDays = makeAs('d');
19003
var asWeeks = makeAs('w');
19004
var asMonths = makeAs('M');
19005
var asQuarters = makeAs('Q');
19006
var asYears = makeAs('y');
19007
19008
function clone$1 () {
19009
return createDuration(this);
19010
}
19011
19012
function get$2 (units) {
19013
units = normalizeUnits(units);
19014
return this.isValid() ? this[units + 's']() : NaN;
19015
}
19016
19017
function makeGetter(name) {
19018
return function () {
19019
return this.isValid() ? this._data[name] : NaN;
19020
};
19021
}
19022
19023
var milliseconds = makeGetter('milliseconds');
19024
var seconds = makeGetter('seconds');
19025
var minutes = makeGetter('minutes');
19026
var hours = makeGetter('hours');
19027
var days = makeGetter('days');
19028
var months = makeGetter('months');
19029
var years = makeGetter('years');
19030
19031
function weeks () {
19032
return absFloor(this.days() / 7);
19033
}
19034
19035
var round = Math.round;
19036
var thresholds = {
19037
ss: 44, // a few seconds to seconds
19038
s : 45, // seconds to minute
19039
m : 45, // minutes to hour
19040
h : 22, // hours to day
19041
d : 26, // days to month
19042
M : 11 // months to year
19043
};
19044
19045
// helper function for moment.fn.from, moment.fn.fromNow, and moment.duration.fn.humanize
19046
function substituteTimeAgo(string, number, withoutSuffix, isFuture, locale) {
19047
return locale.relativeTime(number || 1, !!withoutSuffix, string, isFuture);
19048
}
19049
19050
function relativeTime$1 (posNegDuration, withoutSuffix, locale) {
19051
var duration = createDuration(posNegDuration).abs();
19052
var seconds = round(duration.as('s'));
19053
var minutes = round(duration.as('m'));
19054
var hours = round(duration.as('h'));
19055
var days = round(duration.as('d'));
19056
var months = round(duration.as('M'));
19057
var years = round(duration.as('y'));
19058
19059
var a = seconds <= thresholds.ss && ['s', seconds] ||
19060
seconds < thresholds.s && ['ss', seconds] ||
19061
minutes <= 1 && ['m'] ||
19062
minutes < thresholds.m && ['mm', minutes] ||
19063
hours <= 1 && ['h'] ||
19064
hours < thresholds.h && ['hh', hours] ||
19065
days <= 1 && ['d'] ||
19066
days < thresholds.d && ['dd', days] ||
19067
months <= 1 && ['M'] ||
19068
months < thresholds.M && ['MM', months] ||
19069
years <= 1 && ['y'] || ['yy', years];
19070
19071
a[2] = withoutSuffix;
19072
a[3] = +posNegDuration > 0;
19073
a[4] = locale;
19074
return substituteTimeAgo.apply(null, a);
19075
}
19076
19077
// This function allows you to set the rounding function for relative time strings
19078
function getSetRelativeTimeRounding (roundingFunction) {
19079
if (roundingFunction === undefined) {
19080
return round;
19081
}
19082
if (typeof(roundingFunction) === 'function') {
19083
round = roundingFunction;
19084
return true;
19085
}
19086
return false;
19087
}
19088
19089
// This function allows you to set a threshold for relative time strings
19090
function getSetRelativeTimeThreshold (threshold, limit) {
19091
if (thresholds[threshold] === undefined) {
19092
return false;
19093
}
19094
if (limit === undefined) {
19095
return thresholds[threshold];
19096
}
19097
thresholds[threshold] = limit;
19098
if (threshold === 's') {
19099
thresholds.ss = limit - 1;
19100
}
19101
return true;
19102
}
19103
19104
function humanize (withSuffix) {
19105
if (!this.isValid()) {
19106
return this.localeData().invalidDate();
19107
}
19108
19109
var locale = this.localeData();
19110
var output = relativeTime$1(this, !withSuffix, locale);
19111
19112
if (withSuffix) {
19113
output = locale.pastFuture(+this, output);
19114
}
19115
19116
return locale.postformat(output);
19117
}
19118
19119
var abs$1 = Math.abs;
19120
19121
function sign(x) {
19122
return ((x > 0) - (x < 0)) || +x;
19123
}
19124
19125
function toISOString$1() {
19126
// for ISO strings we do not use the normal bubbling rules:
19127
// * milliseconds bubble up until they become hours
19128
// * days do not bubble at all
19129
// * months bubble up until they become years
19130
// This is because there is no context-free conversion between hours and days
19131
// (think of clock changes)
19132
// and also not between days and months (28-31 days per month)
19133
if (!this.isValid()) {
19134
return this.localeData().invalidDate();
19135
}
19136
19137
var seconds = abs$1(this._milliseconds) / 1000;
19138
var days = abs$1(this._days);
19139
var months = abs$1(this._months);
19140
var minutes, hours, years;
19141
19142
// 3600 seconds -> 60 minutes -> 1 hour
19143
minutes = absFloor(seconds / 60);
19144
hours = absFloor(minutes / 60);
19145
seconds %= 60;
19146
minutes %= 60;
19147
19148
// 12 months -> 1 year
19149
years = absFloor(months / 12);
19150
months %= 12;
19151
19152
19153
// inspired by https://github.com/dordille/moment-isoduration/blob/master/moment.isoduration.js
19154
var Y = years;
19155
var M = months;
19156
var D = days;
19157
var h = hours;
19158
var m = minutes;
19159
var s = seconds ? seconds.toFixed(3).replace(/\.?0+$/, '') : '';
19160
var total = this.asSeconds();
19161
19162
if (!total) {
19163
// this is the same as C#'s (Noda) and python (isodate)...
19164
// but not other JS (goog.date)
19165
return 'P0D';
19166
}
19167
19168
var totalSign = total < 0 ? '-' : '';
19169
var ymSign = sign(this._months) !== sign(total) ? '-' : '';
19170
var daysSign = sign(this._days) !== sign(total) ? '-' : '';
19171
var hmsSign = sign(this._milliseconds) !== sign(total) ? '-' : '';
19172
19173
return totalSign + 'P' +
19174
(Y ? ymSign + Y + 'Y' : '') +
19175
(M ? ymSign + M + 'M' : '') +
19176
(D ? daysSign + D + 'D' : '') +
19177
((h || m || s) ? 'T' : '') +
19178
(h ? hmsSign + h + 'H' : '') +
19179
(m ? hmsSign + m + 'M' : '') +
19180
(s ? hmsSign + s + 'S' : '');
19181
}
19182
19183
var proto$2 = Duration.prototype;
19184
19185
proto$2.isValid = isValid$1;
19186
proto$2.abs = abs;
19187
proto$2.add = add$1;
19188
proto$2.subtract = subtract$1;
19189
proto$2.as = as;
19190
proto$2.asMilliseconds = asMilliseconds;
19191
proto$2.asSeconds = asSeconds;
19192
proto$2.asMinutes = asMinutes;
19193
proto$2.asHours = asHours;
19194
proto$2.asDays = asDays;
19195
proto$2.asWeeks = asWeeks;
19196
proto$2.asMonths = asMonths;
19197
proto$2.asQuarters = asQuarters;
19198
proto$2.asYears = asYears;
19199
proto$2.valueOf = valueOf$1;
19200
proto$2._bubble = bubble;
19201
proto$2.clone = clone$1;
19202
proto$2.get = get$2;
19203
proto$2.milliseconds = milliseconds;
19204
proto$2.seconds = seconds;
19205
proto$2.minutes = minutes;
19206
proto$2.hours = hours;
19207
proto$2.days = days;
19208
proto$2.weeks = weeks;
19209
proto$2.months = months;
19210
proto$2.years = years;
19211
proto$2.humanize = humanize;
19212
proto$2.toISOString = toISOString$1;
19213
proto$2.toString = toISOString$1;
19214
proto$2.toJSON = toISOString$1;
19215
proto$2.locale = locale;
19216
proto$2.localeData = localeData;
19217
19218
proto$2.toIsoString = deprecate('toIsoString() is deprecated. Please use toISOString() instead (notice the capitals)', toISOString$1);
19219
proto$2.lang = lang;
19220
19221
// Side effect imports
19222
19223
// FORMATTING
19224
19225
addFormatToken('X', 0, 0, 'unix');
19226
addFormatToken('x', 0, 0, 'valueOf');
19227
19228
// PARSING
19229
19230
addRegexToken('x', matchSigned);
19231
addRegexToken('X', matchTimestamp);
19232
addParseToken('X', function (input, array, config) {
19233
config._d = new Date(parseFloat(input, 10) * 1000);
19234
});
19235
addParseToken('x', function (input, array, config) {
19236
config._d = new Date(toInt(input));
19237
});
19238
19239
// Side effect imports
19240
19241
19242
hooks.version = '2.24.0';
19243
19244
setHookCallback(createLocal);
19245
19246
hooks.fn = proto;
19247
hooks.min = min;
19248
hooks.max = max;
19249
hooks.now = now;
19250
hooks.utc = createUTC;
19251
hooks.unix = createUnix;
19252
hooks.months = listMonths;
19253
hooks.isDate = isDate;
19254
hooks.locale = getSetGlobalLocale;
19255
hooks.invalid = createInvalid;
19256
hooks.duration = createDuration;
19257
hooks.isMoment = isMoment;
19258
hooks.weekdays = listWeekdays;
19259
hooks.parseZone = createInZone;
19260
hooks.localeData = getLocale;
19261
hooks.isDuration = isDuration;
19262
hooks.monthsShort = listMonthsShort;
19263
hooks.weekdaysMin = listWeekdaysMin;
19264
hooks.defineLocale = defineLocale;
19265
hooks.updateLocale = updateLocale;
19266
hooks.locales = listLocales;
19267
hooks.weekdaysShort = listWeekdaysShort;
19268
hooks.normalizeUnits = normalizeUnits;
19269
hooks.relativeTimeRounding = getSetRelativeTimeRounding;
19270
hooks.relativeTimeThreshold = getSetRelativeTimeThreshold;
19271
hooks.calendarFormat = getCalendarFormat;
19272
hooks.prototype = proto;
19273
19274
// currently HTML5 input type only supports 24-hour formats
19275
hooks.HTML5_FMT = {
19276
DATETIME_LOCAL: 'YYYY-MM-DDTHH:mm', // <input type="datetime-local" />
19277
DATETIME_LOCAL_SECONDS: 'YYYY-MM-DDTHH:mm:ss', // <input type="datetime-local" step="1" />
19278
DATETIME_LOCAL_MS: 'YYYY-MM-DDTHH:mm:ss.SSS', // <input type="datetime-local" step="0.001" />
19279
DATE: 'YYYY-MM-DD', // <input type="date" />
19280
TIME: 'HH:mm', // <input type="time" />
19281
TIME_SECONDS: 'HH:mm:ss', // <input type="time" step="1" />
19282
TIME_MS: 'HH:mm:ss.SSS', // <input type="time" step="0.001" />
19283
WEEK: 'GGGG-[W]WW', // <input type="week" />
19284
MONTH: 'YYYY-MM' // <input type="month" />
19285
};
19286
19287
return hooks;
19288
19289
})));
19290
});
19291
19292
var FORMATS = {
19293
datetime: 'MMM D, YYYY, h:mm:ss a',
19294
millisecond: 'h:mm:ss.SSS a',
19295
second: 'h:mm:ss a',
19296
minute: 'h:mm a',
19297
hour: 'hA',
19298
day: 'MMM D',
19299
week: 'll',
19300
month: 'MMM YYYY',
19301
quarter: '[Q]Q - YYYY',
19302
year: 'YYYY'
19303
};
19304
19305
core_adapters._date.override(typeof moment === 'function' ? {
19306
_id: 'moment', // DEBUG ONLY
19307
19308
formats: function() {
19309
return FORMATS;
19310
},
19311
19312
parse: function(value, format) {
19313
if (typeof value === 'string' && typeof format === 'string') {
19314
value = moment(value, format);
19315
} else if (!(value instanceof moment)) {
19316
value = moment(value);
19317
}
19318
return value.isValid() ? value.valueOf() : null;
19319
},
19320
19321
format: function(time, format) {
19322
return moment(time).format(format);
19323
},
19324
19325
add: function(time, amount, unit) {
19326
return moment(time).add(amount, unit).valueOf();
19327
},
19328
19329
diff: function(max, min, unit) {
19330
return moment(max).diff(moment(min), unit);
19331
},
19332
19333
startOf: function(time, unit, weekday) {
19334
time = moment(time);
19335
if (unit === 'isoWeek') {
19336
return time.isoWeekday(weekday).valueOf();
19337
}
19338
return time.startOf(unit).valueOf();
19339
},
19340
19341
endOf: function(time, unit) {
19342
return moment(time).endOf(unit).valueOf();
19343
},
19344
19345
// DEPRECATIONS
19346
19347
/**
19348
* Provided for backward compatibility with scale.getValueForPixel().
19349
* @deprecated since version 2.8.0
19350
* @todo remove at version 3
19351
* @private
19352
*/
19353
_create: function(time) {
19354
return moment(time);
19355
},
19356
} : {});
19357
19358
core_defaults._set('global', {
19359
plugins: {
19360
filler: {
19361
propagate: true
19362
}
19363
}
19364
});
19365
19366
var mappers = {
19367
dataset: function(source) {
19368
var index = source.fill;
19369
var chart = source.chart;
19370
var meta = chart.getDatasetMeta(index);
19371
var visible = meta && chart.isDatasetVisible(index);
19372
var points = (visible && meta.dataset._children) || [];
19373
var length = points.length || 0;
19374
19375
return !length ? null : function(point, i) {
19376
return (i < length && points[i]._view) || null;
19377
};
19378
},
19379
19380
boundary: function(source) {
19381
var boundary = source.boundary;
19382
var x = boundary ? boundary.x : null;
19383
var y = boundary ? boundary.y : null;
19384
19385
if (helpers$1.isArray(boundary)) {
19386
return function(point, i) {
19387
return boundary[i];
19388
};
19389
}
19390
19391
return function(point) {
19392
return {
19393
x: x === null ? point.x : x,
19394
y: y === null ? point.y : y,
19395
};
19396
};
19397
}
19398
};
19399
19400
// @todo if (fill[0] === '#')
19401
function decodeFill(el, index, count) {
19402
var model = el._model || {};
19403
var fill = model.fill;
19404
var target;
19405
19406
if (fill === undefined) {
19407
fill = !!model.backgroundColor;
19408
}
19409
19410
if (fill === false || fill === null) {
19411
return false;
19412
}
19413
19414
if (fill === true) {
19415
return 'origin';
19416
}
19417
19418
target = parseFloat(fill, 10);
19419
if (isFinite(target) && Math.floor(target) === target) {
19420
if (fill[0] === '-' || fill[0] === '+') {
19421
target = index + target;
19422
}
19423
19424
if (target === index || target < 0 || target >= count) {
19425
return false;
19426
}
19427
19428
return target;
19429
}
19430
19431
switch (fill) {
19432
// compatibility
19433
case 'bottom':
19434
return 'start';
19435
case 'top':
19436
return 'end';
19437
case 'zero':
19438
return 'origin';
19439
// supported boundaries
19440
case 'origin':
19441
case 'start':
19442
case 'end':
19443
return fill;
19444
// invalid fill values
19445
default:
19446
return false;
19447
}
19448
}
19449
19450
function computeLinearBoundary(source) {
19451
var model = source.el._model || {};
19452
var scale = source.el._scale || {};
19453
var fill = source.fill;
19454
var target = null;
19455
var horizontal;
19456
19457
if (isFinite(fill)) {
19458
return null;
19459
}
19460
19461
// Backward compatibility: until v3, we still need to support boundary values set on
19462
// the model (scaleTop, scaleBottom and scaleZero) because some external plugins and
19463
// controllers might still use it (e.g. the Smith chart).
19464
19465
if (fill === 'start') {
19466
target = model.scaleBottom === undefined ? scale.bottom : model.scaleBottom;
19467
} else if (fill === 'end') {
19468
target = model.scaleTop === undefined ? scale.top : model.scaleTop;
19469
} else if (model.scaleZero !== undefined) {
19470
target = model.scaleZero;
19471
} else if (scale.getBasePixel) {
19472
target = scale.getBasePixel();
19473
}
19474
19475
if (target !== undefined && target !== null) {
19476
if (target.x !== undefined && target.y !== undefined) {
19477
return target;
19478
}
19479
19480
if (helpers$1.isFinite(target)) {
19481
horizontal = scale.isHorizontal();
19482
return {
19483
x: horizontal ? target : null,
19484
y: horizontal ? null : target
19485
};
19486
}
19487
}
19488
19489
return null;
19490
}
19491
19492
function computeCircularBoundary(source) {
19493
var scale = source.el._scale;
19494
var options = scale.options;
19495
var length = scale.chart.data.labels.length;
19496
var fill = source.fill;
19497
var target = [];
19498
var start, end, center, i, point;
19499
19500
if (!length) {
19501
return null;
19502
}
19503
19504
start = options.ticks.reverse ? scale.max : scale.min;
19505
end = options.ticks.reverse ? scale.min : scale.max;
19506
center = scale.getPointPositionForValue(0, start);
19507
for (i = 0; i < length; ++i) {
19508
point = fill === 'start' || fill === 'end'
19509
? scale.getPointPositionForValue(i, fill === 'start' ? start : end)
19510
: scale.getBasePosition(i);
19511
if (options.gridLines.circular) {
19512
point.cx = center.x;
19513
point.cy = center.y;
19514
point.angle = scale.getIndexAngle(i) - Math.PI / 2;
19515
}
19516
target.push(point);
19517
}
19518
return target;
19519
}
19520
19521
function computeBoundary(source) {
19522
var scale = source.el._scale || {};
19523
19524
if (scale.getPointPositionForValue) {
19525
return computeCircularBoundary(source);
19526
}
19527
return computeLinearBoundary(source);
19528
}
19529
19530
function resolveTarget(sources, index, propagate) {
19531
var source = sources[index];
19532
var fill = source.fill;
19533
var visited = [index];
19534
var target;
19535
19536
if (!propagate) {
19537
return fill;
19538
}
19539
19540
while (fill !== false && visited.indexOf(fill) === -1) {
19541
if (!isFinite(fill)) {
19542
return fill;
19543
}
19544
19545
target = sources[fill];
19546
if (!target) {
19547
return false;
19548
}
19549
19550
if (target.visible) {
19551
return fill;
19552
}
19553
19554
visited.push(fill);
19555
fill = target.fill;
19556
}
19557
19558
return false;
19559
}
19560
19561
function createMapper(source) {
19562
var fill = source.fill;
19563
var type = 'dataset';
19564
19565
if (fill === false) {
19566
return null;
19567
}
19568
19569
if (!isFinite(fill)) {
19570
type = 'boundary';
19571
}
19572
19573
return mappers[type](source);
19574
}
19575
19576
function isDrawable(point) {
19577
return point && !point.skip;
19578
}
19579
19580
function drawArea(ctx, curve0, curve1, len0, len1) {
19581
var i, cx, cy, r;
19582
19583
if (!len0 || !len1) {
19584
return;
19585
}
19586
19587
// building first area curve (normal)
19588
ctx.moveTo(curve0[0].x, curve0[0].y);
19589
for (i = 1; i < len0; ++i) {
19590
helpers$1.canvas.lineTo(ctx, curve0[i - 1], curve0[i]);
19591
}
19592
19593
if (curve1[0].angle !== undefined) {
19594
cx = curve1[0].cx;
19595
cy = curve1[0].cy;
19596
r = Math.sqrt(Math.pow(curve1[0].x - cx, 2) + Math.pow(curve1[0].y - cy, 2));
19597
for (i = len1 - 1; i > 0; --i) {
19598
ctx.arc(cx, cy, r, curve1[i].angle, curve1[i - 1].angle, true);
19599
}
19600
return;
19601
}
19602
19603
// joining the two area curves
19604
ctx.lineTo(curve1[len1 - 1].x, curve1[len1 - 1].y);
19605
19606
// building opposite area curve (reverse)
19607
for (i = len1 - 1; i > 0; --i) {
19608
helpers$1.canvas.lineTo(ctx, curve1[i], curve1[i - 1], true);
19609
}
19610
}
19611
19612
function doFill(ctx, points, mapper, view, color, loop) {
19613
var count = points.length;
19614
var span = view.spanGaps;
19615
var curve0 = [];
19616
var curve1 = [];
19617
var len0 = 0;
19618
var len1 = 0;
19619
var i, ilen, index, p0, p1, d0, d1, loopOffset;
19620
19621
ctx.beginPath();
19622
19623
for (i = 0, ilen = count; i < ilen; ++i) {
19624
index = i % count;
19625
p0 = points[index]._view;
19626
p1 = mapper(p0, index, view);
19627
d0 = isDrawable(p0);
19628
d1 = isDrawable(p1);
19629
19630
if (loop && loopOffset === undefined && d0) {
19631
loopOffset = i + 1;
19632
ilen = count + loopOffset;
19633
}
19634
19635
if (d0 && d1) {
19636
len0 = curve0.push(p0);
19637
len1 = curve1.push(p1);
19638
} else if (len0 && len1) {
19639
if (!span) {
19640
drawArea(ctx, curve0, curve1, len0, len1);
19641
len0 = len1 = 0;
19642
curve0 = [];
19643
curve1 = [];
19644
} else {
19645
if (d0) {
19646
curve0.push(p0);
19647
}
19648
if (d1) {
19649
curve1.push(p1);
19650
}
19651
}
19652
}
19653
}
19654
19655
drawArea(ctx, curve0, curve1, len0, len1);
19656
19657
ctx.closePath();
19658
ctx.fillStyle = color;
19659
ctx.fill();
19660
}
19661
19662
var plugin_filler = {
19663
id: 'filler',
19664
19665
afterDatasetsUpdate: function(chart, options) {
19666
var count = (chart.data.datasets || []).length;
19667
var propagate = options.propagate;
19668
var sources = [];
19669
var meta, i, el, source;
19670
19671
for (i = 0; i < count; ++i) {
19672
meta = chart.getDatasetMeta(i);
19673
el = meta.dataset;
19674
source = null;
19675
19676
if (el && el._model && el instanceof elements.Line) {
19677
source = {
19678
visible: chart.isDatasetVisible(i),
19679
fill: decodeFill(el, i, count),
19680
chart: chart,
19681
el: el
19682
};
19683
}
19684
19685
meta.$filler = source;
19686
sources.push(source);
19687
}
19688
19689
for (i = 0; i < count; ++i) {
19690
source = sources[i];
19691
if (!source) {
19692
continue;
19693
}
19694
19695
source.fill = resolveTarget(sources, i, propagate);
19696
source.boundary = computeBoundary(source);
19697
source.mapper = createMapper(source);
19698
}
19699
},
19700
19701
beforeDatasetsDraw: function(chart) {
19702
var metasets = chart._getSortedVisibleDatasetMetas();
19703
var ctx = chart.ctx;
19704
var meta, i, el, view, points, mapper, color;
19705
19706
for (i = metasets.length - 1; i >= 0; --i) {
19707
meta = metasets[i].$filler;
19708
19709
if (!meta || !meta.visible) {
19710
continue;
19711
}
19712
19713
el = meta.el;
19714
view = el._view;
19715
points = el._children || [];
19716
mapper = meta.mapper;
19717
color = view.backgroundColor || core_defaults.global.defaultColor;
19718
19719
if (mapper && color && points.length) {
19720
helpers$1.canvas.clipArea(ctx, chart.chartArea);
19721
doFill(ctx, points, mapper, view, color, el._loop);
19722
helpers$1.canvas.unclipArea(ctx);
19723
}
19724
}
19725
}
19726
};
19727
19728
var getRtlHelper$1 = helpers$1.rtl.getRtlAdapter;
19729
var noop$1 = helpers$1.noop;
19730
var valueOrDefault$e = helpers$1.valueOrDefault;
19731
19732
core_defaults._set('global', {
19733
legend: {
19734
display: true,
19735
position: 'top',
19736
align: 'center',
19737
fullWidth: true,
19738
reverse: false,
19739
weight: 1000,
19740
19741
// a callback that will handle
19742
onClick: function(e, legendItem) {
19743
var index = legendItem.datasetIndex;
19744
var ci = this.chart;
19745
var meta = ci.getDatasetMeta(index);
19746
19747
// See controller.isDatasetVisible comment
19748
meta.hidden = meta.hidden === null ? !ci.data.datasets[index].hidden : null;
19749
19750
// We hid a dataset ... rerender the chart
19751
ci.update();
19752
},
19753
19754
onHover: null,
19755
onLeave: null,
19756
19757
labels: {
19758
boxWidth: 40,
19759
padding: 10,
19760
// Generates labels shown in the legend
19761
// Valid properties to return:
19762
// text : text to display
19763
// fillStyle : fill of coloured box
19764
// strokeStyle: stroke of coloured box
19765
// hidden : if this legend item refers to a hidden item
19766
// lineCap : cap style for line
19767
// lineDash
19768
// lineDashOffset :
19769
// lineJoin :
19770
// lineWidth :
19771
generateLabels: function(chart) {
19772
var datasets = chart.data.datasets;
19773
var options = chart.options.legend || {};
19774
var usePointStyle = options.labels && options.labels.usePointStyle;
19775
19776
return chart._getSortedDatasetMetas().map(function(meta) {
19777
var style = meta.controller.getStyle(usePointStyle ? 0 : undefined);
19778
19779
return {
19780
text: datasets[meta.index].label,
19781
fillStyle: style.backgroundColor,
19782
hidden: !chart.isDatasetVisible(meta.index),
19783
lineCap: style.borderCapStyle,
19784
lineDash: style.borderDash,
19785
lineDashOffset: style.borderDashOffset,
19786
lineJoin: style.borderJoinStyle,
19787
lineWidth: style.borderWidth,
19788
strokeStyle: style.borderColor,
19789
pointStyle: style.pointStyle,
19790
rotation: style.rotation,
19791
19792
// Below is extra data used for toggling the datasets
19793
datasetIndex: meta.index
19794
};
19795
}, this);
19796
}
19797
}
19798
},
19799
19800
legendCallback: function(chart) {
19801
var list = document.createElement('ul');
19802
var datasets = chart.data.datasets;
19803
var i, ilen, listItem, listItemSpan;
19804
19805
list.setAttribute('class', chart.id + '-legend');
19806
19807
for (i = 0, ilen = datasets.length; i < ilen; i++) {
19808
listItem = list.appendChild(document.createElement('li'));
19809
listItemSpan = listItem.appendChild(document.createElement('span'));
19810
listItemSpan.style.backgroundColor = datasets[i].backgroundColor;
19811
if (datasets[i].label) {
19812
listItem.appendChild(document.createTextNode(datasets[i].label));
19813
}
19814
}
19815
19816
return list.outerHTML;
19817
}
19818
});
19819
19820
/**
19821
* Helper function to get the box width based on the usePointStyle option
19822
* @param {object} labelopts - the label options on the legend
19823
* @param {number} fontSize - the label font size
19824
* @return {number} width of the color box area
19825
*/
19826
function getBoxWidth(labelOpts, fontSize) {
19827
return labelOpts.usePointStyle && labelOpts.boxWidth > fontSize ?
19828
fontSize :
19829
labelOpts.boxWidth;
19830
}
19831
19832
/**
19833
* IMPORTANT: this class is exposed publicly as Chart.Legend, backward compatibility required!
19834
*/
19835
var Legend = core_element.extend({
19836
19837
initialize: function(config) {
19838
var me = this;
19839
helpers$1.extend(me, config);
19840
19841
// Contains hit boxes for each dataset (in dataset order)
19842
me.legendHitBoxes = [];
19843
19844
/**
19845
* @private
19846
*/
19847
me._hoveredItem = null;
19848
19849
// Are we in doughnut mode which has a different data type
19850
me.doughnutMode = false;
19851
},
19852
19853
// These methods are ordered by lifecycle. Utilities then follow.
19854
// Any function defined here is inherited by all legend types.
19855
// Any function can be extended by the legend type
19856
19857
beforeUpdate: noop$1,
19858
update: function(maxWidth, maxHeight, margins) {
19859
var me = this;
19860
19861
// Update Lifecycle - Probably don't want to ever extend or overwrite this function ;)
19862
me.beforeUpdate();
19863
19864
// Absorb the master measurements
19865
me.maxWidth = maxWidth;
19866
me.maxHeight = maxHeight;
19867
me.margins = margins;
19868
19869
// Dimensions
19870
me.beforeSetDimensions();
19871
me.setDimensions();
19872
me.afterSetDimensions();
19873
// Labels
19874
me.beforeBuildLabels();
19875
me.buildLabels();
19876
me.afterBuildLabels();
19877
19878
// Fit
19879
me.beforeFit();
19880
me.fit();
19881
me.afterFit();
19882
//
19883
me.afterUpdate();
19884
19885
return me.minSize;
19886
},
19887
afterUpdate: noop$1,
19888
19889
//
19890
19891
beforeSetDimensions: noop$1,
19892
setDimensions: function() {
19893
var me = this;
19894
// Set the unconstrained dimension before label rotation
19895
if (me.isHorizontal()) {
19896
// Reset position before calculating rotation
19897
me.width = me.maxWidth;
19898
me.left = 0;
19899
me.right = me.width;
19900
} else {
19901
me.height = me.maxHeight;
19902
19903
// Reset position before calculating rotation
19904
me.top = 0;
19905
me.bottom = me.height;
19906
}
19907
19908
// Reset padding
19909
me.paddingLeft = 0;
19910
me.paddingTop = 0;
19911
me.paddingRight = 0;
19912
me.paddingBottom = 0;
19913
19914
// Reset minSize
19915
me.minSize = {
19916
width: 0,
19917
height: 0
19918
};
19919
},
19920
afterSetDimensions: noop$1,
19921
19922
//
19923
19924
beforeBuildLabels: noop$1,
19925
buildLabels: function() {
19926
var me = this;
19927
var labelOpts = me.options.labels || {};
19928
var legendItems = helpers$1.callback(labelOpts.generateLabels, [me.chart], me) || [];
19929
19930
if (labelOpts.filter) {
19931
legendItems = legendItems.filter(function(item) {
19932
return labelOpts.filter(item, me.chart.data);
19933
});
19934
}
19935
19936
if (me.options.reverse) {
19937
legendItems.reverse();
19938
}
19939
19940
me.legendItems = legendItems;
19941
},
19942
afterBuildLabels: noop$1,
19943
19944
//
19945
19946
beforeFit: noop$1,
19947
fit: function() {
19948
var me = this;
19949
var opts = me.options;
19950
var labelOpts = opts.labels;
19951
var display = opts.display;
19952
19953
var ctx = me.ctx;
19954
19955
var labelFont = helpers$1.options._parseFont(labelOpts);
19956
var fontSize = labelFont.size;
19957
19958
// Reset hit boxes
19959
var hitboxes = me.legendHitBoxes = [];
19960
19961
var minSize = me.minSize;
19962
var isHorizontal = me.isHorizontal();
19963
19964
if (isHorizontal) {
19965
minSize.width = me.maxWidth; // fill all the width
19966
minSize.height = display ? 10 : 0;
19967
} else {
19968
minSize.width = display ? 10 : 0;
19969
minSize.height = me.maxHeight; // fill all the height
19970
}
19971
19972
// Increase sizes here
19973
if (!display) {
19974
me.width = minSize.width = me.height = minSize.height = 0;
19975
return;
19976
}
19977
ctx.font = labelFont.string;
19978
19979
if (isHorizontal) {
19980
// Labels
19981
19982
// Width of each line of legend boxes. Labels wrap onto multiple lines when there are too many to fit on one
19983
var lineWidths = me.lineWidths = [0];
19984
var totalHeight = 0;
19985
19986
ctx.textAlign = 'left';
19987
ctx.textBaseline = 'middle';
19988
19989
helpers$1.each(me.legendItems, function(legendItem, i) {
19990
var boxWidth = getBoxWidth(labelOpts, fontSize);
19991
var width = boxWidth + (fontSize / 2) + ctx.measureText(legendItem.text).width;
19992
19993
if (i === 0 || lineWidths[lineWidths.length - 1] + width + 2 * labelOpts.padding > minSize.width) {
19994
totalHeight += fontSize + labelOpts.padding;
19995
lineWidths[lineWidths.length - (i > 0 ? 0 : 1)] = 0;
19996
}
19997
19998
// Store the hitbox width and height here. Final position will be updated in `draw`
19999
hitboxes[i] = {
20000
left: 0,
20001
top: 0,
20002
width: width,
20003
height: fontSize
20004
};
20005
20006
lineWidths[lineWidths.length - 1] += width + labelOpts.padding;
20007
});
20008
20009
minSize.height += totalHeight;
20010
20011
} else {
20012
var vPadding = labelOpts.padding;
20013
var columnWidths = me.columnWidths = [];
20014
var columnHeights = me.columnHeights = [];
20015
var totalWidth = labelOpts.padding;
20016
var currentColWidth = 0;
20017
var currentColHeight = 0;
20018
20019
helpers$1.each(me.legendItems, function(legendItem, i) {
20020
var boxWidth = getBoxWidth(labelOpts, fontSize);
20021
var itemWidth = boxWidth + (fontSize / 2) + ctx.measureText(legendItem.text).width;
20022
20023
// If too tall, go to new column
20024
if (i > 0 && currentColHeight + fontSize + 2 * vPadding > minSize.height) {
20025
totalWidth += currentColWidth + labelOpts.padding;
20026
columnWidths.push(currentColWidth); // previous column width
20027
columnHeights.push(currentColHeight);
20028
currentColWidth = 0;
20029
currentColHeight = 0;
20030
}
20031
20032
// Get max width
20033
currentColWidth = Math.max(currentColWidth, itemWidth);
20034
currentColHeight += fontSize + vPadding;
20035
20036
// Store the hitbox width and height here. Final position will be updated in `draw`
20037
hitboxes[i] = {
20038
left: 0,
20039
top: 0,
20040
width: itemWidth,
20041
height: fontSize
20042
};
20043
});
20044
20045
totalWidth += currentColWidth;
20046
columnWidths.push(currentColWidth);
20047
columnHeights.push(currentColHeight);
20048
minSize.width += totalWidth;
20049
}
20050
20051
me.width = minSize.width;
20052
me.height = minSize.height;
20053
},
20054
afterFit: noop$1,
20055
20056
// Shared Methods
20057
isHorizontal: function() {
20058
return this.options.position === 'top' || this.options.position === 'bottom';
20059
},
20060
20061
// Actually draw the legend on the canvas
20062
draw: function() {
20063
var me = this;
20064
var opts = me.options;
20065
var labelOpts = opts.labels;
20066
var globalDefaults = core_defaults.global;
20067
var defaultColor = globalDefaults.defaultColor;
20068
var lineDefault = globalDefaults.elements.line;
20069
var legendHeight = me.height;
20070
var columnHeights = me.columnHeights;
20071
var legendWidth = me.width;
20072
var lineWidths = me.lineWidths;
20073
20074
if (!opts.display) {
20075
return;
20076
}
20077
20078
var rtlHelper = getRtlHelper$1(opts.rtl, me.left, me.minSize.width);
20079
var ctx = me.ctx;
20080
var fontColor = valueOrDefault$e(labelOpts.fontColor, globalDefaults.defaultFontColor);
20081
var labelFont = helpers$1.options._parseFont(labelOpts);
20082
var fontSize = labelFont.size;
20083
var cursor;
20084
20085
// Canvas setup
20086
ctx.textAlign = rtlHelper.textAlign('left');
20087
ctx.textBaseline = 'middle';
20088
ctx.lineWidth = 0.5;
20089
ctx.strokeStyle = fontColor; // for strikethrough effect
20090
ctx.fillStyle = fontColor; // render in correct colour
20091
ctx.font = labelFont.string;
20092
20093
var boxWidth = getBoxWidth(labelOpts, fontSize);
20094
var hitboxes = me.legendHitBoxes;
20095
20096
// current position
20097
var drawLegendBox = function(x, y, legendItem) {
20098
if (isNaN(boxWidth) || boxWidth <= 0) {
20099
return;
20100
}
20101
20102
// Set the ctx for the box
20103
ctx.save();
20104
20105
var lineWidth = valueOrDefault$e(legendItem.lineWidth, lineDefault.borderWidth);
20106
ctx.fillStyle = valueOrDefault$e(legendItem.fillStyle, defaultColor);
20107
ctx.lineCap = valueOrDefault$e(legendItem.lineCap, lineDefault.borderCapStyle);
20108
ctx.lineDashOffset = valueOrDefault$e(legendItem.lineDashOffset, lineDefault.borderDashOffset);
20109
ctx.lineJoin = valueOrDefault$e(legendItem.lineJoin, lineDefault.borderJoinStyle);
20110
ctx.lineWidth = lineWidth;
20111
ctx.strokeStyle = valueOrDefault$e(legendItem.strokeStyle, defaultColor);
20112
20113
if (ctx.setLineDash) {
20114
// IE 9 and 10 do not support line dash
20115
ctx.setLineDash(valueOrDefault$e(legendItem.lineDash, lineDefault.borderDash));
20116
}
20117
20118
if (labelOpts && labelOpts.usePointStyle) {
20119
// Recalculate x and y for drawPoint() because its expecting
20120
// x and y to be center of figure (instead of top left)
20121
var radius = boxWidth * Math.SQRT2 / 2;
20122
var centerX = rtlHelper.xPlus(x, boxWidth / 2);
20123
var centerY = y + fontSize / 2;
20124
20125
// Draw pointStyle as legend symbol
20126
helpers$1.canvas.drawPoint(ctx, legendItem.pointStyle, radius, centerX, centerY, legendItem.rotation);
20127
} else {
20128
// Draw box as legend symbol
20129
ctx.fillRect(rtlHelper.leftForLtr(x, boxWidth), y, boxWidth, fontSize);
20130
if (lineWidth !== 0) {
20131
ctx.strokeRect(rtlHelper.leftForLtr(x, boxWidth), y, boxWidth, fontSize);
20132
}
20133
}
20134
20135
ctx.restore();
20136
};
20137
20138
var fillText = function(x, y, legendItem, textWidth) {
20139
var halfFontSize = fontSize / 2;
20140
var xLeft = rtlHelper.xPlus(x, boxWidth + halfFontSize);
20141
var yMiddle = y + halfFontSize;
20142
20143
ctx.fillText(legendItem.text, xLeft, yMiddle);
20144
20145
if (legendItem.hidden) {
20146
// Strikethrough the text if hidden
20147
ctx.beginPath();
20148
ctx.lineWidth = 2;
20149
ctx.moveTo(xLeft, yMiddle);
20150
ctx.lineTo(rtlHelper.xPlus(xLeft, textWidth), yMiddle);
20151
ctx.stroke();
20152
}
20153
};
20154
20155
var alignmentOffset = function(dimension, blockSize) {
20156
switch (opts.align) {
20157
case 'start':
20158
return labelOpts.padding;
20159
case 'end':
20160
return dimension - blockSize;
20161
default: // center
20162
return (dimension - blockSize + labelOpts.padding) / 2;
20163
}
20164
};
20165
20166
// Horizontal
20167
var isHorizontal = me.isHorizontal();
20168
if (isHorizontal) {
20169
cursor = {
20170
x: me.left + alignmentOffset(legendWidth, lineWidths[0]),
20171
y: me.top + labelOpts.padding,
20172
line: 0
20173
};
20174
} else {
20175
cursor = {
20176
x: me.left + labelOpts.padding,
20177
y: me.top + alignmentOffset(legendHeight, columnHeights[0]),
20178
line: 0
20179
};
20180
}
20181
20182
helpers$1.rtl.overrideTextDirection(me.ctx, opts.textDirection);
20183
20184
var itemHeight = fontSize + labelOpts.padding;
20185
helpers$1.each(me.legendItems, function(legendItem, i) {
20186
var textWidth = ctx.measureText(legendItem.text).width;
20187
var width = boxWidth + (fontSize / 2) + textWidth;
20188
var x = cursor.x;
20189
var y = cursor.y;
20190
20191
rtlHelper.setWidth(me.minSize.width);
20192
20193
// Use (me.left + me.minSize.width) and (me.top + me.minSize.height)
20194
// instead of me.right and me.bottom because me.width and me.height
20195
// may have been changed since me.minSize was calculated
20196
if (isHorizontal) {
20197
if (i > 0 && x + width + labelOpts.padding > me.left + me.minSize.width) {
20198
y = cursor.y += itemHeight;
20199
cursor.line++;
20200
x = cursor.x = me.left + alignmentOffset(legendWidth, lineWidths[cursor.line]);
20201
}
20202
} else if (i > 0 && y + itemHeight > me.top + me.minSize.height) {
20203
x = cursor.x = x + me.columnWidths[cursor.line] + labelOpts.padding;
20204
cursor.line++;
20205
y = cursor.y = me.top + alignmentOffset(legendHeight, columnHeights[cursor.line]);
20206
}
20207
20208
var realX = rtlHelper.x(x);
20209
20210
drawLegendBox(realX, y, legendItem);
20211
20212
hitboxes[i].left = rtlHelper.leftForLtr(realX, hitboxes[i].width);
20213
hitboxes[i].top = y;
20214
20215
// Fill the actual label
20216
fillText(realX, y, legendItem, textWidth);
20217
20218
if (isHorizontal) {
20219
cursor.x += width + labelOpts.padding;
20220
} else {
20221
cursor.y += itemHeight;
20222
}
20223
});
20224
20225
helpers$1.rtl.restoreTextDirection(me.ctx, opts.textDirection);
20226
},
20227
20228
/**
20229
* @private
20230
*/
20231
_getLegendItemAt: function(x, y) {
20232
var me = this;
20233
var i, hitBox, lh;
20234
20235
if (x >= me.left && x <= me.right && y >= me.top && y <= me.bottom) {
20236
// See if we are touching one of the dataset boxes
20237
lh = me.legendHitBoxes;
20238
for (i = 0; i < lh.length; ++i) {
20239
hitBox = lh[i];
20240
20241
if (x >= hitBox.left && x <= hitBox.left + hitBox.width && y >= hitBox.top && y <= hitBox.top + hitBox.height) {
20242
// Touching an element
20243
return me.legendItems[i];
20244
}
20245
}
20246
}
20247
20248
return null;
20249
},
20250
20251
/**
20252
* Handle an event
20253
* @private
20254
* @param {IEvent} event - The event to handle
20255
*/
20256
handleEvent: function(e) {
20257
var me = this;
20258
var opts = me.options;
20259
var type = e.type === 'mouseup' ? 'click' : e.type;
20260
var hoveredItem;
20261
20262
if (type === 'mousemove') {
20263
if (!opts.onHover && !opts.onLeave) {
20264
return;
20265
}
20266
} else if (type === 'click') {
20267
if (!opts.onClick) {
20268
return;
20269
}
20270
} else {
20271
return;
20272
}
20273
20274
// Chart event already has relative position in it
20275
hoveredItem = me._getLegendItemAt(e.x, e.y);
20276
20277
if (type === 'click') {
20278
if (hoveredItem && opts.onClick) {
20279
// use e.native for backwards compatibility
20280
opts.onClick.call(me, e.native, hoveredItem);
20281
}
20282
} else {
20283
if (opts.onLeave && hoveredItem !== me._hoveredItem) {
20284
if (me._hoveredItem) {
20285
opts.onLeave.call(me, e.native, me._hoveredItem);
20286
}
20287
me._hoveredItem = hoveredItem;
20288
}
20289
20290
if (opts.onHover && hoveredItem) {
20291
// use e.native for backwards compatibility
20292
opts.onHover.call(me, e.native, hoveredItem);
20293
}
20294
}
20295
}
20296
});
20297
20298
function createNewLegendAndAttach(chart, legendOpts) {
20299
var legend = new Legend({
20300
ctx: chart.ctx,
20301
options: legendOpts,
20302
chart: chart
20303
});
20304
20305
core_layouts.configure(chart, legend, legendOpts);
20306
core_layouts.addBox(chart, legend);
20307
chart.legend = legend;
20308
}
20309
20310
var plugin_legend = {
20311
id: 'legend',
20312
20313
/**
20314
* Backward compatibility: since 2.1.5, the legend is registered as a plugin, making
20315
* Chart.Legend obsolete. To avoid a breaking change, we export the Legend as part of
20316
* the plugin, which one will be re-exposed in the chart.js file.
20317
* https://github.com/chartjs/Chart.js/pull/2640
20318
* @private
20319
*/
20320
_element: Legend,
20321
20322
beforeInit: function(chart) {
20323
var legendOpts = chart.options.legend;
20324
20325
if (legendOpts) {
20326
createNewLegendAndAttach(chart, legendOpts);
20327
}
20328
},
20329
20330
beforeUpdate: function(chart) {
20331
var legendOpts = chart.options.legend;
20332
var legend = chart.legend;
20333
20334
if (legendOpts) {
20335
helpers$1.mergeIf(legendOpts, core_defaults.global.legend);
20336
20337
if (legend) {
20338
core_layouts.configure(chart, legend, legendOpts);
20339
legend.options = legendOpts;
20340
} else {
20341
createNewLegendAndAttach(chart, legendOpts);
20342
}
20343
} else if (legend) {
20344
core_layouts.removeBox(chart, legend);
20345
delete chart.legend;
20346
}
20347
},
20348
20349
afterEvent: function(chart, e) {
20350
var legend = chart.legend;
20351
if (legend) {
20352
legend.handleEvent(e);
20353
}
20354
}
20355
};
20356
20357
var noop$2 = helpers$1.noop;
20358
20359
core_defaults._set('global', {
20360
title: {
20361
display: false,
20362
fontStyle: 'bold',
20363
fullWidth: true,
20364
padding: 10,
20365
position: 'top',
20366
text: '',
20367
weight: 2000 // by default greater than legend (1000) to be above
20368
}
20369
});
20370
20371
/**
20372
* IMPORTANT: this class is exposed publicly as Chart.Legend, backward compatibility required!
20373
*/
20374
var Title = core_element.extend({
20375
initialize: function(config) {
20376
var me = this;
20377
helpers$1.extend(me, config);
20378
20379
// Contains hit boxes for each dataset (in dataset order)
20380
me.legendHitBoxes = [];
20381
},
20382
20383
// These methods are ordered by lifecycle. Utilities then follow.
20384
20385
beforeUpdate: noop$2,
20386
update: function(maxWidth, maxHeight, margins) {
20387
var me = this;
20388
20389
// Update Lifecycle - Probably don't want to ever extend or overwrite this function ;)
20390
me.beforeUpdate();
20391
20392
// Absorb the master measurements
20393
me.maxWidth = maxWidth;
20394
me.maxHeight = maxHeight;
20395
me.margins = margins;
20396
20397
// Dimensions
20398
me.beforeSetDimensions();
20399
me.setDimensions();
20400
me.afterSetDimensions();
20401
// Labels
20402
me.beforeBuildLabels();
20403
me.buildLabels();
20404
me.afterBuildLabels();
20405
20406
// Fit
20407
me.beforeFit();
20408
me.fit();
20409
me.afterFit();
20410
//
20411
me.afterUpdate();
20412
20413
return me.minSize;
20414
20415
},
20416
afterUpdate: noop$2,
20417
20418
//
20419
20420
beforeSetDimensions: noop$2,
20421
setDimensions: function() {
20422
var me = this;
20423
// Set the unconstrained dimension before label rotation
20424
if (me.isHorizontal()) {
20425
// Reset position before calculating rotation
20426
me.width = me.maxWidth;
20427
me.left = 0;
20428
me.right = me.width;
20429
} else {
20430
me.height = me.maxHeight;
20431
20432
// Reset position before calculating rotation
20433
me.top = 0;
20434
me.bottom = me.height;
20435
}
20436
20437
// Reset padding
20438
me.paddingLeft = 0;
20439
me.paddingTop = 0;
20440
me.paddingRight = 0;
20441
me.paddingBottom = 0;
20442
20443
// Reset minSize
20444
me.minSize = {
20445
width: 0,
20446
height: 0
20447
};
20448
},
20449
afterSetDimensions: noop$2,
20450
20451
//
20452
20453
beforeBuildLabels: noop$2,
20454
buildLabels: noop$2,
20455
afterBuildLabels: noop$2,
20456
20457
//
20458
20459
beforeFit: noop$2,
20460
fit: function() {
20461
var me = this;
20462
var opts = me.options;
20463
var minSize = me.minSize = {};
20464
var isHorizontal = me.isHorizontal();
20465
var lineCount, textSize;
20466
20467
if (!opts.display) {
20468
me.width = minSize.width = me.height = minSize.height = 0;
20469
return;
20470
}
20471
20472
lineCount = helpers$1.isArray(opts.text) ? opts.text.length : 1;
20473
textSize = lineCount * helpers$1.options._parseFont(opts).lineHeight + opts.padding * 2;
20474
20475
me.width = minSize.width = isHorizontal ? me.maxWidth : textSize;
20476
me.height = minSize.height = isHorizontal ? textSize : me.maxHeight;
20477
},
20478
afterFit: noop$2,
20479
20480
// Shared Methods
20481
isHorizontal: function() {
20482
var pos = this.options.position;
20483
return pos === 'top' || pos === 'bottom';
20484
},
20485
20486
// Actually draw the title block on the canvas
20487
draw: function() {
20488
var me = this;
20489
var ctx = me.ctx;
20490
var opts = me.options;
20491
20492
if (!opts.display) {
20493
return;
20494
}
20495
20496
var fontOpts = helpers$1.options._parseFont(opts);
20497
var lineHeight = fontOpts.lineHeight;
20498
var offset = lineHeight / 2 + opts.padding;
20499
var rotation = 0;
20500
var top = me.top;
20501
var left = me.left;
20502
var bottom = me.bottom;
20503
var right = me.right;
20504
var maxWidth, titleX, titleY;
20505
20506
ctx.fillStyle = helpers$1.valueOrDefault(opts.fontColor, core_defaults.global.defaultFontColor); // render in correct colour
20507
ctx.font = fontOpts.string;
20508
20509
// Horizontal
20510
if (me.isHorizontal()) {
20511
titleX = left + ((right - left) / 2); // midpoint of the width
20512
titleY = top + offset;
20513
maxWidth = right - left;
20514
} else {
20515
titleX = opts.position === 'left' ? left + offset : right - offset;
20516
titleY = top + ((bottom - top) / 2);
20517
maxWidth = bottom - top;
20518
rotation = Math.PI * (opts.position === 'left' ? -0.5 : 0.5);
20519
}
20520
20521
ctx.save();
20522
ctx.translate(titleX, titleY);
20523
ctx.rotate(rotation);
20524
ctx.textAlign = 'center';
20525
ctx.textBaseline = 'middle';
20526
20527
var text = opts.text;
20528
if (helpers$1.isArray(text)) {
20529
var y = 0;
20530
for (var i = 0; i < text.length; ++i) {
20531
ctx.fillText(text[i], 0, y, maxWidth);
20532
y += lineHeight;
20533
}
20534
} else {
20535
ctx.fillText(text, 0, 0, maxWidth);
20536
}
20537
20538
ctx.restore();
20539
}
20540
});
20541
20542
function createNewTitleBlockAndAttach(chart, titleOpts) {
20543
var title = new Title({
20544
ctx: chart.ctx,
20545
options: titleOpts,
20546
chart: chart
20547
});
20548
20549
core_layouts.configure(chart, title, titleOpts);
20550
core_layouts.addBox(chart, title);
20551
chart.titleBlock = title;
20552
}
20553
20554
var plugin_title = {
20555
id: 'title',
20556
20557
/**
20558
* Backward compatibility: since 2.1.5, the title is registered as a plugin, making
20559
* Chart.Title obsolete. To avoid a breaking change, we export the Title as part of
20560
* the plugin, which one will be re-exposed in the chart.js file.
20561
* https://github.com/chartjs/Chart.js/pull/2640
20562
* @private
20563
*/
20564
_element: Title,
20565
20566
beforeInit: function(chart) {
20567
var titleOpts = chart.options.title;
20568
20569
if (titleOpts) {
20570
createNewTitleBlockAndAttach(chart, titleOpts);
20571
}
20572
},
20573
20574
beforeUpdate: function(chart) {
20575
var titleOpts = chart.options.title;
20576
var titleBlock = chart.titleBlock;
20577
20578
if (titleOpts) {
20579
helpers$1.mergeIf(titleOpts, core_defaults.global.title);
20580
20581
if (titleBlock) {
20582
core_layouts.configure(chart, titleBlock, titleOpts);
20583
titleBlock.options = titleOpts;
20584
} else {
20585
createNewTitleBlockAndAttach(chart, titleOpts);
20586
}
20587
} else if (titleBlock) {
20588
core_layouts.removeBox(chart, titleBlock);
20589
delete chart.titleBlock;
20590
}
20591
}
20592
};
20593
20594
var plugins = {};
20595
var filler = plugin_filler;
20596
var legend = plugin_legend;
20597
var title = plugin_title;
20598
plugins.filler = filler;
20599
plugins.legend = legend;
20600
plugins.title = title;
20601
20602
/**
20603
* @namespace Chart
20604
*/
20605
20606
20607
core_controller.helpers = helpers$1;
20608
20609
// @todo dispatch these helpers into appropriated helpers/helpers.* file and write unit tests!
20610
core_helpers();
20611
20612
core_controller._adapters = core_adapters;
20613
core_controller.Animation = core_animation;
20614
core_controller.animationService = core_animations;
20615
core_controller.controllers = controllers;
20616
core_controller.DatasetController = core_datasetController;
20617
core_controller.defaults = core_defaults;
20618
core_controller.Element = core_element;
20619
core_controller.elements = elements;
20620
core_controller.Interaction = core_interaction;
20621
core_controller.layouts = core_layouts;
20622
core_controller.platform = platform;
20623
core_controller.plugins = core_plugins;
20624
core_controller.Scale = core_scale;
20625
core_controller.scaleService = core_scaleService;
20626
core_controller.Ticks = core_ticks;
20627
core_controller.Tooltip = core_tooltip;
20628
20629
// Register built-in scales
20630
20631
core_controller.helpers.each(scales, function(scale, type) {
20632
core_controller.scaleService.registerScaleType(type, scale, scale._defaults);
20633
});
20634
20635
// Load to register built-in adapters (as side effects)
20636
20637
20638
// Loading built-in plugins
20639
20640
for (var k in plugins) {
20641
if (plugins.hasOwnProperty(k)) {
20642
core_controller.plugins.register(plugins[k]);
20643
}
20644
}
20645
20646
core_controller.platform.initialize();
20647
20648
var src = core_controller;
20649
if (typeof window !== 'undefined') {
20650
window.Chart = core_controller;
20651
}
20652
20653
// DEPRECATIONS
20654
20655
/**
20656
* Provided for backward compatibility, not available anymore
20657
* @namespace Chart.Chart
20658
* @deprecated since version 2.8.0
20659
* @todo remove at version 3
20660
* @private
20661
*/
20662
core_controller.Chart = core_controller;
20663
20664
/**
20665
* Provided for backward compatibility, not available anymore
20666
* @namespace Chart.Legend
20667
* @deprecated since version 2.1.5
20668
* @todo remove at version 3
20669
* @private
20670
*/
20671
core_controller.Legend = plugins.legend._element;
20672
20673
/**
20674
* Provided for backward compatibility, not available anymore
20675
* @namespace Chart.Title
20676
* @deprecated since version 2.1.5
20677
* @todo remove at version 3
20678
* @private
20679
*/
20680
core_controller.Title = plugins.title._element;
20681
20682
/**
20683
* Provided for backward compatibility, use Chart.plugins instead
20684
* @namespace Chart.pluginService
20685
* @deprecated since version 2.1.5
20686
* @todo remove at version 3
20687
* @private
20688
*/
20689
core_controller.pluginService = core_controller.plugins;
20690
20691
/**
20692
* Provided for backward compatibility, inheriting from Chart.PlugingBase has no
20693
* effect, instead simply create/register plugins via plain JavaScript objects.
20694
* @interface Chart.PluginBase
20695
* @deprecated since version 2.5.0
20696
* @todo remove at version 3
20697
* @private
20698
*/
20699
core_controller.PluginBase = core_controller.Element.extend({});
20700
20701
/**
20702
* Provided for backward compatibility, use Chart.helpers.canvas instead.
20703
* @namespace Chart.canvasHelpers
20704
* @deprecated since version 2.6.0
20705
* @todo remove at version 3
20706
* @private
20707
*/
20708
core_controller.canvasHelpers = core_controller.helpers.canvas;
20709
20710
/**
20711
* Provided for backward compatibility, use Chart.layouts instead.
20712
* @namespace Chart.layoutService
20713
* @deprecated since version 2.7.3
20714
* @todo remove at version 3
20715
* @private
20716
*/
20717
core_controller.layoutService = core_controller.layouts;
20718
20719
/**
20720
* Provided for backward compatibility, not available anymore.
20721
* @namespace Chart.LinearScaleBase
20722
* @deprecated since version 2.8
20723
* @todo remove at version 3
20724
* @private
20725
*/
20726
core_controller.LinearScaleBase = scale_linearbase;
20727
20728
/**
20729
* Provided for backward compatibility, instead we should create a new Chart
20730
* by setting the type in the config (`new Chart(id, {type: '{chart-type}'}`).
20731
* @deprecated since version 2.8.0
20732
* @todo remove at version 3
20733
*/
20734
core_controller.helpers.each(
20735
[
20736
'Bar',
20737
'Bubble',
20738
'Doughnut',
20739
'Line',
20740
'PolarArea',
20741
'Radar',
20742
'Scatter'
20743
],
20744
function(klass) {
20745
core_controller[klass] = function(ctx, cfg) {
20746
return new core_controller(ctx, core_controller.helpers.merge(cfg || {}, {
20747
type: klass.charAt(0).toLowerCase() + klass.slice(1)
20748
}));
20749
};
20750
}
20751
);
20752
20753
return src;
20754
20755
})));
20756
20757