From 56a412108f8ab5291a371931f252f741d0db769b Mon Sep 17 00:00:00 2001 From: amki Date: Fri, 5 Feb 2021 06:41:05 +0100 Subject: [PATCH] First charts for ping rendering --- ISPChk/Controllers/HostController.cs | 12 +- ISPChk/Models/Host.cs | 2 + ISPChk/Models/PingItem.cs | 4 +- ISPChk/NetworkTest.cs | 30 +- ISPChk/Properties/launchSettings.json | 6 +- ISPChk/ispchk.db | Bin 20480 -> 20480 bytes ISPChk/ispchk.db-shm | Bin 32768 -> 32768 bytes ISPChk/ispchk.db-wal | Bin 24752 -> 3992312 bytes ISPChk/libman.json | 4 + ISPChk/wwwroot/Chart.js/Chart.bundle.js | 20776 ++++++++++++++++++ ISPChk/wwwroot/Chart.js/Chart.bundle.min.js | 7 + ISPChk/wwwroot/Chart.js/Chart.css | 47 + ISPChk/wwwroot/Chart.js/Chart.js | 16172 ++++++++++++++ ISPChk/wwwroot/Chart.js/Chart.min.css | 1 + ISPChk/wwwroot/Chart.js/Chart.min.js | 7 + ISPChk/wwwroot/index.html | 37 +- ISPChk/wwwroot/ispchk.js | 146 +- 17 files changed, 37200 insertions(+), 51 deletions(-) create mode 100644 ISPChk/wwwroot/Chart.js/Chart.bundle.js create mode 100644 ISPChk/wwwroot/Chart.js/Chart.bundle.min.js create mode 100644 ISPChk/wwwroot/Chart.js/Chart.css create mode 100644 ISPChk/wwwroot/Chart.js/Chart.js create mode 100644 ISPChk/wwwroot/Chart.js/Chart.min.css create mode 100644 ISPChk/wwwroot/Chart.js/Chart.min.js diff --git a/ISPChk/Controllers/HostController.cs b/ISPChk/Controllers/HostController.cs index 79d1b3b..bcb2fc4 100644 --- a/ISPChk/Controllers/HostController.cs +++ b/ISPChk/Controllers/HostController.cs @@ -45,9 +45,9 @@ namespace ISPChk.Controllers // GET: api/Host/5 [HttpGet("{id}/pings/{start}/{end?}")] - public async Task> GetPingsByTimeSpan(long id, long start, long end) + public async Task>> GetPingsByTimeSpan(long id, long start, long end) { - System.Diagnostics.Debug.WriteLine("called"); + System.Diagnostics.Debug.WriteLine("called id:" + id + " start:" + start + " end:" + end); var host = await _context.Hosts.FindAsync(id); if (host == null) @@ -55,7 +55,13 @@ namespace ISPChk.Controllers return NotFound(); } - return host; + var startTime = DateTimeOffset.FromUnixTimeMilliseconds(start); + var endTime = DateTimeOffset.FromUnixTimeMilliseconds(end); + + _context.Entry(host).Collection(h => h.PingItems).Load(); + var pis = host.PingItems.Where(p => p.Date > startTime).ToList(); + + return pis; } // PUT: api/Host/5 diff --git a/ISPChk/Models/Host.cs b/ISPChk/Models/Host.cs index c99d34a..c7b926c 100644 --- a/ISPChk/Models/Host.cs +++ b/ISPChk/Models/Host.cs @@ -10,6 +10,8 @@ namespace ISPChk.Models public long HostId { get; set; } public string Name { get; set; } public string HostName { get; set; } + public bool Ping { get; set; } + public bool TCP { get; set; } public List PingItems { get; set; } } diff --git a/ISPChk/Models/PingItem.cs b/ISPChk/Models/PingItem.cs index 46238bf..0265af4 100644 --- a/ISPChk/Models/PingItem.cs +++ b/ISPChk/Models/PingItem.cs @@ -10,11 +10,11 @@ namespace ISPChk.Models { [Key] public long PingId { get; set; } - public DateTime Date { get; set; } + public DateTimeOffset Date { get; set; } public float Min { get; set; } public float Max { get; set; } public float Avg { get; set; } + public int Failures { get; set; } public long HostId { get; set; } - public Host Host { get; set; } } } \ No newline at end of file diff --git a/ISPChk/NetworkTest.cs b/ISPChk/NetworkTest.cs index 78f0310..0f271fc 100644 --- a/ISPChk/NetworkTest.cs +++ b/ISPChk/NetworkTest.cs @@ -31,11 +31,10 @@ namespace ISPChk } } - public void AddHost(Host host) + private void StartPing(Host host) { - System.Diagnostics.Debug.WriteLine("Host added!"); var t = Task.Run(async () => { - + Ping pingSender = new Ping(); // Create a buffer of 32 bytes of data to be transmitted. @@ -55,11 +54,12 @@ namespace ISPChk while (true) { - long min = timeout+1; + long min = timeout + 1; long max = 0; long avg = 0; int successes = 0; - for(var i=0;i<5;++i) + int failures = 0; + for (var i = 0; i < 5; ++i) { // Send the request. PingReply reply = pingSender.Send(host.HostName, timeout, buffer, options); @@ -71,33 +71,41 @@ namespace ISPChk ": bytes=" + reply.Buffer.Length + " time=" + reply.RoundtripTime + "ms"); avg += reply.RoundtripTime; - if(reply.RoundtripTime < min) + if (reply.RoundtripTime < min) { min = reply.RoundtripTime; } - if(reply.RoundtripTime > max) + if (reply.RoundtripTime > max) { max = reply.RoundtripTime; } //System.Diagnostics.Debug.WriteLine("Time to live: {0}", reply.Options.Ttl); //System.Diagnostics.Debug.WriteLine("Don't fragment: {0}", reply.Options.DontFragment); - } + } else { System.Diagnostics.Debug.WriteLine(reply.Status); + failures++; } Thread.Sleep(500); } avg = avg / successes; System.Diagnostics.Debug.WriteLine("min:" + min + " max:" + max + " avg:" + avg); - PingItem pi = new PingItem { Date = DateTime.UtcNow, Min = min, Max = max, Avg = avg }; - if(host.PingItems == null) - host = _context.Hosts.Include(host => host.PingItems).Where(h => h.HostId == host.HostId).Single(); + PingItem pi = new PingItem { Date = DateTimeOffset.UtcNow, Min = min, Max = max, Avg = avg, Failures = failures }; host.PingItems.Add(pi); _context.SaveChanges(); Thread.Sleep(5000); } }); } + + public void AddHost(Host host) + { + System.Diagnostics.Debug.WriteLine("Host added!"); + if (host.PingItems == null) + host = _context.Hosts.Include(host => host.PingItems).Where(h => h.HostId == host.HostId).Single(); + if(host.Ping) + StartPing(host); + } } } diff --git a/ISPChk/Properties/launchSettings.json b/ISPChk/Properties/launchSettings.json index 0dc9796..d0e4101 100644 --- a/ISPChk/Properties/launchSettings.json +++ b/ISPChk/Properties/launchSettings.json @@ -4,7 +4,7 @@ "windowsAuthentication": false, "anonymousAuthentication": true, "iisExpress": { - "applicationUrl": "http://localhost:65025", + "applicationUrl": "http://localhost:4223", "sslPort": 0 } }, @@ -12,7 +12,7 @@ "IIS Express": { "commandName": "IISExpress", "launchBrowser": true, - "launchUrl": "/", + "launchUrl": "", "environmentVariables": { "ASPNETCORE_ENVIRONMENT": "Development" } @@ -22,7 +22,7 @@ "dotnetRunMessages": "true", "launchBrowser": true, "launchUrl": "/", - "applicationUrl": "http://localhost:5000", + "applicationUrl": "http://localhost:4223", "environmentVariables": { "ASPNETCORE_ENVIRONMENT": "Development" } diff --git a/ISPChk/ispchk.db b/ISPChk/ispchk.db index 854669c821d6faa69be3289b71511621ce177f3a..0c73603f423a8b0f6efe9009851053fbe9dcfc3e 100644 GIT binary patch delta 358 zcmZozz}T>WaY80H&jtp5LB9RG!aN%`Hrnt^=H*S{)MRECmz8B~Q=MGJ`$^C(F*B#M zD79Eg!P76q)!j8{ax~v3&KyQ|adC0RX7kBm{L4Lbc)1jSKq(+IFCDHKh1^ML$U_vjyC>^AViT?!y z|2O^@Ky`QcRd|?~Wf?(MdX}W-GI206i*tJ97nc-6R4!rQzr?>}v!K8X{tO)^4hDZl zMn(=!BLgEtT>~Rs15*V96Dw0QD-#Pn6GKB212YS40|P4q17QhK9Y)-$4fM>7j4h4L i%ps~pB*nPE?)uKa|A+rO&|RWaY80HFE0bX58p4|0$$$Df&w#nCU4+L;bdoK7nhY~Y)zaj%lm1v2fxJR z2mI`l1NaVc8Zxqri;FWhIZl@5U(UUEmLSqol`^&kHJA3G1coe1pr&F|vf_IICwuMsS;1P?6Ub_dUa_b&$m=a+w~?GS8tbqTTd*Cw zuonk#7{_oDXYe6D!RPn_7jXsGaSM0xH6G##zQuQViJt<~G8scKG{Z3>qcA#SF)kA@ zF_SSR(=a_VF)MR0H}kO|i?BFLu`DaFGOMvB>##l>u_;@yHQTWxyRbWZu`dU3Fo$s@ z$8bC+aVlqUHs^66mvA{(aVl~YAkQFYZ) zT{Tc+HB(EqQG0b#SM^YD_0vEN(Qu8@SWVDmP18)x(R?k^QmxQxt+E42`g>otfE!1>Q>9@S_5lr&8(%hvG&%^tGZT)PZ4YA=i z%EsCRn{3l;rp>YWw#b&+3R`XKY@=L`F2k zL>$CNA|yo$q((YqL>6R6F62c46h<+WL>ZJvB~(QX)J8otL=!YeE3`!ibVfJyL?86W zAPmI_jK(-j#1u@&EX>6MEXFdd#2T!}CTztH?8ZJE#1S0FDV)Ve_!Q@G0he$UH*g#G za37EG6wmNIUg2l_#8-Zm`pdH9eeKu~A~4HcI7CDgL`N*dMFJ#7GNeQrq(>%XMGoXf zJ`_X|6h|qPMFmtwHPl2M)JG#UMGLe>J9I=BbVo1r#Q+S(FpR_)jK?HQ#SF~GJS@Z# zEXOLW#RhD~HtfV6?8hM-#R;6o2lyDD;XJ;?Wn9Be+`(6PfXDa-&+!63;1`g=7?NQa zo{<=pF&LZin2<@BoT-?W8JL;bn3H*!pM_YIC0Lr}Sdmp&owZn(4cM5?*phA7o}Jj0 zJ=mN5IFLg)oTE6F6F8aEIFoZYpNqJZE4Z5LxRG19ox8Y~2Y8src#>!MAwS{g{DK#G zh1YqDclk9R@(I7?cYMj8`G!((g;H2WP-I0@OvO=rB~ntQP->-9MrBcU;eP;J#yLp4!zwNhJkP-k^hPxVoM4bo7J&}fa*L`~6j&C*;g&|)pqO0Cg) zZPHfl&~EM1K^@U?ozhu-q)&BD7j#M2bxU{kwI1q;zSVd7K|kw_WWgRCf;V$H3UwY3h`*}7Rz z>tp?GkPWpFHrmG7M4MvMZI;co1-96h*-Beu>ur;5wH>zG_Sr!@V#n>&UwY;TUcawp z?Z@Q;|5D%$#NZ6Yu#CXSjK-LZ!}v_Zq)froOvjAO!tBh&yez=NEXI;7!}6@es;t4< ztjC6I!scwnw(P*p?8ct#!~Pt^p&Y@{9LI^A!s(pFxm>`-T*j4L!}Z+6t=z%g+{c4F z!s9%}v;2sk@*FSl60hYn9e&gEniMc509I>yVD>gih-NeXP%PUSI06Zs@k|>AoK6 zsh;V3zvKUtN8NY#R^UhdBA8hS3vJ;nqD8Uj7R%yV0!wVkETyHf^p?r8S`N!?`K+K7 zvEo+B%31}hY}Kr$)v@~4$eLOUYi;eUqjjY^05`@ixh(+6Ujgf-4{rbf delta 174 zcmZo@U}|V!s+V}A%K!pQK+MR%AixHsS%FyUy#MK=8W-m>U1Of>qF%GTYDrJ{G5@!x zNmUOt3JjQmI*nkXnLjcmB BH2nYo diff --git a/ISPChk/ispchk.db-wal b/ISPChk/ispchk.db-wal index 32d8cc9f86a3207f0f861b9b421969b5adbe3401..9559866c202fb5e4e7e01db9ade8825cc43e1ceb 100644 GIT binary patch literal 3992312 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 z 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 Fe*sGCpy>br literal 24752 zcmeI(zb^w}9LMqJ?m?Z9YgD6=XbhD$l2-5fyRirZ25d$#G-5!c-AIJppU}Z%kr)hO zGMNlmNEZn~#Asm9G@h$eNEkb`-rwY&CwcM=_kBIvH~FMC+qOmc&L@OhIHh07;&^c5 zXzjV=y`8?Us3ZCPyxe`W5ZarHbUAm;Ct<#-g+c%U1Q0*~0R#|0009ILKmdV;7U@_A<^M$->s>NxnHZK@@$ldpzJgtAu1rCK-G7lTNaef{F1Q0*~ z0R#|0009ILKmdW}5(wxnF{){rVMlE%9JRyI#6Z-_CSuuGJYuJlnWWX@^A2h*G4|^% zI~hsDtxUXnP0_SEUu`bn8TRi7bNXY4I@HZKVV2cGA%Fk^2q1s}0tg_000IagfIu?| zv`9nKr8ErH)Q=p&@%zPyJFx2~N6<{qO)m%_fB*srAbm$M~*=Log*-| zD(?LJ+<`L}kmgP`N1!$e0R#|0009ILKmY**5I_I{1nMmikbflv;#MS+$fVM?$LFnU yj^J$Oysvl}I(2dc(tN4r2-HR)fB*srAb 1) { + h -= 1; + } + } + + return [ + h * 360, + s * 100, + v * 100 + ]; +}; + +convert.rgb.hwb = function (rgb) { + var r = rgb[0]; + var g = rgb[1]; + var b = rgb[2]; + var h = convert.rgb.hsl(rgb)[0]; + var w = 1 / 255 * Math.min(r, Math.min(g, b)); + + b = 1 - 1 / 255 * Math.max(r, Math.max(g, b)); + + return [h, w * 100, b * 100]; +}; + +convert.rgb.cmyk = function (rgb) { + var r = rgb[0] / 255; + var g = rgb[1] / 255; + var b = rgb[2] / 255; + var c; + var m; + var y; + var k; + + k = Math.min(1 - r, 1 - g, 1 - b); + c = (1 - r - k) / (1 - k) || 0; + m = (1 - g - k) / (1 - k) || 0; + y = (1 - b - k) / (1 - k) || 0; + + return [c * 100, m * 100, y * 100, k * 100]; +}; + +/** + * See https://en.m.wikipedia.org/wiki/Euclidean_distance#Squared_Euclidean_distance + * */ +function comparativeDistance(x, y) { + return ( + Math.pow(x[0] - y[0], 2) + + Math.pow(x[1] - y[1], 2) + + Math.pow(x[2] - y[2], 2) + ); +} + +convert.rgb.keyword = function (rgb) { + var reversed = reverseKeywords[rgb]; + if (reversed) { + return reversed; + } + + var currentClosestDistance = Infinity; + var currentClosestKeyword; + + for (var keyword in colorName) { + if (colorName.hasOwnProperty(keyword)) { + var value = colorName[keyword]; + + // Compute comparative distance + var distance = comparativeDistance(rgb, value); + + // Check if its less, if so set as closest + if (distance < currentClosestDistance) { + currentClosestDistance = distance; + currentClosestKeyword = keyword; + } + } + } + + return currentClosestKeyword; +}; + +convert.keyword.rgb = function (keyword) { + return colorName[keyword]; +}; + +convert.rgb.xyz = function (rgb) { + var r = rgb[0] / 255; + var g = rgb[1] / 255; + var b = rgb[2] / 255; + + // assume sRGB + r = r > 0.04045 ? Math.pow(((r + 0.055) / 1.055), 2.4) : (r / 12.92); + g = g > 0.04045 ? Math.pow(((g + 0.055) / 1.055), 2.4) : (g / 12.92); + b = b > 0.04045 ? Math.pow(((b + 0.055) / 1.055), 2.4) : (b / 12.92); + + var x = (r * 0.4124) + (g * 0.3576) + (b * 0.1805); + var y = (r * 0.2126) + (g * 0.7152) + (b * 0.0722); + var z = (r * 0.0193) + (g * 0.1192) + (b * 0.9505); + + return [x * 100, y * 100, z * 100]; +}; + +convert.rgb.lab = function (rgb) { + var xyz = convert.rgb.xyz(rgb); + var x = xyz[0]; + var y = xyz[1]; + var z = xyz[2]; + var l; + var a; + var b; + + x /= 95.047; + y /= 100; + z /= 108.883; + + x = x > 0.008856 ? Math.pow(x, 1 / 3) : (7.787 * x) + (16 / 116); + y = y > 0.008856 ? Math.pow(y, 1 / 3) : (7.787 * y) + (16 / 116); + z = z > 0.008856 ? Math.pow(z, 1 / 3) : (7.787 * z) + (16 / 116); + + l = (116 * y) - 16; + a = 500 * (x - y); + b = 200 * (y - z); + + return [l, a, b]; +}; + +convert.hsl.rgb = function (hsl) { + var h = hsl[0] / 360; + var s = hsl[1] / 100; + var l = hsl[2] / 100; + var t1; + var t2; + var t3; + var rgb; + var val; + + if (s === 0) { + val = l * 255; + return [val, val, val]; + } + + if (l < 0.5) { + t2 = l * (1 + s); + } else { + t2 = l + s - l * s; + } + + t1 = 2 * l - t2; + + rgb = [0, 0, 0]; + for (var i = 0; i < 3; i++) { + t3 = h + 1 / 3 * -(i - 1); + if (t3 < 0) { + t3++; + } + if (t3 > 1) { + t3--; + } + + if (6 * t3 < 1) { + val = t1 + (t2 - t1) * 6 * t3; + } else if (2 * t3 < 1) { + val = t2; + } else if (3 * t3 < 2) { + val = t1 + (t2 - t1) * (2 / 3 - t3) * 6; + } else { + val = t1; + } + + rgb[i] = val * 255; + } + + return rgb; +}; + +convert.hsl.hsv = function (hsl) { + var h = hsl[0]; + var s = hsl[1] / 100; + var l = hsl[2] / 100; + var smin = s; + var lmin = Math.max(l, 0.01); + var sv; + var v; + + l *= 2; + s *= (l <= 1) ? l : 2 - l; + smin *= lmin <= 1 ? lmin : 2 - lmin; + v = (l + s) / 2; + sv = l === 0 ? (2 * smin) / (lmin + smin) : (2 * s) / (l + s); + + return [h, sv * 100, v * 100]; +}; + +convert.hsv.rgb = function (hsv) { + var h = hsv[0] / 60; + var s = hsv[1] / 100; + var v = hsv[2] / 100; + var hi = Math.floor(h) % 6; + + var f = h - Math.floor(h); + var p = 255 * v * (1 - s); + var q = 255 * v * (1 - (s * f)); + var t = 255 * v * (1 - (s * (1 - f))); + v *= 255; + + switch (hi) { + case 0: + return [v, t, p]; + case 1: + return [q, v, p]; + case 2: + return [p, v, t]; + case 3: + return [p, q, v]; + case 4: + return [t, p, v]; + case 5: + return [v, p, q]; + } +}; + +convert.hsv.hsl = function (hsv) { + var h = hsv[0]; + var s = hsv[1] / 100; + var v = hsv[2] / 100; + var vmin = Math.max(v, 0.01); + var lmin; + var sl; + var l; + + l = (2 - s) * v; + lmin = (2 - s) * vmin; + sl = s * vmin; + sl /= (lmin <= 1) ? lmin : 2 - lmin; + sl = sl || 0; + l /= 2; + + return [h, sl * 100, l * 100]; +}; + +// http://dev.w3.org/csswg/css-color/#hwb-to-rgb +convert.hwb.rgb = function (hwb) { + var h = hwb[0] / 360; + var wh = hwb[1] / 100; + var bl = hwb[2] / 100; + var ratio = wh + bl; + var i; + var v; + var f; + var n; + + // wh + bl cant be > 1 + if (ratio > 1) { + wh /= ratio; + bl /= ratio; + } + + i = Math.floor(6 * h); + v = 1 - bl; + f = 6 * h - i; + + if ((i & 0x01) !== 0) { + f = 1 - f; + } + + n = wh + f * (v - wh); // linear interpolation + + var r; + var g; + var b; + switch (i) { + default: + case 6: + case 0: r = v; g = n; b = wh; break; + case 1: r = n; g = v; b = wh; break; + case 2: r = wh; g = v; b = n; break; + case 3: r = wh; g = n; b = v; break; + case 4: r = n; g = wh; b = v; break; + case 5: r = v; g = wh; b = n; break; + } + + return [r * 255, g * 255, b * 255]; +}; + +convert.cmyk.rgb = function (cmyk) { + var c = cmyk[0] / 100; + var m = cmyk[1] / 100; + var y = cmyk[2] / 100; + var k = cmyk[3] / 100; + var r; + var g; + var b; + + r = 1 - Math.min(1, c * (1 - k) + k); + g = 1 - Math.min(1, m * (1 - k) + k); + b = 1 - Math.min(1, y * (1 - k) + k); + + return [r * 255, g * 255, b * 255]; +}; + +convert.xyz.rgb = function (xyz) { + var x = xyz[0] / 100; + var y = xyz[1] / 100; + var z = xyz[2] / 100; + var r; + var g; + var b; + + r = (x * 3.2406) + (y * -1.5372) + (z * -0.4986); + g = (x * -0.9689) + (y * 1.8758) + (z * 0.0415); + b = (x * 0.0557) + (y * -0.2040) + (z * 1.0570); + + // assume sRGB + r = r > 0.0031308 + ? ((1.055 * Math.pow(r, 1.0 / 2.4)) - 0.055) + : r * 12.92; + + g = g > 0.0031308 + ? ((1.055 * Math.pow(g, 1.0 / 2.4)) - 0.055) + : g * 12.92; + + b = b > 0.0031308 + ? ((1.055 * Math.pow(b, 1.0 / 2.4)) - 0.055) + : b * 12.92; + + r = Math.min(Math.max(0, r), 1); + g = Math.min(Math.max(0, g), 1); + b = Math.min(Math.max(0, b), 1); + + return [r * 255, g * 255, b * 255]; +}; + +convert.xyz.lab = function (xyz) { + var x = xyz[0]; + var y = xyz[1]; + var z = xyz[2]; + var l; + var a; + var b; + + x /= 95.047; + y /= 100; + z /= 108.883; + + x = x > 0.008856 ? Math.pow(x, 1 / 3) : (7.787 * x) + (16 / 116); + y = y > 0.008856 ? Math.pow(y, 1 / 3) : (7.787 * y) + (16 / 116); + z = z > 0.008856 ? Math.pow(z, 1 / 3) : (7.787 * z) + (16 / 116); + + l = (116 * y) - 16; + a = 500 * (x - y); + b = 200 * (y - z); + + return [l, a, b]; +}; + +convert.lab.xyz = function (lab) { + var l = lab[0]; + var a = lab[1]; + var b = lab[2]; + var x; + var y; + var z; + + y = (l + 16) / 116; + x = a / 500 + y; + z = y - b / 200; + + var y2 = Math.pow(y, 3); + var x2 = Math.pow(x, 3); + var z2 = Math.pow(z, 3); + y = y2 > 0.008856 ? y2 : (y - 16 / 116) / 7.787; + x = x2 > 0.008856 ? x2 : (x - 16 / 116) / 7.787; + z = z2 > 0.008856 ? z2 : (z - 16 / 116) / 7.787; + + x *= 95.047; + y *= 100; + z *= 108.883; + + return [x, y, z]; +}; + +convert.lab.lch = function (lab) { + var l = lab[0]; + var a = lab[1]; + var b = lab[2]; + var hr; + var h; + var c; + + hr = Math.atan2(b, a); + h = hr * 360 / 2 / Math.PI; + + if (h < 0) { + h += 360; + } + + c = Math.sqrt(a * a + b * b); + + return [l, c, h]; +}; + +convert.lch.lab = function (lch) { + var l = lch[0]; + var c = lch[1]; + var h = lch[2]; + var a; + var b; + var hr; + + hr = h / 360 * 2 * Math.PI; + a = c * Math.cos(hr); + b = c * Math.sin(hr); + + return [l, a, b]; +}; + +convert.rgb.ansi16 = function (args) { + var r = args[0]; + var g = args[1]; + var b = args[2]; + var value = 1 in arguments ? arguments[1] : convert.rgb.hsv(args)[2]; // hsv -> ansi16 optimization + + value = Math.round(value / 50); + + if (value === 0) { + return 30; + } + + var ansi = 30 + + ((Math.round(b / 255) << 2) + | (Math.round(g / 255) << 1) + | Math.round(r / 255)); + + if (value === 2) { + ansi += 60; + } + + return ansi; +}; + +convert.hsv.ansi16 = function (args) { + // optimization here; we already know the value and don't need to get + // it converted for us. + return convert.rgb.ansi16(convert.hsv.rgb(args), args[2]); +}; + +convert.rgb.ansi256 = function (args) { + var r = args[0]; + var g = args[1]; + var b = args[2]; + + // we use the extended greyscale palette here, with the exception of + // black and white. normal palette only has 4 greyscale shades. + if (r === g && g === b) { + if (r < 8) { + return 16; + } + + if (r > 248) { + return 231; + } + + return Math.round(((r - 8) / 247) * 24) + 232; + } + + var ansi = 16 + + (36 * Math.round(r / 255 * 5)) + + (6 * Math.round(g / 255 * 5)) + + Math.round(b / 255 * 5); + + return ansi; +}; + +convert.ansi16.rgb = function (args) { + var color = args % 10; + + // handle greyscale + if (color === 0 || color === 7) { + if (args > 50) { + color += 3.5; + } + + color = color / 10.5 * 255; + + return [color, color, color]; + } + + var mult = (~~(args > 50) + 1) * 0.5; + var r = ((color & 1) * mult) * 255; + var g = (((color >> 1) & 1) * mult) * 255; + var b = (((color >> 2) & 1) * mult) * 255; + + return [r, g, b]; +}; + +convert.ansi256.rgb = function (args) { + // handle greyscale + if (args >= 232) { + var c = (args - 232) * 10 + 8; + return [c, c, c]; + } + + args -= 16; + + var rem; + var r = Math.floor(args / 36) / 5 * 255; + var g = Math.floor((rem = args % 36) / 6) / 5 * 255; + var b = (rem % 6) / 5 * 255; + + return [r, g, b]; +}; + +convert.rgb.hex = function (args) { + var integer = ((Math.round(args[0]) & 0xFF) << 16) + + ((Math.round(args[1]) & 0xFF) << 8) + + (Math.round(args[2]) & 0xFF); + + var string = integer.toString(16).toUpperCase(); + return '000000'.substring(string.length) + string; +}; + +convert.hex.rgb = function (args) { + var match = args.toString(16).match(/[a-f0-9]{6}|[a-f0-9]{3}/i); + if (!match) { + return [0, 0, 0]; + } + + var colorString = match[0]; + + if (match[0].length === 3) { + colorString = colorString.split('').map(function (char) { + return char + char; + }).join(''); + } + + var integer = parseInt(colorString, 16); + var r = (integer >> 16) & 0xFF; + var g = (integer >> 8) & 0xFF; + var b = integer & 0xFF; + + return [r, g, b]; +}; + +convert.rgb.hcg = function (rgb) { + var r = rgb[0] / 255; + var g = rgb[1] / 255; + var b = rgb[2] / 255; + var max = Math.max(Math.max(r, g), b); + var min = Math.min(Math.min(r, g), b); + var chroma = (max - min); + var grayscale; + var hue; + + if (chroma < 1) { + grayscale = min / (1 - chroma); + } else { + grayscale = 0; + } + + if (chroma <= 0) { + hue = 0; + } else + if (max === r) { + hue = ((g - b) / chroma) % 6; + } else + if (max === g) { + hue = 2 + (b - r) / chroma; + } else { + hue = 4 + (r - g) / chroma + 4; + } + + hue /= 6; + hue %= 1; + + return [hue * 360, chroma * 100, grayscale * 100]; +}; + +convert.hsl.hcg = function (hsl) { + var s = hsl[1] / 100; + var l = hsl[2] / 100; + var c = 1; + var f = 0; + + if (l < 0.5) { + c = 2.0 * s * l; + } else { + c = 2.0 * s * (1.0 - l); + } + + if (c < 1.0) { + f = (l - 0.5 * c) / (1.0 - c); + } + + return [hsl[0], c * 100, f * 100]; +}; + +convert.hsv.hcg = function (hsv) { + var s = hsv[1] / 100; + var v = hsv[2] / 100; + + var c = s * v; + var f = 0; + + if (c < 1.0) { + f = (v - c) / (1 - c); + } + + return [hsv[0], c * 100, f * 100]; +}; + +convert.hcg.rgb = function (hcg) { + var h = hcg[0] / 360; + var c = hcg[1] / 100; + var g = hcg[2] / 100; + + if (c === 0.0) { + return [g * 255, g * 255, g * 255]; + } + + var pure = [0, 0, 0]; + var hi = (h % 1) * 6; + var v = hi % 1; + var w = 1 - v; + var mg = 0; + + switch (Math.floor(hi)) { + case 0: + pure[0] = 1; pure[1] = v; pure[2] = 0; break; + case 1: + pure[0] = w; pure[1] = 1; pure[2] = 0; break; + case 2: + pure[0] = 0; pure[1] = 1; pure[2] = v; break; + case 3: + pure[0] = 0; pure[1] = w; pure[2] = 1; break; + case 4: + pure[0] = v; pure[1] = 0; pure[2] = 1; break; + default: + pure[0] = 1; pure[1] = 0; pure[2] = w; + } + + mg = (1.0 - c) * g; + + return [ + (c * pure[0] + mg) * 255, + (c * pure[1] + mg) * 255, + (c * pure[2] + mg) * 255 + ]; +}; + +convert.hcg.hsv = function (hcg) { + var c = hcg[1] / 100; + var g = hcg[2] / 100; + + var v = c + g * (1.0 - c); + var f = 0; + + if (v > 0.0) { + f = c / v; + } + + return [hcg[0], f * 100, v * 100]; +}; + +convert.hcg.hsl = function (hcg) { + var c = hcg[1] / 100; + var g = hcg[2] / 100; + + var l = g * (1.0 - c) + 0.5 * c; + var s = 0; + + if (l > 0.0 && l < 0.5) { + s = c / (2 * l); + } else + if (l >= 0.5 && l < 1.0) { + s = c / (2 * (1 - l)); + } + + return [hcg[0], s * 100, l * 100]; +}; + +convert.hcg.hwb = function (hcg) { + var c = hcg[1] / 100; + var g = hcg[2] / 100; + var v = c + g * (1.0 - c); + return [hcg[0], (v - c) * 100, (1 - v) * 100]; +}; + +convert.hwb.hcg = function (hwb) { + var w = hwb[1] / 100; + var b = hwb[2] / 100; + var v = 1 - b; + var c = v - w; + var g = 0; + + if (c < 1) { + g = (v - c) / (1 - c); + } + + return [hwb[0], c * 100, g * 100]; +}; + +convert.apple.rgb = function (apple) { + return [(apple[0] / 65535) * 255, (apple[1] / 65535) * 255, (apple[2] / 65535) * 255]; +}; + +convert.rgb.apple = function (rgb) { + return [(rgb[0] / 255) * 65535, (rgb[1] / 255) * 65535, (rgb[2] / 255) * 65535]; +}; + +convert.gray.rgb = function (args) { + return [args[0] / 100 * 255, args[0] / 100 * 255, args[0] / 100 * 255]; +}; + +convert.gray.hsl = convert.gray.hsv = function (args) { + return [0, 0, args[0]]; +}; + +convert.gray.hwb = function (gray) { + return [0, 100, gray[0]]; +}; + +convert.gray.cmyk = function (gray) { + return [0, 0, 0, gray[0]]; +}; + +convert.gray.lab = function (gray) { + return [gray[0], 0, 0]; +}; + +convert.gray.hex = function (gray) { + var val = Math.round(gray[0] / 100 * 255) & 0xFF; + var integer = (val << 16) + (val << 8) + val; + + var string = integer.toString(16).toUpperCase(); + return '000000'.substring(string.length) + string; +}; + +convert.rgb.gray = function (rgb) { + var val = (rgb[0] + rgb[1] + rgb[2]) / 3; + return [val / 255 * 100]; +}; +}); +var conversions_1 = conversions.rgb; +var conversions_2 = conversions.hsl; +var conversions_3 = conversions.hsv; +var conversions_4 = conversions.hwb; +var conversions_5 = conversions.cmyk; +var conversions_6 = conversions.xyz; +var conversions_7 = conversions.lab; +var conversions_8 = conversions.lch; +var conversions_9 = conversions.hex; +var conversions_10 = conversions.keyword; +var conversions_11 = conversions.ansi16; +var conversions_12 = conversions.ansi256; +var conversions_13 = conversions.hcg; +var conversions_14 = conversions.apple; +var conversions_15 = conversions.gray; + +/* + this function routes a model to all other models. + + all functions that are routed have a property `.conversion` attached + to the returned synthetic function. This property is an array + of strings, each with the steps in between the 'from' and 'to' + color models (inclusive). + + conversions that are not possible simply are not included. +*/ + +function buildGraph() { + var graph = {}; + // https://jsperf.com/object-keys-vs-for-in-with-closure/3 + var models = Object.keys(conversions); + + for (var len = models.length, i = 0; i < len; i++) { + graph[models[i]] = { + // http://jsperf.com/1-vs-infinity + // micro-opt, but this is simple. + distance: -1, + parent: null + }; + } + + return graph; +} + +// https://en.wikipedia.org/wiki/Breadth-first_search +function deriveBFS(fromModel) { + var graph = buildGraph(); + var queue = [fromModel]; // unshift -> queue -> pop + + graph[fromModel].distance = 0; + + while (queue.length) { + var current = queue.pop(); + var adjacents = Object.keys(conversions[current]); + + for (var len = adjacents.length, i = 0; i < len; i++) { + var adjacent = adjacents[i]; + var node = graph[adjacent]; + + if (node.distance === -1) { + node.distance = graph[current].distance + 1; + node.parent = current; + queue.unshift(adjacent); + } + } + } + + return graph; +} + +function link(from, to) { + return function (args) { + return to(from(args)); + }; +} + +function wrapConversion(toModel, graph) { + var path = [graph[toModel].parent, toModel]; + var fn = conversions[graph[toModel].parent][toModel]; + + var cur = graph[toModel].parent; + while (graph[cur].parent) { + path.unshift(graph[cur].parent); + fn = link(conversions[graph[cur].parent][cur], fn); + cur = graph[cur].parent; + } + + fn.conversion = path; + return fn; +} + +var route = function (fromModel) { + var graph = deriveBFS(fromModel); + var conversion = {}; + + var models = Object.keys(graph); + for (var len = models.length, i = 0; i < len; i++) { + var toModel = models[i]; + var node = graph[toModel]; + + if (node.parent === null) { + // no possible conversion, or this node is the source model. + continue; + } + + conversion[toModel] = wrapConversion(toModel, graph); + } + + return conversion; +}; + +var convert = {}; + +var models = Object.keys(conversions); + +function wrapRaw(fn) { + var wrappedFn = function (args) { + if (args === undefined || args === null) { + return args; + } + + if (arguments.length > 1) { + args = Array.prototype.slice.call(arguments); + } + + return fn(args); + }; + + // preserve .conversion property if there is one + if ('conversion' in fn) { + wrappedFn.conversion = fn.conversion; + } + + return wrappedFn; +} + +function wrapRounded(fn) { + var wrappedFn = function (args) { + if (args === undefined || args === null) { + return args; + } + + if (arguments.length > 1) { + args = Array.prototype.slice.call(arguments); + } + + var result = fn(args); + + // we're assuming the result is an array here. + // see notice in conversions.js; don't use box types + // in conversion functions. + if (typeof result === 'object') { + for (var len = result.length, i = 0; i < len; i++) { + result[i] = Math.round(result[i]); + } + } + + return result; + }; + + // preserve .conversion property if there is one + if ('conversion' in fn) { + wrappedFn.conversion = fn.conversion; + } + + return wrappedFn; +} + +models.forEach(function (fromModel) { + convert[fromModel] = {}; + + Object.defineProperty(convert[fromModel], 'channels', {value: conversions[fromModel].channels}); + Object.defineProperty(convert[fromModel], 'labels', {value: conversions[fromModel].labels}); + + var routes = route(fromModel); + var routeModels = Object.keys(routes); + + routeModels.forEach(function (toModel) { + var fn = routes[toModel]; + + convert[fromModel][toModel] = wrapRounded(fn); + convert[fromModel][toModel].raw = wrapRaw(fn); + }); +}); + +var colorConvert = convert; + +var colorName$1 = { + "aliceblue": [240, 248, 255], + "antiquewhite": [250, 235, 215], + "aqua": [0, 255, 255], + "aquamarine": [127, 255, 212], + "azure": [240, 255, 255], + "beige": [245, 245, 220], + "bisque": [255, 228, 196], + "black": [0, 0, 0], + "blanchedalmond": [255, 235, 205], + "blue": [0, 0, 255], + "blueviolet": [138, 43, 226], + "brown": [165, 42, 42], + "burlywood": [222, 184, 135], + "cadetblue": [95, 158, 160], + "chartreuse": [127, 255, 0], + "chocolate": [210, 105, 30], + "coral": [255, 127, 80], + "cornflowerblue": [100, 149, 237], + "cornsilk": [255, 248, 220], + "crimson": [220, 20, 60], + "cyan": [0, 255, 255], + "darkblue": [0, 0, 139], + "darkcyan": [0, 139, 139], + "darkgoldenrod": [184, 134, 11], + "darkgray": [169, 169, 169], + "darkgreen": [0, 100, 0], + "darkgrey": [169, 169, 169], + "darkkhaki": [189, 183, 107], + "darkmagenta": [139, 0, 139], + "darkolivegreen": [85, 107, 47], + "darkorange": [255, 140, 0], + "darkorchid": [153, 50, 204], + "darkred": [139, 0, 0], + "darksalmon": [233, 150, 122], + "darkseagreen": [143, 188, 143], + "darkslateblue": [72, 61, 139], + "darkslategray": [47, 79, 79], + "darkslategrey": [47, 79, 79], + "darkturquoise": [0, 206, 209], + "darkviolet": [148, 0, 211], + "deeppink": [255, 20, 147], + "deepskyblue": [0, 191, 255], + "dimgray": [105, 105, 105], + "dimgrey": [105, 105, 105], + "dodgerblue": [30, 144, 255], + "firebrick": [178, 34, 34], + "floralwhite": [255, 250, 240], + "forestgreen": [34, 139, 34], + "fuchsia": [255, 0, 255], + "gainsboro": [220, 220, 220], + "ghostwhite": [248, 248, 255], + "gold": [255, 215, 0], + "goldenrod": [218, 165, 32], + "gray": [128, 128, 128], + "green": [0, 128, 0], + "greenyellow": [173, 255, 47], + "grey": [128, 128, 128], + "honeydew": [240, 255, 240], + "hotpink": [255, 105, 180], + "indianred": [205, 92, 92], + "indigo": [75, 0, 130], + "ivory": [255, 255, 240], + "khaki": [240, 230, 140], + "lavender": [230, 230, 250], + "lavenderblush": [255, 240, 245], + "lawngreen": [124, 252, 0], + "lemonchiffon": [255, 250, 205], + "lightblue": [173, 216, 230], + "lightcoral": [240, 128, 128], + "lightcyan": [224, 255, 255], + "lightgoldenrodyellow": [250, 250, 210], + "lightgray": [211, 211, 211], + "lightgreen": [144, 238, 144], + "lightgrey": [211, 211, 211], + "lightpink": [255, 182, 193], + "lightsalmon": [255, 160, 122], + "lightseagreen": [32, 178, 170], + "lightskyblue": [135, 206, 250], + "lightslategray": [119, 136, 153], + "lightslategrey": [119, 136, 153], + "lightsteelblue": [176, 196, 222], + "lightyellow": [255, 255, 224], + "lime": [0, 255, 0], + "limegreen": [50, 205, 50], + "linen": [250, 240, 230], + "magenta": [255, 0, 255], + "maroon": [128, 0, 0], + "mediumaquamarine": [102, 205, 170], + "mediumblue": [0, 0, 205], + "mediumorchid": [186, 85, 211], + "mediumpurple": [147, 112, 219], + "mediumseagreen": [60, 179, 113], + "mediumslateblue": [123, 104, 238], + "mediumspringgreen": [0, 250, 154], + "mediumturquoise": [72, 209, 204], + "mediumvioletred": [199, 21, 133], + "midnightblue": [25, 25, 112], + "mintcream": [245, 255, 250], + "mistyrose": [255, 228, 225], + "moccasin": [255, 228, 181], + "navajowhite": [255, 222, 173], + "navy": [0, 0, 128], + "oldlace": [253, 245, 230], + "olive": [128, 128, 0], + "olivedrab": [107, 142, 35], + "orange": [255, 165, 0], + "orangered": [255, 69, 0], + "orchid": [218, 112, 214], + "palegoldenrod": [238, 232, 170], + "palegreen": [152, 251, 152], + "paleturquoise": [175, 238, 238], + "palevioletred": [219, 112, 147], + "papayawhip": [255, 239, 213], + "peachpuff": [255, 218, 185], + "peru": [205, 133, 63], + "pink": [255, 192, 203], + "plum": [221, 160, 221], + "powderblue": [176, 224, 230], + "purple": [128, 0, 128], + "rebeccapurple": [102, 51, 153], + "red": [255, 0, 0], + "rosybrown": [188, 143, 143], + "royalblue": [65, 105, 225], + "saddlebrown": [139, 69, 19], + "salmon": [250, 128, 114], + "sandybrown": [244, 164, 96], + "seagreen": [46, 139, 87], + "seashell": [255, 245, 238], + "sienna": [160, 82, 45], + "silver": [192, 192, 192], + "skyblue": [135, 206, 235], + "slateblue": [106, 90, 205], + "slategray": [112, 128, 144], + "slategrey": [112, 128, 144], + "snow": [255, 250, 250], + "springgreen": [0, 255, 127], + "steelblue": [70, 130, 180], + "tan": [210, 180, 140], + "teal": [0, 128, 128], + "thistle": [216, 191, 216], + "tomato": [255, 99, 71], + "turquoise": [64, 224, 208], + "violet": [238, 130, 238], + "wheat": [245, 222, 179], + "white": [255, 255, 255], + "whitesmoke": [245, 245, 245], + "yellow": [255, 255, 0], + "yellowgreen": [154, 205, 50] +}; + +/* MIT license */ + + +var colorString = { + getRgba: getRgba, + getHsla: getHsla, + getRgb: getRgb, + getHsl: getHsl, + getHwb: getHwb, + getAlpha: getAlpha, + + hexString: hexString, + rgbString: rgbString, + rgbaString: rgbaString, + percentString: percentString, + percentaString: percentaString, + hslString: hslString, + hslaString: hslaString, + hwbString: hwbString, + keyword: keyword +}; + +function getRgba(string) { + if (!string) { + return; + } + var abbr = /^#([a-fA-F0-9]{3,4})$/i, + hex = /^#([a-fA-F0-9]{6}([a-fA-F0-9]{2})?)$/i, + rgba = /^rgba?\(\s*([+-]?\d+)\s*,\s*([+-]?\d+)\s*,\s*([+-]?\d+)\s*(?:,\s*([+-]?[\d\.]+)\s*)?\)$/i, + per = /^rgba?\(\s*([+-]?[\d\.]+)\%\s*,\s*([+-]?[\d\.]+)\%\s*,\s*([+-]?[\d\.]+)\%\s*(?:,\s*([+-]?[\d\.]+)\s*)?\)$/i, + keyword = /(\w+)/; + + var rgb = [0, 0, 0], + a = 1, + match = string.match(abbr), + hexAlpha = ""; + if (match) { + match = match[1]; + hexAlpha = match[3]; + for (var i = 0; i < rgb.length; i++) { + rgb[i] = parseInt(match[i] + match[i], 16); + } + if (hexAlpha) { + a = Math.round((parseInt(hexAlpha + hexAlpha, 16) / 255) * 100) / 100; + } + } + else if (match = string.match(hex)) { + hexAlpha = match[2]; + match = match[1]; + for (var i = 0; i < rgb.length; i++) { + rgb[i] = parseInt(match.slice(i * 2, i * 2 + 2), 16); + } + if (hexAlpha) { + a = Math.round((parseInt(hexAlpha, 16) / 255) * 100) / 100; + } + } + else if (match = string.match(rgba)) { + for (var i = 0; i < rgb.length; i++) { + rgb[i] = parseInt(match[i + 1]); + } + a = parseFloat(match[4]); + } + else if (match = string.match(per)) { + for (var i = 0; i < rgb.length; i++) { + rgb[i] = Math.round(parseFloat(match[i + 1]) * 2.55); + } + a = parseFloat(match[4]); + } + else if (match = string.match(keyword)) { + if (match[1] == "transparent") { + return [0, 0, 0, 0]; + } + rgb = colorName$1[match[1]]; + if (!rgb) { + return; + } + } + + for (var i = 0; i < rgb.length; i++) { + rgb[i] = scale(rgb[i], 0, 255); + } + if (!a && a != 0) { + a = 1; + } + else { + a = scale(a, 0, 1); + } + rgb[3] = a; + return rgb; +} + +function getHsla(string) { + if (!string) { + return; + } + var hsl = /^hsla?\(\s*([+-]?\d+)(?:deg)?\s*,\s*([+-]?[\d\.]+)%\s*,\s*([+-]?[\d\.]+)%\s*(?:,\s*([+-]?[\d\.]+)\s*)?\)/; + var match = string.match(hsl); + if (match) { + var alpha = parseFloat(match[4]); + var h = scale(parseInt(match[1]), 0, 360), + s = scale(parseFloat(match[2]), 0, 100), + l = scale(parseFloat(match[3]), 0, 100), + a = scale(isNaN(alpha) ? 1 : alpha, 0, 1); + return [h, s, l, a]; + } +} + +function getHwb(string) { + if (!string) { + return; + } + var hwb = /^hwb\(\s*([+-]?\d+)(?:deg)?\s*,\s*([+-]?[\d\.]+)%\s*,\s*([+-]?[\d\.]+)%\s*(?:,\s*([+-]?[\d\.]+)\s*)?\)/; + var match = string.match(hwb); + if (match) { + var alpha = parseFloat(match[4]); + var h = scale(parseInt(match[1]), 0, 360), + w = scale(parseFloat(match[2]), 0, 100), + b = scale(parseFloat(match[3]), 0, 100), + a = scale(isNaN(alpha) ? 1 : alpha, 0, 1); + return [h, w, b, a]; + } +} + +function getRgb(string) { + var rgba = getRgba(string); + return rgba && rgba.slice(0, 3); +} + +function getHsl(string) { + var hsla = getHsla(string); + return hsla && hsla.slice(0, 3); +} + +function getAlpha(string) { + var vals = getRgba(string); + if (vals) { + return vals[3]; + } + else if (vals = getHsla(string)) { + return vals[3]; + } + else if (vals = getHwb(string)) { + return vals[3]; + } +} + +// generators +function hexString(rgba, a) { + var a = (a !== undefined && rgba.length === 3) ? a : rgba[3]; + return "#" + hexDouble(rgba[0]) + + hexDouble(rgba[1]) + + hexDouble(rgba[2]) + + ( + (a >= 0 && a < 1) + ? hexDouble(Math.round(a * 255)) + : "" + ); +} + +function rgbString(rgba, alpha) { + if (alpha < 1 || (rgba[3] && rgba[3] < 1)) { + return rgbaString(rgba, alpha); + } + return "rgb(" + rgba[0] + ", " + rgba[1] + ", " + rgba[2] + ")"; +} + +function rgbaString(rgba, alpha) { + if (alpha === undefined) { + alpha = (rgba[3] !== undefined ? rgba[3] : 1); + } + return "rgba(" + rgba[0] + ", " + rgba[1] + ", " + rgba[2] + + ", " + alpha + ")"; +} + +function percentString(rgba, alpha) { + if (alpha < 1 || (rgba[3] && rgba[3] < 1)) { + return percentaString(rgba, alpha); + } + var r = Math.round(rgba[0]/255 * 100), + g = Math.round(rgba[1]/255 * 100), + b = Math.round(rgba[2]/255 * 100); + + return "rgb(" + r + "%, " + g + "%, " + b + "%)"; +} + +function percentaString(rgba, alpha) { + var r = Math.round(rgba[0]/255 * 100), + g = Math.round(rgba[1]/255 * 100), + b = Math.round(rgba[2]/255 * 100); + return "rgba(" + r + "%, " + g + "%, " + b + "%, " + (alpha || rgba[3] || 1) + ")"; +} + +function hslString(hsla, alpha) { + if (alpha < 1 || (hsla[3] && hsla[3] < 1)) { + return hslaString(hsla, alpha); + } + return "hsl(" + hsla[0] + ", " + hsla[1] + "%, " + hsla[2] + "%)"; +} + +function hslaString(hsla, alpha) { + if (alpha === undefined) { + alpha = (hsla[3] !== undefined ? hsla[3] : 1); + } + return "hsla(" + hsla[0] + ", " + hsla[1] + "%, " + hsla[2] + "%, " + + alpha + ")"; +} + +// hwb is a bit different than rgb(a) & hsl(a) since there is no alpha specific syntax +// (hwb have alpha optional & 1 is default value) +function hwbString(hwb, alpha) { + if (alpha === undefined) { + alpha = (hwb[3] !== undefined ? hwb[3] : 1); + } + return "hwb(" + hwb[0] + ", " + hwb[1] + "%, " + hwb[2] + "%" + + (alpha !== undefined && alpha !== 1 ? ", " + alpha : "") + ")"; +} + +function keyword(rgb) { + return reverseNames[rgb.slice(0, 3)]; +} + +// helpers +function scale(num, min, max) { + return Math.min(Math.max(min, num), max); +} + +function hexDouble(num) { + var str = num.toString(16).toUpperCase(); + return (str.length < 2) ? "0" + str : str; +} + + +//create a list of reverse color names +var reverseNames = {}; +for (var name in colorName$1) { + reverseNames[colorName$1[name]] = name; +} + +/* MIT license */ + + + +var Color = function (obj) { + if (obj instanceof Color) { + return obj; + } + if (!(this instanceof Color)) { + return new Color(obj); + } + + this.valid = false; + this.values = { + rgb: [0, 0, 0], + hsl: [0, 0, 0], + hsv: [0, 0, 0], + hwb: [0, 0, 0], + cmyk: [0, 0, 0, 0], + alpha: 1 + }; + + // parse Color() argument + var vals; + if (typeof obj === 'string') { + vals = colorString.getRgba(obj); + if (vals) { + this.setValues('rgb', vals); + } else if (vals = colorString.getHsla(obj)) { + this.setValues('hsl', vals); + } else if (vals = colorString.getHwb(obj)) { + this.setValues('hwb', vals); + } + } else if (typeof obj === 'object') { + vals = obj; + if (vals.r !== undefined || vals.red !== undefined) { + this.setValues('rgb', vals); + } else if (vals.l !== undefined || vals.lightness !== undefined) { + this.setValues('hsl', vals); + } else if (vals.v !== undefined || vals.value !== undefined) { + this.setValues('hsv', vals); + } else if (vals.w !== undefined || vals.whiteness !== undefined) { + this.setValues('hwb', vals); + } else if (vals.c !== undefined || vals.cyan !== undefined) { + this.setValues('cmyk', vals); + } + } +}; + +Color.prototype = { + isValid: function () { + return this.valid; + }, + rgb: function () { + return this.setSpace('rgb', arguments); + }, + hsl: function () { + return this.setSpace('hsl', arguments); + }, + hsv: function () { + return this.setSpace('hsv', arguments); + }, + hwb: function () { + return this.setSpace('hwb', arguments); + }, + cmyk: function () { + return this.setSpace('cmyk', arguments); + }, + + rgbArray: function () { + return this.values.rgb; + }, + hslArray: function () { + return this.values.hsl; + }, + hsvArray: function () { + return this.values.hsv; + }, + hwbArray: function () { + var values = this.values; + if (values.alpha !== 1) { + return values.hwb.concat([values.alpha]); + } + return values.hwb; + }, + cmykArray: function () { + return this.values.cmyk; + }, + rgbaArray: function () { + var values = this.values; + return values.rgb.concat([values.alpha]); + }, + hslaArray: function () { + var values = this.values; + return values.hsl.concat([values.alpha]); + }, + alpha: function (val) { + if (val === undefined) { + return this.values.alpha; + } + this.setValues('alpha', val); + return this; + }, + + red: function (val) { + return this.setChannel('rgb', 0, val); + }, + green: function (val) { + return this.setChannel('rgb', 1, val); + }, + blue: function (val) { + return this.setChannel('rgb', 2, val); + }, + hue: function (val) { + if (val) { + val %= 360; + val = val < 0 ? 360 + val : val; + } + return this.setChannel('hsl', 0, val); + }, + saturation: function (val) { + return this.setChannel('hsl', 1, val); + }, + lightness: function (val) { + return this.setChannel('hsl', 2, val); + }, + saturationv: function (val) { + return this.setChannel('hsv', 1, val); + }, + whiteness: function (val) { + return this.setChannel('hwb', 1, val); + }, + blackness: function (val) { + return this.setChannel('hwb', 2, val); + }, + value: function (val) { + return this.setChannel('hsv', 2, val); + }, + cyan: function (val) { + return this.setChannel('cmyk', 0, val); + }, + magenta: function (val) { + return this.setChannel('cmyk', 1, val); + }, + yellow: function (val) { + return this.setChannel('cmyk', 2, val); + }, + black: function (val) { + return this.setChannel('cmyk', 3, val); + }, + + hexString: function () { + return colorString.hexString(this.values.rgb); + }, + rgbString: function () { + return colorString.rgbString(this.values.rgb, this.values.alpha); + }, + rgbaString: function () { + return colorString.rgbaString(this.values.rgb, this.values.alpha); + }, + percentString: function () { + return colorString.percentString(this.values.rgb, this.values.alpha); + }, + hslString: function () { + return colorString.hslString(this.values.hsl, this.values.alpha); + }, + hslaString: function () { + return colorString.hslaString(this.values.hsl, this.values.alpha); + }, + hwbString: function () { + return colorString.hwbString(this.values.hwb, this.values.alpha); + }, + keyword: function () { + return colorString.keyword(this.values.rgb, this.values.alpha); + }, + + rgbNumber: function () { + var rgb = this.values.rgb; + return (rgb[0] << 16) | (rgb[1] << 8) | rgb[2]; + }, + + luminosity: function () { + // http://www.w3.org/TR/WCAG20/#relativeluminancedef + var rgb = this.values.rgb; + var lum = []; + for (var i = 0; i < rgb.length; i++) { + var chan = rgb[i] / 255; + lum[i] = (chan <= 0.03928) ? chan / 12.92 : Math.pow(((chan + 0.055) / 1.055), 2.4); + } + return 0.2126 * lum[0] + 0.7152 * lum[1] + 0.0722 * lum[2]; + }, + + contrast: function (color2) { + // http://www.w3.org/TR/WCAG20/#contrast-ratiodef + var lum1 = this.luminosity(); + var lum2 = color2.luminosity(); + if (lum1 > lum2) { + return (lum1 + 0.05) / (lum2 + 0.05); + } + return (lum2 + 0.05) / (lum1 + 0.05); + }, + + level: function (color2) { + var contrastRatio = this.contrast(color2); + if (contrastRatio >= 7.1) { + return 'AAA'; + } + + return (contrastRatio >= 4.5) ? 'AA' : ''; + }, + + dark: function () { + // YIQ equation from http://24ways.org/2010/calculating-color-contrast + var rgb = this.values.rgb; + var yiq = (rgb[0] * 299 + rgb[1] * 587 + rgb[2] * 114) / 1000; + return yiq < 128; + }, + + light: function () { + return !this.dark(); + }, + + negate: function () { + var rgb = []; + for (var i = 0; i < 3; i++) { + rgb[i] = 255 - this.values.rgb[i]; + } + this.setValues('rgb', rgb); + return this; + }, + + lighten: function (ratio) { + var hsl = this.values.hsl; + hsl[2] += hsl[2] * ratio; + this.setValues('hsl', hsl); + return this; + }, + + darken: function (ratio) { + var hsl = this.values.hsl; + hsl[2] -= hsl[2] * ratio; + this.setValues('hsl', hsl); + return this; + }, + + saturate: function (ratio) { + var hsl = this.values.hsl; + hsl[1] += hsl[1] * ratio; + this.setValues('hsl', hsl); + return this; + }, + + desaturate: function (ratio) { + var hsl = this.values.hsl; + hsl[1] -= hsl[1] * ratio; + this.setValues('hsl', hsl); + return this; + }, + + whiten: function (ratio) { + var hwb = this.values.hwb; + hwb[1] += hwb[1] * ratio; + this.setValues('hwb', hwb); + return this; + }, + + blacken: function (ratio) { + var hwb = this.values.hwb; + hwb[2] += hwb[2] * ratio; + this.setValues('hwb', hwb); + return this; + }, + + greyscale: function () { + var rgb = this.values.rgb; + // http://en.wikipedia.org/wiki/Grayscale#Converting_color_to_grayscale + var val = rgb[0] * 0.3 + rgb[1] * 0.59 + rgb[2] * 0.11; + this.setValues('rgb', [val, val, val]); + return this; + }, + + clearer: function (ratio) { + var alpha = this.values.alpha; + this.setValues('alpha', alpha - (alpha * ratio)); + return this; + }, + + opaquer: function (ratio) { + var alpha = this.values.alpha; + this.setValues('alpha', alpha + (alpha * ratio)); + return this; + }, + + rotate: function (degrees) { + var hsl = this.values.hsl; + var hue = (hsl[0] + degrees) % 360; + hsl[0] = hue < 0 ? 360 + hue : hue; + this.setValues('hsl', hsl); + return this; + }, + + /** + * Ported from sass implementation in C + * https://github.com/sass/libsass/blob/0e6b4a2850092356aa3ece07c6b249f0221caced/functions.cpp#L209 + */ + mix: function (mixinColor, weight) { + var color1 = this; + var color2 = mixinColor; + var p = weight === undefined ? 0.5 : weight; + + var w = 2 * p - 1; + var a = color1.alpha() - color2.alpha(); + + var w1 = (((w * a === -1) ? w : (w + a) / (1 + w * a)) + 1) / 2.0; + var w2 = 1 - w1; + + return this + .rgb( + w1 * color1.red() + w2 * color2.red(), + w1 * color1.green() + w2 * color2.green(), + w1 * color1.blue() + w2 * color2.blue() + ) + .alpha(color1.alpha() * p + color2.alpha() * (1 - p)); + }, + + toJSON: function () { + return this.rgb(); + }, + + clone: function () { + // NOTE(SB): using node-clone creates a dependency to Buffer when using browserify, + // making the final build way to big to embed in Chart.js. So let's do it manually, + // assuming that values to clone are 1 dimension arrays containing only numbers, + // except 'alpha' which is a number. + var result = new Color(); + var source = this.values; + var target = result.values; + var value, type; + + for (var prop in source) { + if (source.hasOwnProperty(prop)) { + value = source[prop]; + type = ({}).toString.call(value); + if (type === '[object Array]') { + target[prop] = value.slice(0); + } else if (type === '[object Number]') { + target[prop] = value; + } else { + console.error('unexpected color value:', value); + } + } + } + + return result; + } +}; + +Color.prototype.spaces = { + rgb: ['red', 'green', 'blue'], + hsl: ['hue', 'saturation', 'lightness'], + hsv: ['hue', 'saturation', 'value'], + hwb: ['hue', 'whiteness', 'blackness'], + cmyk: ['cyan', 'magenta', 'yellow', 'black'] +}; + +Color.prototype.maxes = { + rgb: [255, 255, 255], + hsl: [360, 100, 100], + hsv: [360, 100, 100], + hwb: [360, 100, 100], + cmyk: [100, 100, 100, 100] +}; + +Color.prototype.getValues = function (space) { + var values = this.values; + var vals = {}; + + for (var i = 0; i < space.length; i++) { + vals[space.charAt(i)] = values[space][i]; + } + + if (values.alpha !== 1) { + vals.a = values.alpha; + } + + // {r: 255, g: 255, b: 255, a: 0.4} + return vals; +}; + +Color.prototype.setValues = function (space, vals) { + var values = this.values; + var spaces = this.spaces; + var maxes = this.maxes; + var alpha = 1; + var i; + + this.valid = true; + + if (space === 'alpha') { + alpha = vals; + } else if (vals.length) { + // [10, 10, 10] + values[space] = vals.slice(0, space.length); + alpha = vals[space.length]; + } else if (vals[space.charAt(0)] !== undefined) { + // {r: 10, g: 10, b: 10} + for (i = 0; i < space.length; i++) { + values[space][i] = vals[space.charAt(i)]; + } + + alpha = vals.a; + } else if (vals[spaces[space][0]] !== undefined) { + // {red: 10, green: 10, blue: 10} + var chans = spaces[space]; + + for (i = 0; i < space.length; i++) { + values[space][i] = vals[chans[i]]; + } + + alpha = vals.alpha; + } + + values.alpha = Math.max(0, Math.min(1, (alpha === undefined ? values.alpha : alpha))); + + if (space === 'alpha') { + return false; + } + + var capped; + + // cap values of the space prior converting all values + for (i = 0; i < space.length; i++) { + capped = Math.max(0, Math.min(maxes[space][i], values[space][i])); + values[space][i] = Math.round(capped); + } + + // convert to all the other color spaces + for (var sname in spaces) { + if (sname !== space) { + values[sname] = colorConvert[space][sname](values[space]); + } + } + + return true; +}; + +Color.prototype.setSpace = function (space, args) { + var vals = args[0]; + + if (vals === undefined) { + // color.rgb() + return this.getValues(space); + } + + // color.rgb(10, 10, 10) + if (typeof vals === 'number') { + vals = Array.prototype.slice.call(args); + } + + this.setValues(space, vals); + return this; +}; + +Color.prototype.setChannel = function (space, index, val) { + var svalues = this.values[space]; + if (val === undefined) { + // color.red() + return svalues[index]; + } else if (val === svalues[index]) { + // color.red(color.red()) + return this; + } + + // color.red(100) + svalues[index] = val; + this.setValues(space, svalues); + + return this; +}; + +if (typeof window !== 'undefined') { + window.Color = Color; +} + +var chartjsColor = Color; + +function isValidKey(key) { + return ['__proto__', 'prototype', 'constructor'].indexOf(key) === -1; +} + +/** + * @namespace Chart.helpers + */ +var helpers = { + /** + * An empty function that can be used, for example, for optional callback. + */ + noop: function() {}, + + /** + * Returns a unique id, sequentially generated from a global variable. + * @returns {number} + * @function + */ + uid: (function() { + var id = 0; + return function() { + return id++; + }; + }()), + + /** + * Returns true if `value` is neither null nor undefined, else returns false. + * @param {*} value - The value to test. + * @returns {boolean} + * @since 2.7.0 + */ + isNullOrUndef: function(value) { + return value === null || typeof value === 'undefined'; + }, + + /** + * Returns true if `value` is an array (including typed arrays), else returns false. + * @param {*} value - The value to test. + * @returns {boolean} + * @function + */ + isArray: function(value) { + if (Array.isArray && Array.isArray(value)) { + return true; + } + var type = Object.prototype.toString.call(value); + if (type.substr(0, 7) === '[object' && type.substr(-6) === 'Array]') { + return true; + } + return false; + }, + + /** + * Returns true if `value` is an object (excluding null), else returns false. + * @param {*} value - The value to test. + * @returns {boolean} + * @since 2.7.0 + */ + isObject: function(value) { + return value !== null && Object.prototype.toString.call(value) === '[object Object]'; + }, + + /** + * Returns true if `value` is a finite number, else returns false + * @param {*} value - The value to test. + * @returns {boolean} + */ + isFinite: function(value) { + return (typeof value === 'number' || value instanceof Number) && isFinite(value); + }, + + /** + * Returns `value` if defined, else returns `defaultValue`. + * @param {*} value - The value to return if defined. + * @param {*} defaultValue - The value to return if `value` is undefined. + * @returns {*} + */ + valueOrDefault: function(value, defaultValue) { + return typeof value === 'undefined' ? defaultValue : value; + }, + + /** + * Returns value at the given `index` in array if defined, else returns `defaultValue`. + * @param {Array} value - The array to lookup for value at `index`. + * @param {number} index - The index in `value` to lookup for value. + * @param {*} defaultValue - The value to return if `value[index]` is undefined. + * @returns {*} + */ + valueAtIndexOrDefault: function(value, index, defaultValue) { + return helpers.valueOrDefault(helpers.isArray(value) ? value[index] : value, defaultValue); + }, + + /** + * Calls `fn` with the given `args` in the scope defined by `thisArg` and returns the + * value returned by `fn`. If `fn` is not a function, this method returns undefined. + * @param {function} fn - The function to call. + * @param {Array|undefined|null} args - The arguments with which `fn` should be called. + * @param {object} [thisArg] - The value of `this` provided for the call to `fn`. + * @returns {*} + */ + callback: function(fn, args, thisArg) { + if (fn && typeof fn.call === 'function') { + return fn.apply(thisArg, args); + } + }, + + /** + * Note(SB) for performance sake, this method should only be used when loopable type + * is unknown or in none intensive code (not called often and small loopable). Else + * it's preferable to use a regular for() loop and save extra function calls. + * @param {object|Array} loopable - The object or array to be iterated. + * @param {function} fn - The function to call for each item. + * @param {object} [thisArg] - The value of `this` provided for the call to `fn`. + * @param {boolean} [reverse] - If true, iterates backward on the loopable. + */ + each: function(loopable, fn, thisArg, reverse) { + var i, len, keys; + if (helpers.isArray(loopable)) { + len = loopable.length; + if (reverse) { + for (i = len - 1; i >= 0; i--) { + fn.call(thisArg, loopable[i], i); + } + } else { + for (i = 0; i < len; i++) { + fn.call(thisArg, loopable[i], i); + } + } + } else if (helpers.isObject(loopable)) { + keys = Object.keys(loopable); + len = keys.length; + for (i = 0; i < len; i++) { + fn.call(thisArg, loopable[keys[i]], keys[i]); + } + } + }, + + /** + * Returns true if the `a0` and `a1` arrays have the same content, else returns false. + * @see https://stackoverflow.com/a/14853974 + * @param {Array} a0 - The array to compare + * @param {Array} a1 - The array to compare + * @returns {boolean} + */ + arrayEquals: function(a0, a1) { + var i, ilen, v0, v1; + + if (!a0 || !a1 || a0.length !== a1.length) { + return false; + } + + for (i = 0, ilen = a0.length; i < ilen; ++i) { + v0 = a0[i]; + v1 = a1[i]; + + if (v0 instanceof Array && v1 instanceof Array) { + if (!helpers.arrayEquals(v0, v1)) { + return false; + } + } else if (v0 !== v1) { + // NOTE: two different object instances will never be equal: {x:20} != {x:20} + return false; + } + } + + return true; + }, + + /** + * Returns a deep copy of `source` without keeping references on objects and arrays. + * @param {*} source - The value to clone. + * @returns {*} + */ + clone: function(source) { + if (helpers.isArray(source)) { + return source.map(helpers.clone); + } + + if (helpers.isObject(source)) { + var target = Object.create(source); + var keys = Object.keys(source); + var klen = keys.length; + var k = 0; + + for (; k < klen; ++k) { + target[keys[k]] = helpers.clone(source[keys[k]]); + } + + return target; + } + + return source; + }, + + /** + * The default merger when Chart.helpers.merge is called without merger option. + * Note(SB): also used by mergeConfig and mergeScaleConfig as fallback. + * @private + */ + _merger: function(key, target, source, options) { + if (!isValidKey(key)) { + // We want to ensure we do not copy prototypes over + // as this can pollute global namespaces + return; + } + + var tval = target[key]; + var sval = source[key]; + + if (helpers.isObject(tval) && helpers.isObject(sval)) { + helpers.merge(tval, sval, options); + } else { + target[key] = helpers.clone(sval); + } + }, + + /** + * Merges source[key] in target[key] only if target[key] is undefined. + * @private + */ + _mergerIf: function(key, target, source) { + if (!isValidKey(key)) { + // We want to ensure we do not copy prototypes over + // as this can pollute global namespaces + return; + } + + var tval = target[key]; + var sval = source[key]; + + if (helpers.isObject(tval) && helpers.isObject(sval)) { + helpers.mergeIf(tval, sval); + } else if (!target.hasOwnProperty(key)) { + target[key] = helpers.clone(sval); + } + }, + + /** + * Recursively deep copies `source` properties into `target` with the given `options`. + * IMPORTANT: `target` is not cloned and will be updated with `source` properties. + * @param {object} target - The target object in which all sources are merged into. + * @param {object|object[]} source - Object(s) to merge into `target`. + * @param {object} [options] - Merging options: + * @param {function} [options.merger] - The merge method (key, target, source, options) + * @returns {object} The `target` object. + */ + merge: function(target, source, options) { + var sources = helpers.isArray(source) ? source : [source]; + var ilen = sources.length; + var merge, i, keys, klen, k; + + if (!helpers.isObject(target)) { + return target; + } + + options = options || {}; + merge = options.merger || helpers._merger; + + for (i = 0; i < ilen; ++i) { + source = sources[i]; + if (!helpers.isObject(source)) { + continue; + } + + keys = Object.keys(source); + for (k = 0, klen = keys.length; k < klen; ++k) { + merge(keys[k], target, source, options); + } + } + + return target; + }, + + /** + * Recursively deep copies `source` properties into `target` *only* if not defined in target. + * IMPORTANT: `target` is not cloned and will be updated with `source` properties. + * @param {object} target - The target object in which all sources are merged into. + * @param {object|object[]} source - Object(s) to merge into `target`. + * @returns {object} The `target` object. + */ + mergeIf: function(target, source) { + return helpers.merge(target, source, {merger: helpers._mergerIf}); + }, + + /** + * Applies the contents of two or more objects together into the first object. + * @param {object} target - The target object in which all objects are merged into. + * @param {object} arg1 - Object containing additional properties to merge in target. + * @param {object} argN - Additional objects containing properties to merge in target. + * @returns {object} The `target` object. + */ + extend: Object.assign || function(target) { + return helpers.merge(target, [].slice.call(arguments, 1), { + merger: function(key, dst, src) { + dst[key] = src[key]; + } + }); + }, + + /** + * Basic javascript inheritance based on the model created in Backbone.js + */ + inherits: function(extensions) { + var me = this; + var ChartElement = (extensions && extensions.hasOwnProperty('constructor')) ? extensions.constructor : function() { + return me.apply(this, arguments); + }; + + var Surrogate = function() { + this.constructor = ChartElement; + }; + + Surrogate.prototype = me.prototype; + ChartElement.prototype = new Surrogate(); + ChartElement.extend = helpers.inherits; + + if (extensions) { + helpers.extend(ChartElement.prototype, extensions); + } + + ChartElement.__super__ = me.prototype; + return ChartElement; + }, + + _deprecated: function(scope, value, previous, current) { + if (value !== undefined) { + console.warn(scope + ': "' + previous + + '" is deprecated. Please use "' + current + '" instead'); + } + } +}; + +var helpers_core = helpers; + +// DEPRECATIONS + +/** + * Provided for backward compatibility, use Chart.helpers.callback instead. + * @function Chart.helpers.callCallback + * @deprecated since version 2.6.0 + * @todo remove at version 3 + * @private + */ +helpers.callCallback = helpers.callback; + +/** + * Provided for backward compatibility, use Array.prototype.indexOf instead. + * Array.prototype.indexOf compatibility: Chrome, Opera, Safari, FF1.5+, IE9+ + * @function Chart.helpers.indexOf + * @deprecated since version 2.7.0 + * @todo remove at version 3 + * @private + */ +helpers.indexOf = function(array, item, fromIndex) { + return Array.prototype.indexOf.call(array, item, fromIndex); +}; + +/** + * Provided for backward compatibility, use Chart.helpers.valueOrDefault instead. + * @function Chart.helpers.getValueOrDefault + * @deprecated since version 2.7.0 + * @todo remove at version 3 + * @private + */ +helpers.getValueOrDefault = helpers.valueOrDefault; + +/** + * Provided for backward compatibility, use Chart.helpers.valueAtIndexOrDefault instead. + * @function Chart.helpers.getValueAtIndexOrDefault + * @deprecated since version 2.7.0 + * @todo remove at version 3 + * @private + */ +helpers.getValueAtIndexOrDefault = helpers.valueAtIndexOrDefault; + +/** + * Easing functions adapted from Robert Penner's easing equations. + * @namespace Chart.helpers.easingEffects + * @see http://www.robertpenner.com/easing/ + */ +var effects = { + linear: function(t) { + return t; + }, + + easeInQuad: function(t) { + return t * t; + }, + + easeOutQuad: function(t) { + return -t * (t - 2); + }, + + easeInOutQuad: function(t) { + if ((t /= 0.5) < 1) { + return 0.5 * t * t; + } + return -0.5 * ((--t) * (t - 2) - 1); + }, + + easeInCubic: function(t) { + return t * t * t; + }, + + easeOutCubic: function(t) { + return (t = t - 1) * t * t + 1; + }, + + easeInOutCubic: function(t) { + if ((t /= 0.5) < 1) { + return 0.5 * t * t * t; + } + return 0.5 * ((t -= 2) * t * t + 2); + }, + + easeInQuart: function(t) { + return t * t * t * t; + }, + + easeOutQuart: function(t) { + return -((t = t - 1) * t * t * t - 1); + }, + + easeInOutQuart: function(t) { + if ((t /= 0.5) < 1) { + return 0.5 * t * t * t * t; + } + return -0.5 * ((t -= 2) * t * t * t - 2); + }, + + easeInQuint: function(t) { + return t * t * t * t * t; + }, + + easeOutQuint: function(t) { + return (t = t - 1) * t * t * t * t + 1; + }, + + easeInOutQuint: function(t) { + if ((t /= 0.5) < 1) { + return 0.5 * t * t * t * t * t; + } + return 0.5 * ((t -= 2) * t * t * t * t + 2); + }, + + easeInSine: function(t) { + return -Math.cos(t * (Math.PI / 2)) + 1; + }, + + easeOutSine: function(t) { + return Math.sin(t * (Math.PI / 2)); + }, + + easeInOutSine: function(t) { + return -0.5 * (Math.cos(Math.PI * t) - 1); + }, + + easeInExpo: function(t) { + return (t === 0) ? 0 : Math.pow(2, 10 * (t - 1)); + }, + + easeOutExpo: function(t) { + return (t === 1) ? 1 : -Math.pow(2, -10 * t) + 1; + }, + + easeInOutExpo: function(t) { + if (t === 0) { + return 0; + } + if (t === 1) { + return 1; + } + if ((t /= 0.5) < 1) { + return 0.5 * Math.pow(2, 10 * (t - 1)); + } + return 0.5 * (-Math.pow(2, -10 * --t) + 2); + }, + + easeInCirc: function(t) { + if (t >= 1) { + return t; + } + return -(Math.sqrt(1 - t * t) - 1); + }, + + easeOutCirc: function(t) { + return Math.sqrt(1 - (t = t - 1) * t); + }, + + easeInOutCirc: function(t) { + if ((t /= 0.5) < 1) { + return -0.5 * (Math.sqrt(1 - t * t) - 1); + } + return 0.5 * (Math.sqrt(1 - (t -= 2) * t) + 1); + }, + + easeInElastic: function(t) { + var s = 1.70158; + var p = 0; + var a = 1; + if (t === 0) { + return 0; + } + if (t === 1) { + return 1; + } + if (!p) { + p = 0.3; + } + if (a < 1) { + a = 1; + s = p / 4; + } else { + s = p / (2 * Math.PI) * Math.asin(1 / a); + } + return -(a * Math.pow(2, 10 * (t -= 1)) * Math.sin((t - s) * (2 * Math.PI) / p)); + }, + + easeOutElastic: function(t) { + var s = 1.70158; + var p = 0; + var a = 1; + if (t === 0) { + return 0; + } + if (t === 1) { + return 1; + } + if (!p) { + p = 0.3; + } + if (a < 1) { + a = 1; + s = p / 4; + } else { + s = p / (2 * Math.PI) * Math.asin(1 / a); + } + return a * Math.pow(2, -10 * t) * Math.sin((t - s) * (2 * Math.PI) / p) + 1; + }, + + easeInOutElastic: function(t) { + var s = 1.70158; + var p = 0; + var a = 1; + if (t === 0) { + return 0; + } + if ((t /= 0.5) === 2) { + return 1; + } + if (!p) { + p = 0.45; + } + if (a < 1) { + a = 1; + s = p / 4; + } else { + s = p / (2 * Math.PI) * Math.asin(1 / a); + } + if (t < 1) { + return -0.5 * (a * Math.pow(2, 10 * (t -= 1)) * Math.sin((t - s) * (2 * Math.PI) / p)); + } + return a * Math.pow(2, -10 * (t -= 1)) * Math.sin((t - s) * (2 * Math.PI) / p) * 0.5 + 1; + }, + easeInBack: function(t) { + var s = 1.70158; + return t * t * ((s + 1) * t - s); + }, + + easeOutBack: function(t) { + var s = 1.70158; + return (t = t - 1) * t * ((s + 1) * t + s) + 1; + }, + + easeInOutBack: function(t) { + var s = 1.70158; + if ((t /= 0.5) < 1) { + return 0.5 * (t * t * (((s *= (1.525)) + 1) * t - s)); + } + return 0.5 * ((t -= 2) * t * (((s *= (1.525)) + 1) * t + s) + 2); + }, + + easeInBounce: function(t) { + return 1 - effects.easeOutBounce(1 - t); + }, + + easeOutBounce: function(t) { + if (t < (1 / 2.75)) { + return 7.5625 * t * t; + } + if (t < (2 / 2.75)) { + return 7.5625 * (t -= (1.5 / 2.75)) * t + 0.75; + } + if (t < (2.5 / 2.75)) { + return 7.5625 * (t -= (2.25 / 2.75)) * t + 0.9375; + } + return 7.5625 * (t -= (2.625 / 2.75)) * t + 0.984375; + }, + + easeInOutBounce: function(t) { + if (t < 0.5) { + return effects.easeInBounce(t * 2) * 0.5; + } + return effects.easeOutBounce(t * 2 - 1) * 0.5 + 0.5; + } +}; + +var helpers_easing = { + effects: effects +}; + +// DEPRECATIONS + +/** + * Provided for backward compatibility, use Chart.helpers.easing.effects instead. + * @function Chart.helpers.easingEffects + * @deprecated since version 2.7.0 + * @todo remove at version 3 + * @private + */ +helpers_core.easingEffects = effects; + +var PI = Math.PI; +var RAD_PER_DEG = PI / 180; +var DOUBLE_PI = PI * 2; +var HALF_PI = PI / 2; +var QUARTER_PI = PI / 4; +var TWO_THIRDS_PI = PI * 2 / 3; + +/** + * @namespace Chart.helpers.canvas + */ +var exports$1 = { + /** + * Clears the entire canvas associated to the given `chart`. + * @param {Chart} chart - The chart for which to clear the canvas. + */ + clear: function(chart) { + chart.ctx.clearRect(0, 0, chart.width, chart.height); + }, + + /** + * Creates a "path" for a rectangle with rounded corners at position (x, y) with a + * given size (width, height) and the same `radius` for all corners. + * @param {CanvasRenderingContext2D} ctx - The canvas 2D Context. + * @param {number} x - The x axis of the coordinate for the rectangle starting point. + * @param {number} y - The y axis of the coordinate for the rectangle starting point. + * @param {number} width - The rectangle's width. + * @param {number} height - The rectangle's height. + * @param {number} radius - The rounded amount (in pixels) for the four corners. + * @todo handle `radius` as top-left, top-right, bottom-right, bottom-left array/object? + */ + roundedRect: function(ctx, x, y, width, height, radius) { + if (radius) { + var r = Math.min(radius, height / 2, width / 2); + var left = x + r; + var top = y + r; + var right = x + width - r; + var bottom = y + height - r; + + ctx.moveTo(x, top); + if (left < right && top < bottom) { + ctx.arc(left, top, r, -PI, -HALF_PI); + ctx.arc(right, top, r, -HALF_PI, 0); + ctx.arc(right, bottom, r, 0, HALF_PI); + ctx.arc(left, bottom, r, HALF_PI, PI); + } else if (left < right) { + ctx.moveTo(left, y); + ctx.arc(right, top, r, -HALF_PI, HALF_PI); + ctx.arc(left, top, r, HALF_PI, PI + HALF_PI); + } else if (top < bottom) { + ctx.arc(left, top, r, -PI, 0); + ctx.arc(left, bottom, r, 0, PI); + } else { + ctx.arc(left, top, r, -PI, PI); + } + ctx.closePath(); + ctx.moveTo(x, y); + } else { + ctx.rect(x, y, width, height); + } + }, + + drawPoint: function(ctx, style, radius, x, y, rotation) { + var type, xOffset, yOffset, size, cornerRadius; + var rad = (rotation || 0) * RAD_PER_DEG; + + if (style && typeof style === 'object') { + type = style.toString(); + if (type === '[object HTMLImageElement]' || type === '[object HTMLCanvasElement]') { + ctx.save(); + ctx.translate(x, y); + ctx.rotate(rad); + ctx.drawImage(style, -style.width / 2, -style.height / 2, style.width, style.height); + ctx.restore(); + return; + } + } + + if (isNaN(radius) || radius <= 0) { + return; + } + + ctx.beginPath(); + + switch (style) { + // Default includes circle + default: + ctx.arc(x, y, radius, 0, DOUBLE_PI); + ctx.closePath(); + break; + case 'triangle': + ctx.moveTo(x + Math.sin(rad) * radius, y - Math.cos(rad) * radius); + rad += TWO_THIRDS_PI; + ctx.lineTo(x + Math.sin(rad) * radius, y - Math.cos(rad) * radius); + rad += TWO_THIRDS_PI; + ctx.lineTo(x + Math.sin(rad) * radius, y - Math.cos(rad) * radius); + ctx.closePath(); + break; + case 'rectRounded': + // NOTE: the rounded rect implementation changed to use `arc` instead of + // `quadraticCurveTo` since it generates better results when rect is + // almost a circle. 0.516 (instead of 0.5) produces results with visually + // closer proportion to the previous impl and it is inscribed in the + // circle with `radius`. For more details, see the following PRs: + // https://github.com/chartjs/Chart.js/issues/5597 + // https://github.com/chartjs/Chart.js/issues/5858 + cornerRadius = radius * 0.516; + size = radius - cornerRadius; + xOffset = Math.cos(rad + QUARTER_PI) * size; + yOffset = Math.sin(rad + QUARTER_PI) * size; + ctx.arc(x - xOffset, y - yOffset, cornerRadius, rad - PI, rad - HALF_PI); + ctx.arc(x + yOffset, y - xOffset, cornerRadius, rad - HALF_PI, rad); + ctx.arc(x + xOffset, y + yOffset, cornerRadius, rad, rad + HALF_PI); + ctx.arc(x - yOffset, y + xOffset, cornerRadius, rad + HALF_PI, rad + PI); + ctx.closePath(); + break; + case 'rect': + if (!rotation) { + size = Math.SQRT1_2 * radius; + ctx.rect(x - size, y - size, 2 * size, 2 * size); + break; + } + rad += QUARTER_PI; + /* falls through */ + case 'rectRot': + xOffset = Math.cos(rad) * radius; + yOffset = Math.sin(rad) * radius; + ctx.moveTo(x - xOffset, y - yOffset); + ctx.lineTo(x + yOffset, y - xOffset); + ctx.lineTo(x + xOffset, y + yOffset); + ctx.lineTo(x - yOffset, y + xOffset); + ctx.closePath(); + break; + case 'crossRot': + rad += QUARTER_PI; + /* falls through */ + case 'cross': + xOffset = Math.cos(rad) * radius; + yOffset = Math.sin(rad) * radius; + ctx.moveTo(x - xOffset, y - yOffset); + ctx.lineTo(x + xOffset, y + yOffset); + ctx.moveTo(x + yOffset, y - xOffset); + ctx.lineTo(x - yOffset, y + xOffset); + break; + case 'star': + xOffset = Math.cos(rad) * radius; + yOffset = Math.sin(rad) * radius; + ctx.moveTo(x - xOffset, y - yOffset); + ctx.lineTo(x + xOffset, y + yOffset); + ctx.moveTo(x + yOffset, y - xOffset); + ctx.lineTo(x - yOffset, y + xOffset); + rad += QUARTER_PI; + xOffset = Math.cos(rad) * radius; + yOffset = Math.sin(rad) * radius; + ctx.moveTo(x - xOffset, y - yOffset); + ctx.lineTo(x + xOffset, y + yOffset); + ctx.moveTo(x + yOffset, y - xOffset); + ctx.lineTo(x - yOffset, y + xOffset); + break; + case 'line': + xOffset = Math.cos(rad) * radius; + yOffset = Math.sin(rad) * radius; + ctx.moveTo(x - xOffset, y - yOffset); + ctx.lineTo(x + xOffset, y + yOffset); + break; + case 'dash': + ctx.moveTo(x, y); + ctx.lineTo(x + Math.cos(rad) * radius, y + Math.sin(rad) * radius); + break; + } + + ctx.fill(); + ctx.stroke(); + }, + + /** + * Returns true if the point is inside the rectangle + * @param {object} point - The point to test + * @param {object} area - The rectangle + * @returns {boolean} + * @private + */ + _isPointInArea: function(point, area) { + var epsilon = 1e-6; // 1e-6 is margin in pixels for accumulated error. + + return point.x > area.left - epsilon && point.x < area.right + epsilon && + point.y > area.top - epsilon && point.y < area.bottom + epsilon; + }, + + clipArea: function(ctx, area) { + ctx.save(); + ctx.beginPath(); + ctx.rect(area.left, area.top, area.right - area.left, area.bottom - area.top); + ctx.clip(); + }, + + unclipArea: function(ctx) { + ctx.restore(); + }, + + lineTo: function(ctx, previous, target, flip) { + var stepped = target.steppedLine; + if (stepped) { + if (stepped === 'middle') { + var midpoint = (previous.x + target.x) / 2.0; + ctx.lineTo(midpoint, flip ? target.y : previous.y); + ctx.lineTo(midpoint, flip ? previous.y : target.y); + } else if ((stepped === 'after' && !flip) || (stepped !== 'after' && flip)) { + ctx.lineTo(previous.x, target.y); + } else { + ctx.lineTo(target.x, previous.y); + } + ctx.lineTo(target.x, target.y); + return; + } + + if (!target.tension) { + ctx.lineTo(target.x, target.y); + return; + } + + ctx.bezierCurveTo( + flip ? previous.controlPointPreviousX : previous.controlPointNextX, + flip ? previous.controlPointPreviousY : previous.controlPointNextY, + flip ? target.controlPointNextX : target.controlPointPreviousX, + flip ? target.controlPointNextY : target.controlPointPreviousY, + target.x, + target.y); + } +}; + +var helpers_canvas = exports$1; + +// DEPRECATIONS + +/** + * Provided for backward compatibility, use Chart.helpers.canvas.clear instead. + * @namespace Chart.helpers.clear + * @deprecated since version 2.7.0 + * @todo remove at version 3 + * @private + */ +helpers_core.clear = exports$1.clear; + +/** + * Provided for backward compatibility, use Chart.helpers.canvas.roundedRect instead. + * @namespace Chart.helpers.drawRoundedRectangle + * @deprecated since version 2.7.0 + * @todo remove at version 3 + * @private + */ +helpers_core.drawRoundedRectangle = function(ctx) { + ctx.beginPath(); + exports$1.roundedRect.apply(exports$1, arguments); +}; + +var defaults = { + /** + * @private + */ + _set: function(scope, values) { + return helpers_core.merge(this[scope] || (this[scope] = {}), values); + } +}; + +// TODO(v3): remove 'global' from namespace. all default are global and +// there's inconsistency around which options are under 'global' +defaults._set('global', { + defaultColor: 'rgba(0,0,0,0.1)', + defaultFontColor: '#666', + defaultFontFamily: "'Helvetica Neue', 'Helvetica', 'Arial', sans-serif", + defaultFontSize: 12, + defaultFontStyle: 'normal', + defaultLineHeight: 1.2, + showLines: true +}); + +var core_defaults = defaults; + +var valueOrDefault = helpers_core.valueOrDefault; + +/** + * Converts the given font object into a CSS font string. + * @param {object} font - A font object. + * @return {string} The CSS font string. See https://developer.mozilla.org/en-US/docs/Web/CSS/font + * @private + */ +function toFontString(font) { + if (!font || helpers_core.isNullOrUndef(font.size) || helpers_core.isNullOrUndef(font.family)) { + return null; + } + + return (font.style ? font.style + ' ' : '') + + (font.weight ? font.weight + ' ' : '') + + font.size + 'px ' + + font.family; +} + +/** + * @alias Chart.helpers.options + * @namespace + */ +var helpers_options = { + /** + * Converts the given line height `value` in pixels for a specific font `size`. + * @param {number|string} value - The lineHeight to parse (eg. 1.6, '14px', '75%', '1.6em'). + * @param {number} size - The font size (in pixels) used to resolve relative `value`. + * @returns {number} The effective line height in pixels (size * 1.2 if value is invalid). + * @see https://developer.mozilla.org/en-US/docs/Web/CSS/line-height + * @since 2.7.0 + */ + toLineHeight: function(value, size) { + var matches = ('' + value).match(/^(normal|(\d+(?:\.\d+)?)(px|em|%)?)$/); + if (!matches || matches[1] === 'normal') { + return size * 1.2; + } + + value = +matches[2]; + + switch (matches[3]) { + case 'px': + return value; + case '%': + value /= 100; + break; + } + + return size * value; + }, + + /** + * Converts the given value into a padding object with pre-computed width/height. + * @param {number|object} value - If a number, set the value to all TRBL component, + * else, if and object, use defined properties and sets undefined ones to 0. + * @returns {object} The padding values (top, right, bottom, left, width, height) + * @since 2.7.0 + */ + toPadding: function(value) { + var t, r, b, l; + + if (helpers_core.isObject(value)) { + t = +value.top || 0; + r = +value.right || 0; + b = +value.bottom || 0; + l = +value.left || 0; + } else { + t = r = b = l = +value || 0; + } + + return { + top: t, + right: r, + bottom: b, + left: l, + height: t + b, + width: l + r + }; + }, + + /** + * Parses font options and returns the font object. + * @param {object} options - A object that contains font options to be parsed. + * @return {object} The font object. + * @todo Support font.* options and renamed to toFont(). + * @private + */ + _parseFont: function(options) { + var globalDefaults = core_defaults.global; + var size = valueOrDefault(options.fontSize, globalDefaults.defaultFontSize); + var font = { + family: valueOrDefault(options.fontFamily, globalDefaults.defaultFontFamily), + lineHeight: helpers_core.options.toLineHeight(valueOrDefault(options.lineHeight, globalDefaults.defaultLineHeight), size), + size: size, + style: valueOrDefault(options.fontStyle, globalDefaults.defaultFontStyle), + weight: null, + string: '' + }; + + font.string = toFontString(font); + return font; + }, + + /** + * Evaluates the given `inputs` sequentially and returns the first defined value. + * @param {Array} inputs - An array of values, falling back to the last value. + * @param {object} [context] - If defined and the current value is a function, the value + * is called with `context` as first argument and the result becomes the new input. + * @param {number} [index] - If defined and the current value is an array, the value + * at `index` become the new input. + * @param {object} [info] - object to return information about resolution in + * @param {boolean} [info.cacheable] - Will be set to `false` if option is not cacheable. + * @since 2.7.0 + */ + resolve: function(inputs, context, index, info) { + var cacheable = true; + var i, ilen, value; + + for (i = 0, ilen = inputs.length; i < ilen; ++i) { + value = inputs[i]; + if (value === undefined) { + continue; + } + if (context !== undefined && typeof value === 'function') { + value = value(context); + cacheable = false; + } + if (index !== undefined && helpers_core.isArray(value)) { + value = value[index]; + cacheable = false; + } + if (value !== undefined) { + if (info && !cacheable) { + info.cacheable = false; + } + return value; + } + } + } +}; + +/** + * @alias Chart.helpers.math + * @namespace + */ +var exports$2 = { + /** + * Returns an array of factors sorted from 1 to sqrt(value) + * @private + */ + _factorize: function(value) { + var result = []; + var sqrt = Math.sqrt(value); + var i; + + for (i = 1; i < sqrt; i++) { + if (value % i === 0) { + result.push(i); + result.push(value / i); + } + } + if (sqrt === (sqrt | 0)) { // if value is a square number + result.push(sqrt); + } + + result.sort(function(a, b) { + return a - b; + }).pop(); + return result; + }, + + log10: Math.log10 || function(x) { + var exponent = Math.log(x) * Math.LOG10E; // Math.LOG10E = 1 / Math.LN10. + // Check for whole powers of 10, + // which due to floating point rounding error should be corrected. + var powerOf10 = Math.round(exponent); + var isPowerOf10 = x === Math.pow(10, powerOf10); + + return isPowerOf10 ? powerOf10 : exponent; + } +}; + +var helpers_math = exports$2; + +// DEPRECATIONS + +/** + * Provided for backward compatibility, use Chart.helpers.math.log10 instead. + * @namespace Chart.helpers.log10 + * @deprecated since version 2.9.0 + * @todo remove at version 3 + * @private + */ +helpers_core.log10 = exports$2.log10; + +var getRtlAdapter = function(rectX, width) { + return { + x: function(x) { + return rectX + rectX + width - x; + }, + setWidth: function(w) { + width = w; + }, + textAlign: function(align) { + if (align === 'center') { + return align; + } + return align === 'right' ? 'left' : 'right'; + }, + xPlus: function(x, value) { + return x - value; + }, + leftForLtr: function(x, itemWidth) { + return x - itemWidth; + }, + }; +}; + +var getLtrAdapter = function() { + return { + x: function(x) { + return x; + }, + setWidth: function(w) { // eslint-disable-line no-unused-vars + }, + textAlign: function(align) { + return align; + }, + xPlus: function(x, value) { + return x + value; + }, + leftForLtr: function(x, _itemWidth) { // eslint-disable-line no-unused-vars + return x; + }, + }; +}; + +var getAdapter = function(rtl, rectX, width) { + return rtl ? getRtlAdapter(rectX, width) : getLtrAdapter(); +}; + +var overrideTextDirection = function(ctx, direction) { + var style, original; + if (direction === 'ltr' || direction === 'rtl') { + style = ctx.canvas.style; + original = [ + style.getPropertyValue('direction'), + style.getPropertyPriority('direction'), + ]; + + style.setProperty('direction', direction, 'important'); + ctx.prevTextDirection = original; + } +}; + +var restoreTextDirection = function(ctx) { + var original = ctx.prevTextDirection; + if (original !== undefined) { + delete ctx.prevTextDirection; + ctx.canvas.style.setProperty('direction', original[0], original[1]); + } +}; + +var helpers_rtl = { + getRtlAdapter: getAdapter, + overrideTextDirection: overrideTextDirection, + restoreTextDirection: restoreTextDirection, +}; + +var helpers$1 = helpers_core; +var easing = helpers_easing; +var canvas = helpers_canvas; +var options = helpers_options; +var math = helpers_math; +var rtl = helpers_rtl; +helpers$1.easing = easing; +helpers$1.canvas = canvas; +helpers$1.options = options; +helpers$1.math = math; +helpers$1.rtl = rtl; + +function interpolate(start, view, model, ease) { + var keys = Object.keys(model); + var i, ilen, key, actual, origin, target, type, c0, c1; + + for (i = 0, ilen = keys.length; i < ilen; ++i) { + key = keys[i]; + + target = model[key]; + + // if a value is added to the model after pivot() has been called, the view + // doesn't contain it, so let's initialize the view to the target value. + if (!view.hasOwnProperty(key)) { + view[key] = target; + } + + actual = view[key]; + + if (actual === target || key[0] === '_') { + continue; + } + + if (!start.hasOwnProperty(key)) { + start[key] = actual; + } + + origin = start[key]; + + type = typeof target; + + if (type === typeof origin) { + if (type === 'string') { + c0 = chartjsColor(origin); + if (c0.valid) { + c1 = chartjsColor(target); + if (c1.valid) { + view[key] = c1.mix(c0, ease).rgbString(); + continue; + } + } + } else if (helpers$1.isFinite(origin) && helpers$1.isFinite(target)) { + view[key] = origin + (target - origin) * ease; + continue; + } + } + + view[key] = target; + } +} + +var Element = function(configuration) { + helpers$1.extend(this, configuration); + this.initialize.apply(this, arguments); +}; + +helpers$1.extend(Element.prototype, { + _type: undefined, + + initialize: function() { + this.hidden = false; + }, + + pivot: function() { + var me = this; + if (!me._view) { + me._view = helpers$1.extend({}, me._model); + } + me._start = {}; + return me; + }, + + transition: function(ease) { + var me = this; + var model = me._model; + var start = me._start; + var view = me._view; + + // No animation -> No Transition + if (!model || ease === 1) { + me._view = helpers$1.extend({}, model); + me._start = null; + return me; + } + + if (!view) { + view = me._view = {}; + } + + if (!start) { + start = me._start = {}; + } + + interpolate(start, view, model, ease); + + return me; + }, + + tooltipPosition: function() { + return { + x: this._model.x, + y: this._model.y + }; + }, + + hasValue: function() { + return helpers$1.isNumber(this._model.x) && helpers$1.isNumber(this._model.y); + } +}); + +Element.extend = helpers$1.inherits; + +var core_element = Element; + +var exports$3 = core_element.extend({ + chart: null, // the animation associated chart instance + currentStep: 0, // the current animation step + numSteps: 60, // default number of steps + easing: '', // the easing to use for this animation + render: null, // render function used by the animation service + + onAnimationProgress: null, // user specified callback to fire on each step of the animation + onAnimationComplete: null, // user specified callback to fire when the animation finishes +}); + +var core_animation = exports$3; + +// DEPRECATIONS + +/** + * Provided for backward compatibility, use Chart.Animation instead + * @prop Chart.Animation#animationObject + * @deprecated since version 2.6.0 + * @todo remove at version 3 + */ +Object.defineProperty(exports$3.prototype, 'animationObject', { + get: function() { + return this; + } +}); + +/** + * Provided for backward compatibility, use Chart.Animation#chart instead + * @prop Chart.Animation#chartInstance + * @deprecated since version 2.6.0 + * @todo remove at version 3 + */ +Object.defineProperty(exports$3.prototype, 'chartInstance', { + get: function() { + return this.chart; + }, + set: function(value) { + this.chart = value; + } +}); + +core_defaults._set('global', { + animation: { + duration: 1000, + easing: 'easeOutQuart', + onProgress: helpers$1.noop, + onComplete: helpers$1.noop + } +}); + +var core_animations = { + animations: [], + request: null, + + /** + * @param {Chart} chart - The chart to animate. + * @param {Chart.Animation} animation - The animation that we will animate. + * @param {number} duration - The animation duration in ms. + * @param {boolean} lazy - if true, the chart is not marked as animating to enable more responsive interactions + */ + addAnimation: function(chart, animation, duration, lazy) { + var animations = this.animations; + var i, ilen; + + animation.chart = chart; + animation.startTime = Date.now(); + animation.duration = duration; + + if (!lazy) { + chart.animating = true; + } + + for (i = 0, ilen = animations.length; i < ilen; ++i) { + if (animations[i].chart === chart) { + animations[i] = animation; + return; + } + } + + animations.push(animation); + + // If there are no animations queued, manually kickstart a digest, for lack of a better word + if (animations.length === 1) { + this.requestAnimationFrame(); + } + }, + + cancelAnimation: function(chart) { + var index = helpers$1.findIndex(this.animations, function(animation) { + return animation.chart === chart; + }); + + if (index !== -1) { + this.animations.splice(index, 1); + chart.animating = false; + } + }, + + requestAnimationFrame: function() { + var me = this; + if (me.request === null) { + // Skip animation frame requests until the active one is executed. + // This can happen when processing mouse events, e.g. 'mousemove' + // and 'mouseout' events will trigger multiple renders. + me.request = helpers$1.requestAnimFrame.call(window, function() { + me.request = null; + me.startDigest(); + }); + } + }, + + /** + * @private + */ + startDigest: function() { + var me = this; + + me.advance(); + + // Do we have more stuff to animate? + if (me.animations.length > 0) { + me.requestAnimationFrame(); + } + }, + + /** + * @private + */ + advance: function() { + var animations = this.animations; + var animation, chart, numSteps, nextStep; + var i = 0; + + // 1 animation per chart, so we are looping charts here + while (i < animations.length) { + animation = animations[i]; + chart = animation.chart; + numSteps = animation.numSteps; + + // Make sure that currentStep starts at 1 + // https://github.com/chartjs/Chart.js/issues/6104 + nextStep = Math.floor((Date.now() - animation.startTime) / animation.duration * numSteps) + 1; + animation.currentStep = Math.min(nextStep, numSteps); + + helpers$1.callback(animation.render, [chart, animation], chart); + helpers$1.callback(animation.onAnimationProgress, [animation], chart); + + if (animation.currentStep >= numSteps) { + helpers$1.callback(animation.onAnimationComplete, [animation], chart); + chart.animating = false; + animations.splice(i, 1); + } else { + ++i; + } + } + } +}; + +var resolve = helpers$1.options.resolve; + +var arrayEvents = ['push', 'pop', 'shift', 'splice', 'unshift']; + +/** + * Hooks the array methods that add or remove values ('push', pop', 'shift', 'splice', + * 'unshift') and notify the listener AFTER the array has been altered. Listeners are + * called on the 'onData*' callbacks (e.g. onDataPush, etc.) with same arguments. + */ +function listenArrayEvents(array, listener) { + if (array._chartjs) { + array._chartjs.listeners.push(listener); + return; + } + + Object.defineProperty(array, '_chartjs', { + configurable: true, + enumerable: false, + value: { + listeners: [listener] + } + }); + + arrayEvents.forEach(function(key) { + var method = 'onData' + key.charAt(0).toUpperCase() + key.slice(1); + var base = array[key]; + + Object.defineProperty(array, key, { + configurable: true, + enumerable: false, + value: function() { + var args = Array.prototype.slice.call(arguments); + var res = base.apply(this, args); + + helpers$1.each(array._chartjs.listeners, function(object) { + if (typeof object[method] === 'function') { + object[method].apply(object, args); + } + }); + + return res; + } + }); + }); +} + +/** + * Removes the given array event listener and cleanup extra attached properties (such as + * the _chartjs stub and overridden methods) if array doesn't have any more listeners. + */ +function unlistenArrayEvents(array, listener) { + var stub = array._chartjs; + if (!stub) { + return; + } + + var listeners = stub.listeners; + var index = listeners.indexOf(listener); + if (index !== -1) { + listeners.splice(index, 1); + } + + if (listeners.length > 0) { + return; + } + + arrayEvents.forEach(function(key) { + delete array[key]; + }); + + delete array._chartjs; +} + +// Base class for all dataset controllers (line, bar, etc) +var DatasetController = function(chart, datasetIndex) { + this.initialize(chart, datasetIndex); +}; + +helpers$1.extend(DatasetController.prototype, { + + /** + * Element type used to generate a meta dataset (e.g. Chart.element.Line). + * @type {Chart.core.element} + */ + datasetElementType: null, + + /** + * Element type used to generate a meta data (e.g. Chart.element.Point). + * @type {Chart.core.element} + */ + dataElementType: null, + + /** + * Dataset element option keys to be resolved in _resolveDatasetElementOptions. + * A derived controller may override this to resolve controller-specific options. + * The keys defined here are for backward compatibility for legend styles. + * @private + */ + _datasetElementOptions: [ + 'backgroundColor', + 'borderCapStyle', + 'borderColor', + 'borderDash', + 'borderDashOffset', + 'borderJoinStyle', + 'borderWidth' + ], + + /** + * Data element option keys to be resolved in _resolveDataElementOptions. + * A derived controller may override this to resolve controller-specific options. + * The keys defined here are for backward compatibility for legend styles. + * @private + */ + _dataElementOptions: [ + 'backgroundColor', + 'borderColor', + 'borderWidth', + 'pointStyle' + ], + + initialize: function(chart, datasetIndex) { + var me = this; + me.chart = chart; + me.index = datasetIndex; + me.linkScales(); + me.addElements(); + me._type = me.getMeta().type; + }, + + updateIndex: function(datasetIndex) { + this.index = datasetIndex; + }, + + linkScales: function() { + var me = this; + var meta = me.getMeta(); + var chart = me.chart; + var scales = chart.scales; + var dataset = me.getDataset(); + var scalesOpts = chart.options.scales; + + if (meta.xAxisID === null || !(meta.xAxisID in scales) || dataset.xAxisID) { + meta.xAxisID = dataset.xAxisID || scalesOpts.xAxes[0].id; + } + if (meta.yAxisID === null || !(meta.yAxisID in scales) || dataset.yAxisID) { + meta.yAxisID = dataset.yAxisID || scalesOpts.yAxes[0].id; + } + }, + + getDataset: function() { + return this.chart.data.datasets[this.index]; + }, + + getMeta: function() { + return this.chart.getDatasetMeta(this.index); + }, + + getScaleForId: function(scaleID) { + return this.chart.scales[scaleID]; + }, + + /** + * @private + */ + _getValueScaleId: function() { + return this.getMeta().yAxisID; + }, + + /** + * @private + */ + _getIndexScaleId: function() { + return this.getMeta().xAxisID; + }, + + /** + * @private + */ + _getValueScale: function() { + return this.getScaleForId(this._getValueScaleId()); + }, + + /** + * @private + */ + _getIndexScale: function() { + return this.getScaleForId(this._getIndexScaleId()); + }, + + reset: function() { + this._update(true); + }, + + /** + * @private + */ + destroy: function() { + if (this._data) { + unlistenArrayEvents(this._data, this); + } + }, + + createMetaDataset: function() { + var me = this; + var type = me.datasetElementType; + return type && new type({ + _chart: me.chart, + _datasetIndex: me.index + }); + }, + + createMetaData: function(index) { + var me = this; + var type = me.dataElementType; + return type && new type({ + _chart: me.chart, + _datasetIndex: me.index, + _index: index + }); + }, + + addElements: function() { + var me = this; + var meta = me.getMeta(); + var data = me.getDataset().data || []; + var metaData = meta.data; + var i, ilen; + + for (i = 0, ilen = data.length; i < ilen; ++i) { + metaData[i] = metaData[i] || me.createMetaData(i); + } + + meta.dataset = meta.dataset || me.createMetaDataset(); + }, + + addElementAndReset: function(index) { + var element = this.createMetaData(index); + this.getMeta().data.splice(index, 0, element); + this.updateElement(element, index, true); + }, + + buildOrUpdateElements: function() { + var me = this; + var dataset = me.getDataset(); + var data = dataset.data || (dataset.data = []); + + // In order to correctly handle data addition/deletion animation (an thus simulate + // real-time charts), we need to monitor these data modifications and synchronize + // the internal meta data accordingly. + if (me._data !== data) { + if (me._data) { + // This case happens when the user replaced the data array instance. + unlistenArrayEvents(me._data, me); + } + + if (data && Object.isExtensible(data)) { + listenArrayEvents(data, me); + } + me._data = data; + } + + // Re-sync meta data in case the user replaced the data array or if we missed + // any updates and so make sure that we handle number of datapoints changing. + me.resyncElements(); + }, + + /** + * Returns the merged user-supplied and default dataset-level options + * @private + */ + _configure: function() { + var me = this; + me._config = helpers$1.merge(Object.create(null), [ + me.chart.options.datasets[me._type], + me.getDataset(), + ], { + merger: function(key, target, source) { + if (key !== '_meta' && key !== 'data') { + helpers$1._merger(key, target, source); + } + } + }); + }, + + _update: function(reset) { + var me = this; + me._configure(); + me._cachedDataOpts = null; + me.update(reset); + }, + + update: helpers$1.noop, + + transition: function(easingValue) { + var meta = this.getMeta(); + var elements = meta.data || []; + var ilen = elements.length; + var i = 0; + + for (; i < ilen; ++i) { + elements[i].transition(easingValue); + } + + if (meta.dataset) { + meta.dataset.transition(easingValue); + } + }, + + draw: function() { + var meta = this.getMeta(); + var elements = meta.data || []; + var ilen = elements.length; + var i = 0; + + if (meta.dataset) { + meta.dataset.draw(); + } + + for (; i < ilen; ++i) { + elements[i].draw(); + } + }, + + /** + * Returns a set of predefined style properties that should be used to represent the dataset + * or the data if the index is specified + * @param {number} index - data index + * @return {IStyleInterface} style object + */ + getStyle: function(index) { + var me = this; + var meta = me.getMeta(); + var dataset = meta.dataset; + var style; + + me._configure(); + if (dataset && index === undefined) { + style = me._resolveDatasetElementOptions(dataset || {}); + } else { + index = index || 0; + style = me._resolveDataElementOptions(meta.data[index] || {}, index); + } + + if (style.fill === false || style.fill === null) { + style.backgroundColor = style.borderColor; + } + + return style; + }, + + /** + * @private + */ + _resolveDatasetElementOptions: function(element, hover) { + var me = this; + var chart = me.chart; + var datasetOpts = me._config; + var custom = element.custom || {}; + var options = chart.options.elements[me.datasetElementType.prototype._type] || {}; + var elementOptions = me._datasetElementOptions; + var values = {}; + var i, ilen, key, readKey; + + // Scriptable options + var context = { + chart: chart, + dataset: me.getDataset(), + datasetIndex: me.index, + hover: hover + }; + + for (i = 0, ilen = elementOptions.length; i < ilen; ++i) { + key = elementOptions[i]; + readKey = hover ? 'hover' + key.charAt(0).toUpperCase() + key.slice(1) : key; + values[key] = resolve([ + custom[readKey], + datasetOpts[readKey], + options[readKey] + ], context); + } + + return values; + }, + + /** + * @private + */ + _resolveDataElementOptions: function(element, index) { + var me = this; + var custom = element && element.custom; + var cached = me._cachedDataOpts; + if (cached && !custom) { + return cached; + } + var chart = me.chart; + var datasetOpts = me._config; + var options = chart.options.elements[me.dataElementType.prototype._type] || {}; + var elementOptions = me._dataElementOptions; + var values = {}; + + // Scriptable options + var context = { + chart: chart, + dataIndex: index, + dataset: me.getDataset(), + datasetIndex: me.index + }; + + // `resolve` sets cacheable to `false` if any option is indexed or scripted + var info = {cacheable: !custom}; + + var keys, i, ilen, key; + + custom = custom || {}; + + if (helpers$1.isArray(elementOptions)) { + for (i = 0, ilen = elementOptions.length; i < ilen; ++i) { + key = elementOptions[i]; + values[key] = resolve([ + custom[key], + datasetOpts[key], + options[key] + ], context, index, info); + } + } else { + keys = Object.keys(elementOptions); + for (i = 0, ilen = keys.length; i < ilen; ++i) { + key = keys[i]; + values[key] = resolve([ + custom[key], + datasetOpts[elementOptions[key]], + datasetOpts[key], + options[key] + ], context, index, info); + } + } + + if (info.cacheable) { + me._cachedDataOpts = Object.freeze(values); + } + + return values; + }, + + removeHoverStyle: function(element) { + helpers$1.merge(element._model, element.$previousStyle || {}); + delete element.$previousStyle; + }, + + setHoverStyle: function(element) { + var dataset = this.chart.data.datasets[element._datasetIndex]; + var index = element._index; + var custom = element.custom || {}; + var model = element._model; + var getHoverColor = helpers$1.getHoverColor; + + element.$previousStyle = { + backgroundColor: model.backgroundColor, + borderColor: model.borderColor, + borderWidth: model.borderWidth + }; + + model.backgroundColor = resolve([custom.hoverBackgroundColor, dataset.hoverBackgroundColor, getHoverColor(model.backgroundColor)], undefined, index); + model.borderColor = resolve([custom.hoverBorderColor, dataset.hoverBorderColor, getHoverColor(model.borderColor)], undefined, index); + model.borderWidth = resolve([custom.hoverBorderWidth, dataset.hoverBorderWidth, model.borderWidth], undefined, index); + }, + + /** + * @private + */ + _removeDatasetHoverStyle: function() { + var element = this.getMeta().dataset; + + if (element) { + this.removeHoverStyle(element); + } + }, + + /** + * @private + */ + _setDatasetHoverStyle: function() { + var element = this.getMeta().dataset; + var prev = {}; + var i, ilen, key, keys, hoverOptions, model; + + if (!element) { + return; + } + + model = element._model; + hoverOptions = this._resolveDatasetElementOptions(element, true); + + keys = Object.keys(hoverOptions); + for (i = 0, ilen = keys.length; i < ilen; ++i) { + key = keys[i]; + prev[key] = model[key]; + model[key] = hoverOptions[key]; + } + + element.$previousStyle = prev; + }, + + /** + * @private + */ + resyncElements: function() { + var me = this; + var meta = me.getMeta(); + var data = me.getDataset().data; + var numMeta = meta.data.length; + var numData = data.length; + + if (numData < numMeta) { + meta.data.splice(numData, numMeta - numData); + } else if (numData > numMeta) { + me.insertElements(numMeta, numData - numMeta); + } + }, + + /** + * @private + */ + insertElements: function(start, count) { + for (var i = 0; i < count; ++i) { + this.addElementAndReset(start + i); + } + }, + + /** + * @private + */ + onDataPush: function() { + var count = arguments.length; + this.insertElements(this.getDataset().data.length - count, count); + }, + + /** + * @private + */ + onDataPop: function() { + this.getMeta().data.pop(); + }, + + /** + * @private + */ + onDataShift: function() { + this.getMeta().data.shift(); + }, + + /** + * @private + */ + onDataSplice: function(start, count) { + this.getMeta().data.splice(start, count); + this.insertElements(start, arguments.length - 2); + }, + + /** + * @private + */ + onDataUnshift: function() { + this.insertElements(0, arguments.length); + } +}); + +DatasetController.extend = helpers$1.inherits; + +var core_datasetController = DatasetController; + +var TAU = Math.PI * 2; + +core_defaults._set('global', { + elements: { + arc: { + backgroundColor: core_defaults.global.defaultColor, + borderColor: '#fff', + borderWidth: 2, + borderAlign: 'center' + } + } +}); + +function clipArc(ctx, arc) { + var startAngle = arc.startAngle; + var endAngle = arc.endAngle; + var pixelMargin = arc.pixelMargin; + var angleMargin = pixelMargin / arc.outerRadius; + var x = arc.x; + var y = arc.y; + + // Draw an inner border by cliping the arc and drawing a double-width border + // Enlarge the clipping arc by 0.33 pixels to eliminate glitches between borders + ctx.beginPath(); + ctx.arc(x, y, arc.outerRadius, startAngle - angleMargin, endAngle + angleMargin); + if (arc.innerRadius > pixelMargin) { + angleMargin = pixelMargin / arc.innerRadius; + ctx.arc(x, y, arc.innerRadius - pixelMargin, endAngle + angleMargin, startAngle - angleMargin, true); + } else { + ctx.arc(x, y, pixelMargin, endAngle + Math.PI / 2, startAngle - Math.PI / 2); + } + ctx.closePath(); + ctx.clip(); +} + +function drawFullCircleBorders(ctx, vm, arc, inner) { + var endAngle = arc.endAngle; + var i; + + if (inner) { + arc.endAngle = arc.startAngle + TAU; + clipArc(ctx, arc); + arc.endAngle = endAngle; + if (arc.endAngle === arc.startAngle && arc.fullCircles) { + arc.endAngle += TAU; + arc.fullCircles--; + } + } + + ctx.beginPath(); + ctx.arc(arc.x, arc.y, arc.innerRadius, arc.startAngle + TAU, arc.startAngle, true); + for (i = 0; i < arc.fullCircles; ++i) { + ctx.stroke(); + } + + ctx.beginPath(); + ctx.arc(arc.x, arc.y, vm.outerRadius, arc.startAngle, arc.startAngle + TAU); + for (i = 0; i < arc.fullCircles; ++i) { + ctx.stroke(); + } +} + +function drawBorder(ctx, vm, arc) { + var inner = vm.borderAlign === 'inner'; + + if (inner) { + ctx.lineWidth = vm.borderWidth * 2; + ctx.lineJoin = 'round'; + } else { + ctx.lineWidth = vm.borderWidth; + ctx.lineJoin = 'bevel'; + } + + if (arc.fullCircles) { + drawFullCircleBorders(ctx, vm, arc, inner); + } + + if (inner) { + clipArc(ctx, arc); + } + + ctx.beginPath(); + ctx.arc(arc.x, arc.y, vm.outerRadius, arc.startAngle, arc.endAngle); + ctx.arc(arc.x, arc.y, arc.innerRadius, arc.endAngle, arc.startAngle, true); + ctx.closePath(); + ctx.stroke(); +} + +var element_arc = core_element.extend({ + _type: 'arc', + + inLabelRange: function(mouseX) { + var vm = this._view; + + if (vm) { + return (Math.pow(mouseX - vm.x, 2) < Math.pow(vm.radius + vm.hoverRadius, 2)); + } + return false; + }, + + inRange: function(chartX, chartY) { + var vm = this._view; + + if (vm) { + var pointRelativePosition = helpers$1.getAngleFromPoint(vm, {x: chartX, y: chartY}); + var angle = pointRelativePosition.angle; + var distance = pointRelativePosition.distance; + + // Sanitise angle range + var startAngle = vm.startAngle; + var endAngle = vm.endAngle; + while (endAngle < startAngle) { + endAngle += TAU; + } + while (angle > endAngle) { + angle -= TAU; + } + while (angle < startAngle) { + angle += TAU; + } + + // Check if within the range of the open/close angle + var betweenAngles = (angle >= startAngle && angle <= endAngle); + var withinRadius = (distance >= vm.innerRadius && distance <= vm.outerRadius); + + return (betweenAngles && withinRadius); + } + return false; + }, + + getCenterPoint: function() { + var vm = this._view; + var halfAngle = (vm.startAngle + vm.endAngle) / 2; + var halfRadius = (vm.innerRadius + vm.outerRadius) / 2; + return { + x: vm.x + Math.cos(halfAngle) * halfRadius, + y: vm.y + Math.sin(halfAngle) * halfRadius + }; + }, + + getArea: function() { + var vm = this._view; + return Math.PI * ((vm.endAngle - vm.startAngle) / (2 * Math.PI)) * (Math.pow(vm.outerRadius, 2) - Math.pow(vm.innerRadius, 2)); + }, + + tooltipPosition: function() { + var vm = this._view; + var centreAngle = vm.startAngle + ((vm.endAngle - vm.startAngle) / 2); + var rangeFromCentre = (vm.outerRadius - vm.innerRadius) / 2 + vm.innerRadius; + + return { + x: vm.x + (Math.cos(centreAngle) * rangeFromCentre), + y: vm.y + (Math.sin(centreAngle) * rangeFromCentre) + }; + }, + + draw: function() { + var ctx = this._chart.ctx; + var vm = this._view; + var pixelMargin = (vm.borderAlign === 'inner') ? 0.33 : 0; + var arc = { + x: vm.x, + y: vm.y, + innerRadius: vm.innerRadius, + outerRadius: Math.max(vm.outerRadius - pixelMargin, 0), + pixelMargin: pixelMargin, + startAngle: vm.startAngle, + endAngle: vm.endAngle, + fullCircles: Math.floor(vm.circumference / TAU) + }; + var i; + + ctx.save(); + + ctx.fillStyle = vm.backgroundColor; + ctx.strokeStyle = vm.borderColor; + + if (arc.fullCircles) { + arc.endAngle = arc.startAngle + TAU; + ctx.beginPath(); + ctx.arc(arc.x, arc.y, arc.outerRadius, arc.startAngle, arc.endAngle); + ctx.arc(arc.x, arc.y, arc.innerRadius, arc.endAngle, arc.startAngle, true); + ctx.closePath(); + for (i = 0; i < arc.fullCircles; ++i) { + ctx.fill(); + } + arc.endAngle = arc.startAngle + vm.circumference % TAU; + } + + ctx.beginPath(); + ctx.arc(arc.x, arc.y, arc.outerRadius, arc.startAngle, arc.endAngle); + ctx.arc(arc.x, arc.y, arc.innerRadius, arc.endAngle, arc.startAngle, true); + ctx.closePath(); + ctx.fill(); + + if (vm.borderWidth) { + drawBorder(ctx, vm, arc); + } + + ctx.restore(); + } +}); + +var valueOrDefault$1 = helpers$1.valueOrDefault; + +var defaultColor = core_defaults.global.defaultColor; + +core_defaults._set('global', { + elements: { + line: { + tension: 0.4, + backgroundColor: defaultColor, + borderWidth: 3, + borderColor: defaultColor, + borderCapStyle: 'butt', + borderDash: [], + borderDashOffset: 0.0, + borderJoinStyle: 'miter', + capBezierPoints: true, + fill: true, // do we fill in the area between the line and its base axis + } + } +}); + +var element_line = core_element.extend({ + _type: 'line', + + draw: function() { + var me = this; + var vm = me._view; + var ctx = me._chart.ctx; + var spanGaps = vm.spanGaps; + var points = me._children.slice(); // clone array + var globalDefaults = core_defaults.global; + var globalOptionLineElements = globalDefaults.elements.line; + var lastDrawnIndex = -1; + var closePath = me._loop; + var index, previous, currentVM; + + if (!points.length) { + return; + } + + if (me._loop) { + for (index = 0; index < points.length; ++index) { + previous = helpers$1.previousItem(points, index); + // If the line has an open path, shift the point array + if (!points[index]._view.skip && previous._view.skip) { + points = points.slice(index).concat(points.slice(0, index)); + closePath = spanGaps; + break; + } + } + // If the line has a close path, add the first point again + if (closePath) { + points.push(points[0]); + } + } + + ctx.save(); + + // Stroke Line Options + ctx.lineCap = vm.borderCapStyle || globalOptionLineElements.borderCapStyle; + + // IE 9 and 10 do not support line dash + if (ctx.setLineDash) { + ctx.setLineDash(vm.borderDash || globalOptionLineElements.borderDash); + } + + ctx.lineDashOffset = valueOrDefault$1(vm.borderDashOffset, globalOptionLineElements.borderDashOffset); + ctx.lineJoin = vm.borderJoinStyle || globalOptionLineElements.borderJoinStyle; + ctx.lineWidth = valueOrDefault$1(vm.borderWidth, globalOptionLineElements.borderWidth); + ctx.strokeStyle = vm.borderColor || globalDefaults.defaultColor; + + // Stroke Line + ctx.beginPath(); + + // First point moves to it's starting position no matter what + currentVM = points[0]._view; + if (!currentVM.skip) { + ctx.moveTo(currentVM.x, currentVM.y); + lastDrawnIndex = 0; + } + + for (index = 1; index < points.length; ++index) { + currentVM = points[index]._view; + previous = lastDrawnIndex === -1 ? helpers$1.previousItem(points, index) : points[lastDrawnIndex]; + + if (!currentVM.skip) { + if ((lastDrawnIndex !== (index - 1) && !spanGaps) || lastDrawnIndex === -1) { + // There was a gap and this is the first point after the gap + ctx.moveTo(currentVM.x, currentVM.y); + } else { + // Line to next point + helpers$1.canvas.lineTo(ctx, previous._view, currentVM); + } + lastDrawnIndex = index; + } + } + + if (closePath) { + ctx.closePath(); + } + + ctx.stroke(); + ctx.restore(); + } +}); + +var valueOrDefault$2 = helpers$1.valueOrDefault; + +var defaultColor$1 = core_defaults.global.defaultColor; + +core_defaults._set('global', { + elements: { + point: { + radius: 3, + pointStyle: 'circle', + backgroundColor: defaultColor$1, + borderColor: defaultColor$1, + borderWidth: 1, + // Hover + hitRadius: 1, + hoverRadius: 4, + hoverBorderWidth: 1 + } + } +}); + +function xRange(mouseX) { + var vm = this._view; + return vm ? (Math.abs(mouseX - vm.x) < vm.radius + vm.hitRadius) : false; +} + +function yRange(mouseY) { + var vm = this._view; + return vm ? (Math.abs(mouseY - vm.y) < vm.radius + vm.hitRadius) : false; +} + +var element_point = core_element.extend({ + _type: 'point', + + inRange: function(mouseX, mouseY) { + var vm = this._view; + return vm ? ((Math.pow(mouseX - vm.x, 2) + Math.pow(mouseY - vm.y, 2)) < Math.pow(vm.hitRadius + vm.radius, 2)) : false; + }, + + inLabelRange: xRange, + inXRange: xRange, + inYRange: yRange, + + getCenterPoint: function() { + var vm = this._view; + return { + x: vm.x, + y: vm.y + }; + }, + + getArea: function() { + return Math.PI * Math.pow(this._view.radius, 2); + }, + + tooltipPosition: function() { + var vm = this._view; + return { + x: vm.x, + y: vm.y, + padding: vm.radius + vm.borderWidth + }; + }, + + draw: function(chartArea) { + var vm = this._view; + var ctx = this._chart.ctx; + var pointStyle = vm.pointStyle; + var rotation = vm.rotation; + var radius = vm.radius; + var x = vm.x; + var y = vm.y; + var globalDefaults = core_defaults.global; + var defaultColor = globalDefaults.defaultColor; // eslint-disable-line no-shadow + + if (vm.skip) { + return; + } + + // Clipping for Points. + if (chartArea === undefined || helpers$1.canvas._isPointInArea(vm, chartArea)) { + ctx.strokeStyle = vm.borderColor || defaultColor; + ctx.lineWidth = valueOrDefault$2(vm.borderWidth, globalDefaults.elements.point.borderWidth); + ctx.fillStyle = vm.backgroundColor || defaultColor; + helpers$1.canvas.drawPoint(ctx, pointStyle, radius, x, y, rotation); + } + } +}); + +var defaultColor$2 = core_defaults.global.defaultColor; + +core_defaults._set('global', { + elements: { + rectangle: { + backgroundColor: defaultColor$2, + borderColor: defaultColor$2, + borderSkipped: 'bottom', + borderWidth: 0 + } + } +}); + +function isVertical(vm) { + return vm && vm.width !== undefined; +} + +/** + * Helper function to get the bounds of the bar regardless of the orientation + * @param bar {Chart.Element.Rectangle} the bar + * @return {Bounds} bounds of the bar + * @private + */ +function getBarBounds(vm) { + var x1, x2, y1, y2, half; + + if (isVertical(vm)) { + half = vm.width / 2; + x1 = vm.x - half; + x2 = vm.x + half; + y1 = Math.min(vm.y, vm.base); + y2 = Math.max(vm.y, vm.base); + } else { + half = vm.height / 2; + x1 = Math.min(vm.x, vm.base); + x2 = Math.max(vm.x, vm.base); + y1 = vm.y - half; + y2 = vm.y + half; + } + + return { + left: x1, + top: y1, + right: x2, + bottom: y2 + }; +} + +function swap(orig, v1, v2) { + return orig === v1 ? v2 : orig === v2 ? v1 : orig; +} + +function parseBorderSkipped(vm) { + var edge = vm.borderSkipped; + var res = {}; + + if (!edge) { + return res; + } + + if (vm.horizontal) { + if (vm.base > vm.x) { + edge = swap(edge, 'left', 'right'); + } + } else if (vm.base < vm.y) { + edge = swap(edge, 'bottom', 'top'); + } + + res[edge] = true; + return res; +} + +function parseBorderWidth(vm, maxW, maxH) { + var value = vm.borderWidth; + var skip = parseBorderSkipped(vm); + var t, r, b, l; + + if (helpers$1.isObject(value)) { + t = +value.top || 0; + r = +value.right || 0; + b = +value.bottom || 0; + l = +value.left || 0; + } else { + t = r = b = l = +value || 0; + } + + return { + t: skip.top || (t < 0) ? 0 : t > maxH ? maxH : t, + r: skip.right || (r < 0) ? 0 : r > maxW ? maxW : r, + b: skip.bottom || (b < 0) ? 0 : b > maxH ? maxH : b, + l: skip.left || (l < 0) ? 0 : l > maxW ? maxW : l + }; +} + +function boundingRects(vm) { + var bounds = getBarBounds(vm); + var width = bounds.right - bounds.left; + var height = bounds.bottom - bounds.top; + var border = parseBorderWidth(vm, width / 2, height / 2); + + return { + outer: { + x: bounds.left, + y: bounds.top, + w: width, + h: height + }, + inner: { + x: bounds.left + border.l, + y: bounds.top + border.t, + w: width - border.l - border.r, + h: height - border.t - border.b + } + }; +} + +function inRange(vm, x, y) { + var skipX = x === null; + var skipY = y === null; + var bounds = !vm || (skipX && skipY) ? false : getBarBounds(vm); + + return bounds + && (skipX || x >= bounds.left && x <= bounds.right) + && (skipY || y >= bounds.top && y <= bounds.bottom); +} + +var element_rectangle = core_element.extend({ + _type: 'rectangle', + + draw: function() { + var ctx = this._chart.ctx; + var vm = this._view; + var rects = boundingRects(vm); + var outer = rects.outer; + var inner = rects.inner; + + ctx.fillStyle = vm.backgroundColor; + ctx.fillRect(outer.x, outer.y, outer.w, outer.h); + + if (outer.w === inner.w && outer.h === inner.h) { + return; + } + + ctx.save(); + ctx.beginPath(); + ctx.rect(outer.x, outer.y, outer.w, outer.h); + ctx.clip(); + ctx.fillStyle = vm.borderColor; + ctx.rect(inner.x, inner.y, inner.w, inner.h); + ctx.fill('evenodd'); + ctx.restore(); + }, + + height: function() { + var vm = this._view; + return vm.base - vm.y; + }, + + inRange: function(mouseX, mouseY) { + return inRange(this._view, mouseX, mouseY); + }, + + inLabelRange: function(mouseX, mouseY) { + var vm = this._view; + return isVertical(vm) + ? inRange(vm, mouseX, null) + : inRange(vm, null, mouseY); + }, + + inXRange: function(mouseX) { + return inRange(this._view, mouseX, null); + }, + + inYRange: function(mouseY) { + return inRange(this._view, null, mouseY); + }, + + getCenterPoint: function() { + var vm = this._view; + var x, y; + if (isVertical(vm)) { + x = vm.x; + y = (vm.y + vm.base) / 2; + } else { + x = (vm.x + vm.base) / 2; + y = vm.y; + } + + return {x: x, y: y}; + }, + + getArea: function() { + var vm = this._view; + + return isVertical(vm) + ? vm.width * Math.abs(vm.y - vm.base) + : vm.height * Math.abs(vm.x - vm.base); + }, + + tooltipPosition: function() { + var vm = this._view; + return { + x: vm.x, + y: vm.y + }; + } +}); + +var elements = {}; +var Arc = element_arc; +var Line = element_line; +var Point = element_point; +var Rectangle = element_rectangle; +elements.Arc = Arc; +elements.Line = Line; +elements.Point = Point; +elements.Rectangle = Rectangle; + +var deprecated = helpers$1._deprecated; +var valueOrDefault$3 = helpers$1.valueOrDefault; + +core_defaults._set('bar', { + hover: { + mode: 'label' + }, + + scales: { + xAxes: [{ + type: 'category', + offset: true, + gridLines: { + offsetGridLines: true + } + }], + + yAxes: [{ + type: 'linear' + }] + } +}); + +core_defaults._set('global', { + datasets: { + bar: { + categoryPercentage: 0.8, + barPercentage: 0.9 + } + } +}); + +/** + * Computes the "optimal" sample size to maintain bars equally sized while preventing overlap. + * @private + */ +function computeMinSampleSize(scale, pixels) { + var min = scale._length; + var prev, curr, i, ilen; + + for (i = 1, ilen = pixels.length; i < ilen; ++i) { + min = Math.min(min, Math.abs(pixels[i] - pixels[i - 1])); + } + + for (i = 0, ilen = scale.getTicks().length; i < ilen; ++i) { + curr = scale.getPixelForTick(i); + min = i > 0 ? Math.min(min, Math.abs(curr - prev)) : min; + prev = curr; + } + + return min; +} + +/** + * Computes an "ideal" category based on the absolute bar thickness or, if undefined or null, + * uses the smallest interval (see computeMinSampleSize) that prevents bar overlapping. This + * mode currently always generates bars equally sized (until we introduce scriptable options?). + * @private + */ +function computeFitCategoryTraits(index, ruler, options) { + var thickness = options.barThickness; + var count = ruler.stackCount; + var curr = ruler.pixels[index]; + var min = helpers$1.isNullOrUndef(thickness) + ? computeMinSampleSize(ruler.scale, ruler.pixels) + : -1; + var size, ratio; + + if (helpers$1.isNullOrUndef(thickness)) { + size = min * options.categoryPercentage; + ratio = options.barPercentage; + } else { + // When bar thickness is enforced, category and bar percentages are ignored. + // Note(SB): we could add support for relative bar thickness (e.g. barThickness: '50%') + // and deprecate barPercentage since this value is ignored when thickness is absolute. + size = thickness * count; + ratio = 1; + } + + return { + chunk: size / count, + ratio: ratio, + start: curr - (size / 2) + }; +} + +/** + * Computes an "optimal" category that globally arranges bars side by side (no gap when + * percentage options are 1), based on the previous and following categories. This mode + * generates bars with different widths when data are not evenly spaced. + * @private + */ +function computeFlexCategoryTraits(index, ruler, options) { + var pixels = ruler.pixels; + var curr = pixels[index]; + var prev = index > 0 ? pixels[index - 1] : null; + var next = index < pixels.length - 1 ? pixels[index + 1] : null; + var percent = options.categoryPercentage; + var start, size; + + if (prev === null) { + // first data: its size is double based on the next point or, + // if it's also the last data, we use the scale size. + prev = curr - (next === null ? ruler.end - ruler.start : next - curr); + } + + if (next === null) { + // last data: its size is also double based on the previous point. + next = curr + curr - prev; + } + + start = curr - (curr - Math.min(prev, next)) / 2 * percent; + size = Math.abs(next - prev) / 2 * percent; + + return { + chunk: size / ruler.stackCount, + ratio: options.barPercentage, + start: start + }; +} + +var controller_bar = core_datasetController.extend({ + + dataElementType: elements.Rectangle, + + /** + * @private + */ + _dataElementOptions: [ + 'backgroundColor', + 'borderColor', + 'borderSkipped', + 'borderWidth', + 'barPercentage', + 'barThickness', + 'categoryPercentage', + 'maxBarThickness', + 'minBarLength' + ], + + initialize: function() { + var me = this; + var meta, scaleOpts; + + core_datasetController.prototype.initialize.apply(me, arguments); + + meta = me.getMeta(); + meta.stack = me.getDataset().stack; + meta.bar = true; + + scaleOpts = me._getIndexScale().options; + deprecated('bar chart', scaleOpts.barPercentage, 'scales.[x/y]Axes.barPercentage', 'dataset.barPercentage'); + deprecated('bar chart', scaleOpts.barThickness, 'scales.[x/y]Axes.barThickness', 'dataset.barThickness'); + deprecated('bar chart', scaleOpts.categoryPercentage, 'scales.[x/y]Axes.categoryPercentage', 'dataset.categoryPercentage'); + deprecated('bar chart', me._getValueScale().options.minBarLength, 'scales.[x/y]Axes.minBarLength', 'dataset.minBarLength'); + deprecated('bar chart', scaleOpts.maxBarThickness, 'scales.[x/y]Axes.maxBarThickness', 'dataset.maxBarThickness'); + }, + + update: function(reset) { + var me = this; + var rects = me.getMeta().data; + var i, ilen; + + me._ruler = me.getRuler(); + + for (i = 0, ilen = rects.length; i < ilen; ++i) { + me.updateElement(rects[i], i, reset); + } + }, + + updateElement: function(rectangle, index, reset) { + var me = this; + var meta = me.getMeta(); + var dataset = me.getDataset(); + var options = me._resolveDataElementOptions(rectangle, index); + + rectangle._xScale = me.getScaleForId(meta.xAxisID); + rectangle._yScale = me.getScaleForId(meta.yAxisID); + rectangle._datasetIndex = me.index; + rectangle._index = index; + rectangle._model = { + backgroundColor: options.backgroundColor, + borderColor: options.borderColor, + borderSkipped: options.borderSkipped, + borderWidth: options.borderWidth, + datasetLabel: dataset.label, + label: me.chart.data.labels[index] + }; + + if (helpers$1.isArray(dataset.data[index])) { + rectangle._model.borderSkipped = null; + } + + me._updateElementGeometry(rectangle, index, reset, options); + + rectangle.pivot(); + }, + + /** + * @private + */ + _updateElementGeometry: function(rectangle, index, reset, options) { + var me = this; + var model = rectangle._model; + var vscale = me._getValueScale(); + var base = vscale.getBasePixel(); + var horizontal = vscale.isHorizontal(); + var ruler = me._ruler || me.getRuler(); + var vpixels = me.calculateBarValuePixels(me.index, index, options); + var ipixels = me.calculateBarIndexPixels(me.index, index, ruler, options); + + model.horizontal = horizontal; + model.base = reset ? base : vpixels.base; + model.x = horizontal ? reset ? base : vpixels.head : ipixels.center; + model.y = horizontal ? ipixels.center : reset ? base : vpixels.head; + model.height = horizontal ? ipixels.size : undefined; + model.width = horizontal ? undefined : ipixels.size; + }, + + /** + * Returns the stacks based on groups and bar visibility. + * @param {number} [last] - The dataset index + * @returns {string[]} The list of stack IDs + * @private + */ + _getStacks: function(last) { + var me = this; + var scale = me._getIndexScale(); + var metasets = scale._getMatchingVisibleMetas(me._type); + var stacked = scale.options.stacked; + var ilen = metasets.length; + var stacks = []; + var i, meta; + + for (i = 0; i < ilen; ++i) { + meta = metasets[i]; + // stacked | meta.stack + // | found | not found | undefined + // false | x | x | x + // true | | x | + // undefined | | x | x + if (stacked === false || stacks.indexOf(meta.stack) === -1 || + (stacked === undefined && meta.stack === undefined)) { + stacks.push(meta.stack); + } + if (meta.index === last) { + break; + } + } + + return stacks; + }, + + /** + * Returns the effective number of stacks based on groups and bar visibility. + * @private + */ + getStackCount: function() { + return this._getStacks().length; + }, + + /** + * Returns the stack index for the given dataset based on groups and bar visibility. + * @param {number} [datasetIndex] - The dataset index + * @param {string} [name] - The stack name to find + * @returns {number} The stack index + * @private + */ + getStackIndex: function(datasetIndex, name) { + var stacks = this._getStacks(datasetIndex); + var index = (name !== undefined) + ? stacks.indexOf(name) + : -1; // indexOf returns -1 if element is not present + + return (index === -1) + ? stacks.length - 1 + : index; + }, + + /** + * @private + */ + getRuler: function() { + var me = this; + var scale = me._getIndexScale(); + var pixels = []; + var i, ilen; + + for (i = 0, ilen = me.getMeta().data.length; i < ilen; ++i) { + pixels.push(scale.getPixelForValue(null, i, me.index)); + } + + return { + pixels: pixels, + start: scale._startPixel, + end: scale._endPixel, + stackCount: me.getStackCount(), + scale: scale + }; + }, + + /** + * Note: pixel values are not clamped to the scale area. + * @private + */ + calculateBarValuePixels: function(datasetIndex, index, options) { + var me = this; + var chart = me.chart; + var scale = me._getValueScale(); + var isHorizontal = scale.isHorizontal(); + var datasets = chart.data.datasets; + var metasets = scale._getMatchingVisibleMetas(me._type); + var value = scale._parseValue(datasets[datasetIndex].data[index]); + var minBarLength = options.minBarLength; + var stacked = scale.options.stacked; + var stack = me.getMeta().stack; + var start = value.start === undefined ? 0 : value.max >= 0 && value.min >= 0 ? value.min : value.max; + var length = value.start === undefined ? value.end : value.max >= 0 && value.min >= 0 ? value.max - value.min : value.min - value.max; + var ilen = metasets.length; + var i, imeta, ivalue, base, head, size, stackLength; + + if (stacked || (stacked === undefined && stack !== undefined)) { + for (i = 0; i < ilen; ++i) { + imeta = metasets[i]; + + if (imeta.index === datasetIndex) { + break; + } + + if (imeta.stack === stack) { + stackLength = scale._parseValue(datasets[imeta.index].data[index]); + ivalue = stackLength.start === undefined ? stackLength.end : stackLength.min >= 0 && stackLength.max >= 0 ? stackLength.max : stackLength.min; + + if ((value.min < 0 && ivalue < 0) || (value.max >= 0 && ivalue > 0)) { + start += ivalue; + } + } + } + } + + base = scale.getPixelForValue(start); + head = scale.getPixelForValue(start + length); + size = head - base; + + if (minBarLength !== undefined && Math.abs(size) < minBarLength) { + size = minBarLength; + if (length >= 0 && !isHorizontal || length < 0 && isHorizontal) { + head = base - minBarLength; + } else { + head = base + minBarLength; + } + } + + return { + size: size, + base: base, + head: head, + center: head + size / 2 + }; + }, + + /** + * @private + */ + calculateBarIndexPixels: function(datasetIndex, index, ruler, options) { + var me = this; + var range = options.barThickness === 'flex' + ? computeFlexCategoryTraits(index, ruler, options) + : computeFitCategoryTraits(index, ruler, options); + + var stackIndex = me.getStackIndex(datasetIndex, me.getMeta().stack); + var center = range.start + (range.chunk * stackIndex) + (range.chunk / 2); + var size = Math.min( + valueOrDefault$3(options.maxBarThickness, Infinity), + range.chunk * range.ratio); + + return { + base: center - size / 2, + head: center + size / 2, + center: center, + size: size + }; + }, + + draw: function() { + var me = this; + var chart = me.chart; + var scale = me._getValueScale(); + var rects = me.getMeta().data; + var dataset = me.getDataset(); + var ilen = rects.length; + var i = 0; + + helpers$1.canvas.clipArea(chart.ctx, chart.chartArea); + + for (; i < ilen; ++i) { + var val = scale._parseValue(dataset.data[i]); + if (!isNaN(val.min) && !isNaN(val.max)) { + rects[i].draw(); + } + } + + helpers$1.canvas.unclipArea(chart.ctx); + }, + + /** + * @private + */ + _resolveDataElementOptions: function() { + var me = this; + var values = helpers$1.extend({}, core_datasetController.prototype._resolveDataElementOptions.apply(me, arguments)); + var indexOpts = me._getIndexScale().options; + var valueOpts = me._getValueScale().options; + + values.barPercentage = valueOrDefault$3(indexOpts.barPercentage, values.barPercentage); + values.barThickness = valueOrDefault$3(indexOpts.barThickness, values.barThickness); + values.categoryPercentage = valueOrDefault$3(indexOpts.categoryPercentage, values.categoryPercentage); + values.maxBarThickness = valueOrDefault$3(indexOpts.maxBarThickness, values.maxBarThickness); + values.minBarLength = valueOrDefault$3(valueOpts.minBarLength, values.minBarLength); + + return values; + } + +}); + +var valueOrDefault$4 = helpers$1.valueOrDefault; +var resolve$1 = helpers$1.options.resolve; + +core_defaults._set('bubble', { + hover: { + mode: 'single' + }, + + scales: { + xAxes: [{ + type: 'linear', // bubble should probably use a linear scale by default + position: 'bottom', + id: 'x-axis-0' // need an ID so datasets can reference the scale + }], + yAxes: [{ + type: 'linear', + position: 'left', + id: 'y-axis-0' + }] + }, + + tooltips: { + callbacks: { + title: function() { + // Title doesn't make sense for scatter since we format the data as a point + return ''; + }, + label: function(item, data) { + var datasetLabel = data.datasets[item.datasetIndex].label || ''; + var dataPoint = data.datasets[item.datasetIndex].data[item.index]; + return datasetLabel + ': (' + item.xLabel + ', ' + item.yLabel + ', ' + dataPoint.r + ')'; + } + } + } +}); + +var controller_bubble = core_datasetController.extend({ + /** + * @protected + */ + dataElementType: elements.Point, + + /** + * @private + */ + _dataElementOptions: [ + 'backgroundColor', + 'borderColor', + 'borderWidth', + 'hoverBackgroundColor', + 'hoverBorderColor', + 'hoverBorderWidth', + 'hoverRadius', + 'hitRadius', + 'pointStyle', + 'rotation' + ], + + /** + * @protected + */ + update: function(reset) { + var me = this; + var meta = me.getMeta(); + var points = meta.data; + + // Update Points + helpers$1.each(points, function(point, index) { + me.updateElement(point, index, reset); + }); + }, + + /** + * @protected + */ + updateElement: function(point, index, reset) { + var me = this; + var meta = me.getMeta(); + var custom = point.custom || {}; + var xScale = me.getScaleForId(meta.xAxisID); + var yScale = me.getScaleForId(meta.yAxisID); + var options = me._resolveDataElementOptions(point, index); + var data = me.getDataset().data[index]; + var dsIndex = me.index; + + var x = reset ? xScale.getPixelForDecimal(0.5) : xScale.getPixelForValue(typeof data === 'object' ? data : NaN, index, dsIndex); + var y = reset ? yScale.getBasePixel() : yScale.getPixelForValue(data, index, dsIndex); + + point._xScale = xScale; + point._yScale = yScale; + point._options = options; + point._datasetIndex = dsIndex; + point._index = index; + point._model = { + backgroundColor: options.backgroundColor, + borderColor: options.borderColor, + borderWidth: options.borderWidth, + hitRadius: options.hitRadius, + pointStyle: options.pointStyle, + rotation: options.rotation, + radius: reset ? 0 : options.radius, + skip: custom.skip || isNaN(x) || isNaN(y), + x: x, + y: y, + }; + + point.pivot(); + }, + + /** + * @protected + */ + setHoverStyle: function(point) { + var model = point._model; + var options = point._options; + var getHoverColor = helpers$1.getHoverColor; + + point.$previousStyle = { + backgroundColor: model.backgroundColor, + borderColor: model.borderColor, + borderWidth: model.borderWidth, + radius: model.radius + }; + + model.backgroundColor = valueOrDefault$4(options.hoverBackgroundColor, getHoverColor(options.backgroundColor)); + model.borderColor = valueOrDefault$4(options.hoverBorderColor, getHoverColor(options.borderColor)); + model.borderWidth = valueOrDefault$4(options.hoverBorderWidth, options.borderWidth); + model.radius = options.radius + options.hoverRadius; + }, + + /** + * @private + */ + _resolveDataElementOptions: function(point, index) { + var me = this; + var chart = me.chart; + var dataset = me.getDataset(); + var custom = point.custom || {}; + var data = dataset.data[index] || {}; + var values = core_datasetController.prototype._resolveDataElementOptions.apply(me, arguments); + + // Scriptable options + var context = { + chart: chart, + dataIndex: index, + dataset: dataset, + datasetIndex: me.index + }; + + // In case values were cached (and thus frozen), we need to clone the values + if (me._cachedDataOpts === values) { + values = helpers$1.extend({}, values); + } + + // Custom radius resolution + values.radius = resolve$1([ + custom.radius, + data.r, + me._config.radius, + chart.options.elements.point.radius + ], context, index); + + return values; + } +}); + +var valueOrDefault$5 = helpers$1.valueOrDefault; + +var PI$1 = Math.PI; +var DOUBLE_PI$1 = PI$1 * 2; +var HALF_PI$1 = PI$1 / 2; + +core_defaults._set('doughnut', { + animation: { + // Boolean - Whether we animate the rotation of the Doughnut + animateRotate: true, + // Boolean - Whether we animate scaling the Doughnut from the centre + animateScale: false + }, + hover: { + mode: 'single' + }, + legendCallback: function(chart) { + var list = document.createElement('ul'); + var data = chart.data; + var datasets = data.datasets; + var labels = data.labels; + var i, ilen, listItem, listItemSpan; + + list.setAttribute('class', chart.id + '-legend'); + if (datasets.length) { + for (i = 0, ilen = datasets[0].data.length; i < ilen; ++i) { + listItem = list.appendChild(document.createElement('li')); + listItemSpan = listItem.appendChild(document.createElement('span')); + listItemSpan.style.backgroundColor = datasets[0].backgroundColor[i]; + if (labels[i]) { + listItem.appendChild(document.createTextNode(labels[i])); + } + } + } + + return list.outerHTML; + }, + legend: { + labels: { + generateLabels: function(chart) { + var data = chart.data; + if (data.labels.length && data.datasets.length) { + return data.labels.map(function(label, i) { + var meta = chart.getDatasetMeta(0); + var style = meta.controller.getStyle(i); + + return { + text: label, + fillStyle: style.backgroundColor, + strokeStyle: style.borderColor, + lineWidth: style.borderWidth, + hidden: isNaN(data.datasets[0].data[i]) || meta.data[i].hidden, + + // Extra data used for toggling the correct item + index: i + }; + }); + } + return []; + } + }, + + onClick: function(e, legendItem) { + var index = legendItem.index; + var chart = this.chart; + var i, ilen, meta; + + for (i = 0, ilen = (chart.data.datasets || []).length; i < ilen; ++i) { + meta = chart.getDatasetMeta(i); + // toggle visibility of index if exists + if (meta.data[index]) { + meta.data[index].hidden = !meta.data[index].hidden; + } + } + + chart.update(); + } + }, + + // The percentage of the chart that we cut out of the middle. + cutoutPercentage: 50, + + // The rotation of the chart, where the first data arc begins. + rotation: -HALF_PI$1, + + // The total circumference of the chart. + circumference: DOUBLE_PI$1, + + // Need to override these to give a nice default + tooltips: { + callbacks: { + title: function() { + return ''; + }, + label: function(tooltipItem, data) { + var dataLabel = data.labels[tooltipItem.index]; + var value = ': ' + data.datasets[tooltipItem.datasetIndex].data[tooltipItem.index]; + + if (helpers$1.isArray(dataLabel)) { + // show value on first line of multiline label + // need to clone because we are changing the value + dataLabel = dataLabel.slice(); + dataLabel[0] += value; + } else { + dataLabel += value; + } + + return dataLabel; + } + } + } +}); + +var controller_doughnut = core_datasetController.extend({ + + dataElementType: elements.Arc, + + linkScales: helpers$1.noop, + + /** + * @private + */ + _dataElementOptions: [ + 'backgroundColor', + 'borderColor', + 'borderWidth', + 'borderAlign', + 'hoverBackgroundColor', + 'hoverBorderColor', + 'hoverBorderWidth', + ], + + // Get index of the dataset in relation to the visible datasets. This allows determining the inner and outer radius correctly + getRingIndex: function(datasetIndex) { + var ringIndex = 0; + + for (var j = 0; j < datasetIndex; ++j) { + if (this.chart.isDatasetVisible(j)) { + ++ringIndex; + } + } + + return ringIndex; + }, + + update: function(reset) { + var me = this; + var chart = me.chart; + var chartArea = chart.chartArea; + var opts = chart.options; + var ratioX = 1; + var ratioY = 1; + var offsetX = 0; + var offsetY = 0; + var meta = me.getMeta(); + var arcs = meta.data; + var cutout = opts.cutoutPercentage / 100 || 0; + var circumference = opts.circumference; + var chartWeight = me._getRingWeight(me.index); + var maxWidth, maxHeight, i, ilen; + + // If the chart's circumference isn't a full circle, calculate size as a ratio of the width/height of the arc + if (circumference < DOUBLE_PI$1) { + var startAngle = opts.rotation % DOUBLE_PI$1; + startAngle += startAngle >= PI$1 ? -DOUBLE_PI$1 : startAngle < -PI$1 ? DOUBLE_PI$1 : 0; + var endAngle = startAngle + circumference; + var startX = Math.cos(startAngle); + var startY = Math.sin(startAngle); + var endX = Math.cos(endAngle); + var endY = Math.sin(endAngle); + var contains0 = (startAngle <= 0 && endAngle >= 0) || endAngle >= DOUBLE_PI$1; + var contains90 = (startAngle <= HALF_PI$1 && endAngle >= HALF_PI$1) || endAngle >= DOUBLE_PI$1 + HALF_PI$1; + var contains180 = startAngle === -PI$1 || endAngle >= PI$1; + var contains270 = (startAngle <= -HALF_PI$1 && endAngle >= -HALF_PI$1) || endAngle >= PI$1 + HALF_PI$1; + var minX = contains180 ? -1 : Math.min(startX, startX * cutout, endX, endX * cutout); + var minY = contains270 ? -1 : Math.min(startY, startY * cutout, endY, endY * cutout); + var maxX = contains0 ? 1 : Math.max(startX, startX * cutout, endX, endX * cutout); + var maxY = contains90 ? 1 : Math.max(startY, startY * cutout, endY, endY * cutout); + ratioX = (maxX - minX) / 2; + ratioY = (maxY - minY) / 2; + offsetX = -(maxX + minX) / 2; + offsetY = -(maxY + minY) / 2; + } + + for (i = 0, ilen = arcs.length; i < ilen; ++i) { + arcs[i]._options = me._resolveDataElementOptions(arcs[i], i); + } + + chart.borderWidth = me.getMaxBorderWidth(); + maxWidth = (chartArea.right - chartArea.left - chart.borderWidth) / ratioX; + maxHeight = (chartArea.bottom - chartArea.top - chart.borderWidth) / ratioY; + chart.outerRadius = Math.max(Math.min(maxWidth, maxHeight) / 2, 0); + chart.innerRadius = Math.max(chart.outerRadius * cutout, 0); + chart.radiusLength = (chart.outerRadius - chart.innerRadius) / (me._getVisibleDatasetWeightTotal() || 1); + chart.offsetX = offsetX * chart.outerRadius; + chart.offsetY = offsetY * chart.outerRadius; + + meta.total = me.calculateTotal(); + + me.outerRadius = chart.outerRadius - chart.radiusLength * me._getRingWeightOffset(me.index); + me.innerRadius = Math.max(me.outerRadius - chart.radiusLength * chartWeight, 0); + + for (i = 0, ilen = arcs.length; i < ilen; ++i) { + me.updateElement(arcs[i], i, reset); + } + }, + + updateElement: function(arc, index, reset) { + var me = this; + var chart = me.chart; + var chartArea = chart.chartArea; + var opts = chart.options; + var animationOpts = opts.animation; + var centerX = (chartArea.left + chartArea.right) / 2; + var centerY = (chartArea.top + chartArea.bottom) / 2; + var startAngle = opts.rotation; // non reset case handled later + var endAngle = opts.rotation; // non reset case handled later + var dataset = me.getDataset(); + var circumference = reset && animationOpts.animateRotate ? 0 : arc.hidden ? 0 : me.calculateCircumference(dataset.data[index]) * (opts.circumference / DOUBLE_PI$1); + var innerRadius = reset && animationOpts.animateScale ? 0 : me.innerRadius; + var outerRadius = reset && animationOpts.animateScale ? 0 : me.outerRadius; + var options = arc._options || {}; + + helpers$1.extend(arc, { + // Utility + _datasetIndex: me.index, + _index: index, + + // Desired view properties + _model: { + backgroundColor: options.backgroundColor, + borderColor: options.borderColor, + borderWidth: options.borderWidth, + borderAlign: options.borderAlign, + x: centerX + chart.offsetX, + y: centerY + chart.offsetY, + startAngle: startAngle, + endAngle: endAngle, + circumference: circumference, + outerRadius: outerRadius, + innerRadius: innerRadius, + label: helpers$1.valueAtIndexOrDefault(dataset.label, index, chart.data.labels[index]) + } + }); + + var model = arc._model; + + // Set correct angles if not resetting + if (!reset || !animationOpts.animateRotate) { + if (index === 0) { + model.startAngle = opts.rotation; + } else { + model.startAngle = me.getMeta().data[index - 1]._model.endAngle; + } + + model.endAngle = model.startAngle + model.circumference; + } + + arc.pivot(); + }, + + calculateTotal: function() { + var dataset = this.getDataset(); + var meta = this.getMeta(); + var total = 0; + var value; + + helpers$1.each(meta.data, function(element, index) { + value = dataset.data[index]; + if (!isNaN(value) && !element.hidden) { + total += Math.abs(value); + } + }); + + /* if (total === 0) { + total = NaN; + }*/ + + return total; + }, + + calculateCircumference: function(value) { + var total = this.getMeta().total; + if (total > 0 && !isNaN(value)) { + return DOUBLE_PI$1 * (Math.abs(value) / total); + } + return 0; + }, + + // gets the max border or hover width to properly scale pie charts + getMaxBorderWidth: function(arcs) { + var me = this; + var max = 0; + var chart = me.chart; + var i, ilen, meta, arc, controller, options, borderWidth, hoverWidth; + + if (!arcs) { + // Find the outmost visible dataset + for (i = 0, ilen = chart.data.datasets.length; i < ilen; ++i) { + if (chart.isDatasetVisible(i)) { + meta = chart.getDatasetMeta(i); + arcs = meta.data; + if (i !== me.index) { + controller = meta.controller; + } + break; + } + } + } + + if (!arcs) { + return 0; + } + + for (i = 0, ilen = arcs.length; i < ilen; ++i) { + arc = arcs[i]; + if (controller) { + controller._configure(); + options = controller._resolveDataElementOptions(arc, i); + } else { + options = arc._options; + } + if (options.borderAlign !== 'inner') { + borderWidth = options.borderWidth; + hoverWidth = options.hoverBorderWidth; + + max = borderWidth > max ? borderWidth : max; + max = hoverWidth > max ? hoverWidth : max; + } + } + return max; + }, + + /** + * @protected + */ + setHoverStyle: function(arc) { + var model = arc._model; + var options = arc._options; + var getHoverColor = helpers$1.getHoverColor; + + arc.$previousStyle = { + backgroundColor: model.backgroundColor, + borderColor: model.borderColor, + borderWidth: model.borderWidth, + }; + + model.backgroundColor = valueOrDefault$5(options.hoverBackgroundColor, getHoverColor(options.backgroundColor)); + model.borderColor = valueOrDefault$5(options.hoverBorderColor, getHoverColor(options.borderColor)); + model.borderWidth = valueOrDefault$5(options.hoverBorderWidth, options.borderWidth); + }, + + /** + * Get radius length offset of the dataset in relation to the visible datasets weights. This allows determining the inner and outer radius correctly + * @private + */ + _getRingWeightOffset: function(datasetIndex) { + var ringWeightOffset = 0; + + for (var i = 0; i < datasetIndex; ++i) { + if (this.chart.isDatasetVisible(i)) { + ringWeightOffset += this._getRingWeight(i); + } + } + + return ringWeightOffset; + }, + + /** + * @private + */ + _getRingWeight: function(dataSetIndex) { + return Math.max(valueOrDefault$5(this.chart.data.datasets[dataSetIndex].weight, 1), 0); + }, + + /** + * Returns the sum of all visibile data set weights. This value can be 0. + * @private + */ + _getVisibleDatasetWeightTotal: function() { + return this._getRingWeightOffset(this.chart.data.datasets.length); + } +}); + +core_defaults._set('horizontalBar', { + hover: { + mode: 'index', + axis: 'y' + }, + + scales: { + xAxes: [{ + type: 'linear', + position: 'bottom' + }], + + yAxes: [{ + type: 'category', + position: 'left', + offset: true, + gridLines: { + offsetGridLines: true + } + }] + }, + + elements: { + rectangle: { + borderSkipped: 'left' + } + }, + + tooltips: { + mode: 'index', + axis: 'y' + } +}); + +core_defaults._set('global', { + datasets: { + horizontalBar: { + categoryPercentage: 0.8, + barPercentage: 0.9 + } + } +}); + +var controller_horizontalBar = controller_bar.extend({ + /** + * @private + */ + _getValueScaleId: function() { + return this.getMeta().xAxisID; + }, + + /** + * @private + */ + _getIndexScaleId: function() { + return this.getMeta().yAxisID; + } +}); + +var valueOrDefault$6 = helpers$1.valueOrDefault; +var resolve$2 = helpers$1.options.resolve; +var isPointInArea = helpers$1.canvas._isPointInArea; + +core_defaults._set('line', { + showLines: true, + spanGaps: false, + + hover: { + mode: 'label' + }, + + scales: { + xAxes: [{ + type: 'category', + id: 'x-axis-0' + }], + yAxes: [{ + type: 'linear', + id: 'y-axis-0' + }] + } +}); + +function scaleClip(scale, halfBorderWidth) { + var tickOpts = scale && scale.options.ticks || {}; + var reverse = tickOpts.reverse; + var min = tickOpts.min === undefined ? halfBorderWidth : 0; + var max = tickOpts.max === undefined ? halfBorderWidth : 0; + return { + start: reverse ? max : min, + end: reverse ? min : max + }; +} + +function defaultClip(xScale, yScale, borderWidth) { + var halfBorderWidth = borderWidth / 2; + var x = scaleClip(xScale, halfBorderWidth); + var y = scaleClip(yScale, halfBorderWidth); + + return { + top: y.end, + right: x.end, + bottom: y.start, + left: x.start + }; +} + +function toClip(value) { + var t, r, b, l; + + if (helpers$1.isObject(value)) { + t = value.top; + r = value.right; + b = value.bottom; + l = value.left; + } else { + t = r = b = l = value; + } + + return { + top: t, + right: r, + bottom: b, + left: l + }; +} + + +var controller_line = core_datasetController.extend({ + + datasetElementType: elements.Line, + + dataElementType: elements.Point, + + /** + * @private + */ + _datasetElementOptions: [ + 'backgroundColor', + 'borderCapStyle', + 'borderColor', + 'borderDash', + 'borderDashOffset', + 'borderJoinStyle', + 'borderWidth', + 'cubicInterpolationMode', + 'fill' + ], + + /** + * @private + */ + _dataElementOptions: { + backgroundColor: 'pointBackgroundColor', + borderColor: 'pointBorderColor', + borderWidth: 'pointBorderWidth', + hitRadius: 'pointHitRadius', + hoverBackgroundColor: 'pointHoverBackgroundColor', + hoverBorderColor: 'pointHoverBorderColor', + hoverBorderWidth: 'pointHoverBorderWidth', + hoverRadius: 'pointHoverRadius', + pointStyle: 'pointStyle', + radius: 'pointRadius', + rotation: 'pointRotation' + }, + + update: function(reset) { + var me = this; + var meta = me.getMeta(); + var line = meta.dataset; + var points = meta.data || []; + var options = me.chart.options; + var config = me._config; + var showLine = me._showLine = valueOrDefault$6(config.showLine, options.showLines); + var i, ilen; + + me._xScale = me.getScaleForId(meta.xAxisID); + me._yScale = me.getScaleForId(meta.yAxisID); + + // Update Line + if (showLine) { + // Compatibility: If the properties are defined with only the old name, use those values + if (config.tension !== undefined && config.lineTension === undefined) { + config.lineTension = config.tension; + } + + // Utility + line._scale = me._yScale; + line._datasetIndex = me.index; + // Data + line._children = points; + // Model + line._model = me._resolveDatasetElementOptions(line); + + line.pivot(); + } + + // Update Points + for (i = 0, ilen = points.length; i < ilen; ++i) { + me.updateElement(points[i], i, reset); + } + + if (showLine && line._model.tension !== 0) { + me.updateBezierControlPoints(); + } + + // Now pivot the point for animation + for (i = 0, ilen = points.length; i < ilen; ++i) { + points[i].pivot(); + } + }, + + updateElement: function(point, index, reset) { + var me = this; + var meta = me.getMeta(); + var custom = point.custom || {}; + var dataset = me.getDataset(); + var datasetIndex = me.index; + var value = dataset.data[index]; + var xScale = me._xScale; + var yScale = me._yScale; + var lineModel = meta.dataset._model; + var x, y; + + var options = me._resolveDataElementOptions(point, index); + + x = xScale.getPixelForValue(typeof value === 'object' ? value : NaN, index, datasetIndex); + y = reset ? yScale.getBasePixel() : me.calculatePointY(value, index, datasetIndex); + + // Utility + point._xScale = xScale; + point._yScale = yScale; + point._options = options; + point._datasetIndex = datasetIndex; + point._index = index; + + // Desired view properties + point._model = { + x: x, + y: y, + skip: custom.skip || isNaN(x) || isNaN(y), + // Appearance + radius: options.radius, + pointStyle: options.pointStyle, + rotation: options.rotation, + backgroundColor: options.backgroundColor, + borderColor: options.borderColor, + borderWidth: options.borderWidth, + tension: valueOrDefault$6(custom.tension, lineModel ? lineModel.tension : 0), + steppedLine: lineModel ? lineModel.steppedLine : false, + // Tooltip + hitRadius: options.hitRadius + }; + }, + + /** + * @private + */ + _resolveDatasetElementOptions: function(element) { + var me = this; + var config = me._config; + var custom = element.custom || {}; + var options = me.chart.options; + var lineOptions = options.elements.line; + var values = core_datasetController.prototype._resolveDatasetElementOptions.apply(me, arguments); + + // The default behavior of lines is to break at null values, according + // to https://github.com/chartjs/Chart.js/issues/2435#issuecomment-216718158 + // This option gives lines the ability to span gaps + values.spanGaps = valueOrDefault$6(config.spanGaps, options.spanGaps); + values.tension = valueOrDefault$6(config.lineTension, lineOptions.tension); + values.steppedLine = resolve$2([custom.steppedLine, config.steppedLine, lineOptions.stepped]); + values.clip = toClip(valueOrDefault$6(config.clip, defaultClip(me._xScale, me._yScale, values.borderWidth))); + + return values; + }, + + calculatePointY: function(value, index, datasetIndex) { + var me = this; + var chart = me.chart; + var yScale = me._yScale; + var sumPos = 0; + var sumNeg = 0; + var i, ds, dsMeta, stackedRightValue, rightValue, metasets, ilen; + + if (yScale.options.stacked) { + rightValue = +yScale.getRightValue(value); + metasets = chart._getSortedVisibleDatasetMetas(); + ilen = metasets.length; + + for (i = 0; i < ilen; ++i) { + dsMeta = metasets[i]; + if (dsMeta.index === datasetIndex) { + break; + } + + ds = chart.data.datasets[dsMeta.index]; + if (dsMeta.type === 'line' && dsMeta.yAxisID === yScale.id) { + stackedRightValue = +yScale.getRightValue(ds.data[index]); + if (stackedRightValue < 0) { + sumNeg += stackedRightValue || 0; + } else { + sumPos += stackedRightValue || 0; + } + } + } + + if (rightValue < 0) { + return yScale.getPixelForValue(sumNeg + rightValue); + } + return yScale.getPixelForValue(sumPos + rightValue); + } + return yScale.getPixelForValue(value); + }, + + updateBezierControlPoints: function() { + var me = this; + var chart = me.chart; + var meta = me.getMeta(); + var lineModel = meta.dataset._model; + var area = chart.chartArea; + var points = meta.data || []; + var i, ilen, model, controlPoints; + + // Only consider points that are drawn in case the spanGaps option is used + if (lineModel.spanGaps) { + points = points.filter(function(pt) { + return !pt._model.skip; + }); + } + + function capControlPoint(pt, min, max) { + return Math.max(Math.min(pt, max), min); + } + + if (lineModel.cubicInterpolationMode === 'monotone') { + helpers$1.splineCurveMonotone(points); + } else { + for (i = 0, ilen = points.length; i < ilen; ++i) { + model = points[i]._model; + controlPoints = helpers$1.splineCurve( + helpers$1.previousItem(points, i)._model, + model, + helpers$1.nextItem(points, i)._model, + lineModel.tension + ); + model.controlPointPreviousX = controlPoints.previous.x; + model.controlPointPreviousY = controlPoints.previous.y; + model.controlPointNextX = controlPoints.next.x; + model.controlPointNextY = controlPoints.next.y; + } + } + + if (chart.options.elements.line.capBezierPoints) { + for (i = 0, ilen = points.length; i < ilen; ++i) { + model = points[i]._model; + if (isPointInArea(model, area)) { + if (i > 0 && isPointInArea(points[i - 1]._model, area)) { + model.controlPointPreviousX = capControlPoint(model.controlPointPreviousX, area.left, area.right); + model.controlPointPreviousY = capControlPoint(model.controlPointPreviousY, area.top, area.bottom); + } + if (i < points.length - 1 && isPointInArea(points[i + 1]._model, area)) { + model.controlPointNextX = capControlPoint(model.controlPointNextX, area.left, area.right); + model.controlPointNextY = capControlPoint(model.controlPointNextY, area.top, area.bottom); + } + } + } + } + }, + + draw: function() { + var me = this; + var chart = me.chart; + var meta = me.getMeta(); + var points = meta.data || []; + var area = chart.chartArea; + var canvas = chart.canvas; + var i = 0; + var ilen = points.length; + var clip; + + if (me._showLine) { + clip = meta.dataset._model.clip; + + helpers$1.canvas.clipArea(chart.ctx, { + left: clip.left === false ? 0 : area.left - clip.left, + right: clip.right === false ? canvas.width : area.right + clip.right, + top: clip.top === false ? 0 : area.top - clip.top, + bottom: clip.bottom === false ? canvas.height : area.bottom + clip.bottom + }); + + meta.dataset.draw(); + + helpers$1.canvas.unclipArea(chart.ctx); + } + + // Draw the points + for (; i < ilen; ++i) { + points[i].draw(area); + } + }, + + /** + * @protected + */ + setHoverStyle: function(point) { + var model = point._model; + var options = point._options; + var getHoverColor = helpers$1.getHoverColor; + + point.$previousStyle = { + backgroundColor: model.backgroundColor, + borderColor: model.borderColor, + borderWidth: model.borderWidth, + radius: model.radius + }; + + model.backgroundColor = valueOrDefault$6(options.hoverBackgroundColor, getHoverColor(options.backgroundColor)); + model.borderColor = valueOrDefault$6(options.hoverBorderColor, getHoverColor(options.borderColor)); + model.borderWidth = valueOrDefault$6(options.hoverBorderWidth, options.borderWidth); + model.radius = valueOrDefault$6(options.hoverRadius, options.radius); + }, +}); + +var resolve$3 = helpers$1.options.resolve; + +core_defaults._set('polarArea', { + scale: { + type: 'radialLinear', + angleLines: { + display: false + }, + gridLines: { + circular: true + }, + pointLabels: { + display: false + }, + ticks: { + beginAtZero: true + } + }, + + // Boolean - Whether to animate the rotation of the chart + animation: { + animateRotate: true, + animateScale: true + }, + + startAngle: -0.5 * Math.PI, + legendCallback: function(chart) { + var list = document.createElement('ul'); + var data = chart.data; + var datasets = data.datasets; + var labels = data.labels; + var i, ilen, listItem, listItemSpan; + + list.setAttribute('class', chart.id + '-legend'); + if (datasets.length) { + for (i = 0, ilen = datasets[0].data.length; i < ilen; ++i) { + listItem = list.appendChild(document.createElement('li')); + listItemSpan = listItem.appendChild(document.createElement('span')); + listItemSpan.style.backgroundColor = datasets[0].backgroundColor[i]; + if (labels[i]) { + listItem.appendChild(document.createTextNode(labels[i])); + } + } + } + + return list.outerHTML; + }, + legend: { + labels: { + generateLabels: function(chart) { + var data = chart.data; + if (data.labels.length && data.datasets.length) { + return data.labels.map(function(label, i) { + var meta = chart.getDatasetMeta(0); + var style = meta.controller.getStyle(i); + + return { + text: label, + fillStyle: style.backgroundColor, + strokeStyle: style.borderColor, + lineWidth: style.borderWidth, + hidden: isNaN(data.datasets[0].data[i]) || meta.data[i].hidden, + + // Extra data used for toggling the correct item + index: i + }; + }); + } + return []; + } + }, + + onClick: function(e, legendItem) { + var index = legendItem.index; + var chart = this.chart; + var i, ilen, meta; + + for (i = 0, ilen = (chart.data.datasets || []).length; i < ilen; ++i) { + meta = chart.getDatasetMeta(i); + meta.data[index].hidden = !meta.data[index].hidden; + } + + chart.update(); + } + }, + + // Need to override these to give a nice default + tooltips: { + callbacks: { + title: function() { + return ''; + }, + label: function(item, data) { + return data.labels[item.index] + ': ' + item.yLabel; + } + } + } +}); + +var controller_polarArea = core_datasetController.extend({ + + dataElementType: elements.Arc, + + linkScales: helpers$1.noop, + + /** + * @private + */ + _dataElementOptions: [ + 'backgroundColor', + 'borderColor', + 'borderWidth', + 'borderAlign', + 'hoverBackgroundColor', + 'hoverBorderColor', + 'hoverBorderWidth', + ], + + /** + * @private + */ + _getIndexScaleId: function() { + return this.chart.scale.id; + }, + + /** + * @private + */ + _getValueScaleId: function() { + return this.chart.scale.id; + }, + + update: function(reset) { + var me = this; + var dataset = me.getDataset(); + var meta = me.getMeta(); + var start = me.chart.options.startAngle || 0; + var starts = me._starts = []; + var angles = me._angles = []; + var arcs = meta.data; + var i, ilen, angle; + + me._updateRadius(); + + meta.count = me.countVisibleElements(); + + for (i = 0, ilen = dataset.data.length; i < ilen; i++) { + starts[i] = start; + angle = me._computeAngle(i); + angles[i] = angle; + start += angle; + } + + for (i = 0, ilen = arcs.length; i < ilen; ++i) { + arcs[i]._options = me._resolveDataElementOptions(arcs[i], i); + me.updateElement(arcs[i], i, reset); + } + }, + + /** + * @private + */ + _updateRadius: function() { + var me = this; + var chart = me.chart; + var chartArea = chart.chartArea; + var opts = chart.options; + var minSize = Math.min(chartArea.right - chartArea.left, chartArea.bottom - chartArea.top); + + chart.outerRadius = Math.max(minSize / 2, 0); + chart.innerRadius = Math.max(opts.cutoutPercentage ? (chart.outerRadius / 100) * (opts.cutoutPercentage) : 1, 0); + chart.radiusLength = (chart.outerRadius - chart.innerRadius) / chart.getVisibleDatasetCount(); + + me.outerRadius = chart.outerRadius - (chart.radiusLength * me.index); + me.innerRadius = me.outerRadius - chart.radiusLength; + }, + + updateElement: function(arc, index, reset) { + var me = this; + var chart = me.chart; + var dataset = me.getDataset(); + var opts = chart.options; + var animationOpts = opts.animation; + var scale = chart.scale; + var labels = chart.data.labels; + + var centerX = scale.xCenter; + var centerY = scale.yCenter; + + // var negHalfPI = -0.5 * Math.PI; + var datasetStartAngle = opts.startAngle; + var distance = arc.hidden ? 0 : scale.getDistanceFromCenterForValue(dataset.data[index]); + var startAngle = me._starts[index]; + var endAngle = startAngle + (arc.hidden ? 0 : me._angles[index]); + + var resetRadius = animationOpts.animateScale ? 0 : scale.getDistanceFromCenterForValue(dataset.data[index]); + var options = arc._options || {}; + + helpers$1.extend(arc, { + // Utility + _datasetIndex: me.index, + _index: index, + _scale: scale, + + // Desired view properties + _model: { + backgroundColor: options.backgroundColor, + borderColor: options.borderColor, + borderWidth: options.borderWidth, + borderAlign: options.borderAlign, + x: centerX, + y: centerY, + innerRadius: 0, + outerRadius: reset ? resetRadius : distance, + startAngle: reset && animationOpts.animateRotate ? datasetStartAngle : startAngle, + endAngle: reset && animationOpts.animateRotate ? datasetStartAngle : endAngle, + label: helpers$1.valueAtIndexOrDefault(labels, index, labels[index]) + } + }); + + arc.pivot(); + }, + + countVisibleElements: function() { + var dataset = this.getDataset(); + var meta = this.getMeta(); + var count = 0; + + helpers$1.each(meta.data, function(element, index) { + if (!isNaN(dataset.data[index]) && !element.hidden) { + count++; + } + }); + + return count; + }, + + /** + * @protected + */ + setHoverStyle: function(arc) { + var model = arc._model; + var options = arc._options; + var getHoverColor = helpers$1.getHoverColor; + var valueOrDefault = helpers$1.valueOrDefault; + + arc.$previousStyle = { + backgroundColor: model.backgroundColor, + borderColor: model.borderColor, + borderWidth: model.borderWidth, + }; + + model.backgroundColor = valueOrDefault(options.hoverBackgroundColor, getHoverColor(options.backgroundColor)); + model.borderColor = valueOrDefault(options.hoverBorderColor, getHoverColor(options.borderColor)); + model.borderWidth = valueOrDefault(options.hoverBorderWidth, options.borderWidth); + }, + + /** + * @private + */ + _computeAngle: function(index) { + var me = this; + var count = this.getMeta().count; + var dataset = me.getDataset(); + var meta = me.getMeta(); + + if (isNaN(dataset.data[index]) || meta.data[index].hidden) { + return 0; + } + + // Scriptable options + var context = { + chart: me.chart, + dataIndex: index, + dataset: dataset, + datasetIndex: me.index + }; + + return resolve$3([ + me.chart.options.elements.arc.angle, + (2 * Math.PI) / count + ], context, index); + } +}); + +core_defaults._set('pie', helpers$1.clone(core_defaults.doughnut)); +core_defaults._set('pie', { + cutoutPercentage: 0 +}); + +// Pie charts are Doughnut chart with different defaults +var controller_pie = controller_doughnut; + +var valueOrDefault$7 = helpers$1.valueOrDefault; + +core_defaults._set('radar', { + spanGaps: false, + scale: { + type: 'radialLinear' + }, + elements: { + line: { + fill: 'start', + tension: 0 // no bezier in radar + } + } +}); + +var controller_radar = core_datasetController.extend({ + datasetElementType: elements.Line, + + dataElementType: elements.Point, + + linkScales: helpers$1.noop, + + /** + * @private + */ + _datasetElementOptions: [ + 'backgroundColor', + 'borderWidth', + 'borderColor', + 'borderCapStyle', + 'borderDash', + 'borderDashOffset', + 'borderJoinStyle', + 'fill' + ], + + /** + * @private + */ + _dataElementOptions: { + backgroundColor: 'pointBackgroundColor', + borderColor: 'pointBorderColor', + borderWidth: 'pointBorderWidth', + hitRadius: 'pointHitRadius', + hoverBackgroundColor: 'pointHoverBackgroundColor', + hoverBorderColor: 'pointHoverBorderColor', + hoverBorderWidth: 'pointHoverBorderWidth', + hoverRadius: 'pointHoverRadius', + pointStyle: 'pointStyle', + radius: 'pointRadius', + rotation: 'pointRotation' + }, + + /** + * @private + */ + _getIndexScaleId: function() { + return this.chart.scale.id; + }, + + /** + * @private + */ + _getValueScaleId: function() { + return this.chart.scale.id; + }, + + update: function(reset) { + var me = this; + var meta = me.getMeta(); + var line = meta.dataset; + var points = meta.data || []; + var scale = me.chart.scale; + var config = me._config; + var i, ilen; + + // Compatibility: If the properties are defined with only the old name, use those values + if (config.tension !== undefined && config.lineTension === undefined) { + config.lineTension = config.tension; + } + + // Utility + line._scale = scale; + line._datasetIndex = me.index; + // Data + line._children = points; + line._loop = true; + // Model + line._model = me._resolveDatasetElementOptions(line); + + line.pivot(); + + // Update Points + for (i = 0, ilen = points.length; i < ilen; ++i) { + me.updateElement(points[i], i, reset); + } + + // Update bezier control points + me.updateBezierControlPoints(); + + // Now pivot the point for animation + for (i = 0, ilen = points.length; i < ilen; ++i) { + points[i].pivot(); + } + }, + + updateElement: function(point, index, reset) { + var me = this; + var custom = point.custom || {}; + var dataset = me.getDataset(); + var scale = me.chart.scale; + var pointPosition = scale.getPointPositionForValue(index, dataset.data[index]); + var options = me._resolveDataElementOptions(point, index); + var lineModel = me.getMeta().dataset._model; + var x = reset ? scale.xCenter : pointPosition.x; + var y = reset ? scale.yCenter : pointPosition.y; + + // Utility + point._scale = scale; + point._options = options; + point._datasetIndex = me.index; + point._index = index; + + // Desired view properties + point._model = { + x: x, // value not used in dataset scale, but we want a consistent API between scales + y: y, + skip: custom.skip || isNaN(x) || isNaN(y), + // Appearance + radius: options.radius, + pointStyle: options.pointStyle, + rotation: options.rotation, + backgroundColor: options.backgroundColor, + borderColor: options.borderColor, + borderWidth: options.borderWidth, + tension: valueOrDefault$7(custom.tension, lineModel ? lineModel.tension : 0), + + // Tooltip + hitRadius: options.hitRadius + }; + }, + + /** + * @private + */ + _resolveDatasetElementOptions: function() { + var me = this; + var config = me._config; + var options = me.chart.options; + var values = core_datasetController.prototype._resolveDatasetElementOptions.apply(me, arguments); + + values.spanGaps = valueOrDefault$7(config.spanGaps, options.spanGaps); + values.tension = valueOrDefault$7(config.lineTension, options.elements.line.tension); + + return values; + }, + + updateBezierControlPoints: function() { + var me = this; + var meta = me.getMeta(); + var area = me.chart.chartArea; + var points = meta.data || []; + var i, ilen, model, controlPoints; + + // Only consider points that are drawn in case the spanGaps option is used + if (meta.dataset._model.spanGaps) { + points = points.filter(function(pt) { + return !pt._model.skip; + }); + } + + function capControlPoint(pt, min, max) { + return Math.max(Math.min(pt, max), min); + } + + for (i = 0, ilen = points.length; i < ilen; ++i) { + model = points[i]._model; + controlPoints = helpers$1.splineCurve( + helpers$1.previousItem(points, i, true)._model, + model, + helpers$1.nextItem(points, i, true)._model, + model.tension + ); + + // Prevent the bezier going outside of the bounds of the graph + model.controlPointPreviousX = capControlPoint(controlPoints.previous.x, area.left, area.right); + model.controlPointPreviousY = capControlPoint(controlPoints.previous.y, area.top, area.bottom); + model.controlPointNextX = capControlPoint(controlPoints.next.x, area.left, area.right); + model.controlPointNextY = capControlPoint(controlPoints.next.y, area.top, area.bottom); + } + }, + + setHoverStyle: function(point) { + var model = point._model; + var options = point._options; + var getHoverColor = helpers$1.getHoverColor; + + point.$previousStyle = { + backgroundColor: model.backgroundColor, + borderColor: model.borderColor, + borderWidth: model.borderWidth, + radius: model.radius + }; + + model.backgroundColor = valueOrDefault$7(options.hoverBackgroundColor, getHoverColor(options.backgroundColor)); + model.borderColor = valueOrDefault$7(options.hoverBorderColor, getHoverColor(options.borderColor)); + model.borderWidth = valueOrDefault$7(options.hoverBorderWidth, options.borderWidth); + model.radius = valueOrDefault$7(options.hoverRadius, options.radius); + } +}); + +core_defaults._set('scatter', { + hover: { + mode: 'single' + }, + + scales: { + xAxes: [{ + id: 'x-axis-1', // need an ID so datasets can reference the scale + type: 'linear', // scatter should not use a category axis + position: 'bottom' + }], + yAxes: [{ + id: 'y-axis-1', + type: 'linear', + position: 'left' + }] + }, + + tooltips: { + callbacks: { + title: function() { + return ''; // doesn't make sense for scatter since data are formatted as a point + }, + label: function(item) { + return '(' + item.xLabel + ', ' + item.yLabel + ')'; + } + } + } +}); + +core_defaults._set('global', { + datasets: { + scatter: { + showLine: false + } + } +}); + +// Scatter charts use line controllers +var controller_scatter = controller_line; + +// NOTE export a map in which the key represents the controller type, not +// the class, and so must be CamelCase in order to be correctly retrieved +// by the controller in core.controller.js (`controllers[meta.type]`). + +var controllers = { + bar: controller_bar, + bubble: controller_bubble, + doughnut: controller_doughnut, + horizontalBar: controller_horizontalBar, + line: controller_line, + polarArea: controller_polarArea, + pie: controller_pie, + radar: controller_radar, + scatter: controller_scatter +}; + +/** + * Helper function to get relative position for an event + * @param {Event|IEvent} event - The event to get the position for + * @param {Chart} chart - The chart + * @returns {object} the event position + */ +function getRelativePosition(e, chart) { + if (e.native) { + return { + x: e.x, + y: e.y + }; + } + + return helpers$1.getRelativePosition(e, chart); +} + +/** + * Helper function to traverse all of the visible elements in the chart + * @param {Chart} chart - the chart + * @param {function} handler - the callback to execute for each visible item + */ +function parseVisibleItems(chart, handler) { + var metasets = chart._getSortedVisibleDatasetMetas(); + var metadata, i, j, ilen, jlen, element; + + for (i = 0, ilen = metasets.length; i < ilen; ++i) { + metadata = metasets[i].data; + for (j = 0, jlen = metadata.length; j < jlen; ++j) { + element = metadata[j]; + if (!element._view.skip) { + handler(element); + } + } + } +} + +/** + * Helper function to get the items that intersect the event position + * @param {ChartElement[]} items - elements to filter + * @param {object} position - the point to be nearest to + * @return {ChartElement[]} the nearest items + */ +function getIntersectItems(chart, position) { + var elements = []; + + parseVisibleItems(chart, function(element) { + if (element.inRange(position.x, position.y)) { + elements.push(element); + } + }); + + return elements; +} + +/** + * Helper function to get the items nearest to the event position considering all visible items in teh chart + * @param {Chart} chart - the chart to look at elements from + * @param {object} position - the point to be nearest to + * @param {boolean} intersect - if true, only consider items that intersect the position + * @param {function} distanceMetric - function to provide the distance between points + * @return {ChartElement[]} the nearest items + */ +function getNearestItems(chart, position, intersect, distanceMetric) { + var minDistance = Number.POSITIVE_INFINITY; + var nearestItems = []; + + parseVisibleItems(chart, function(element) { + if (intersect && !element.inRange(position.x, position.y)) { + return; + } + + var center = element.getCenterPoint(); + var distance = distanceMetric(position, center); + if (distance < minDistance) { + nearestItems = [element]; + minDistance = distance; + } else if (distance === minDistance) { + // Can have multiple items at the same distance in which case we sort by size + nearestItems.push(element); + } + }); + + return nearestItems; +} + +/** + * Get a distance metric function for two points based on the + * axis mode setting + * @param {string} axis - the axis mode. x|y|xy + */ +function getDistanceMetricForAxis(axis) { + var useX = axis.indexOf('x') !== -1; + var useY = axis.indexOf('y') !== -1; + + return function(pt1, pt2) { + var deltaX = useX ? Math.abs(pt1.x - pt2.x) : 0; + var deltaY = useY ? Math.abs(pt1.y - pt2.y) : 0; + return Math.sqrt(Math.pow(deltaX, 2) + Math.pow(deltaY, 2)); + }; +} + +function indexMode(chart, e, options) { + var position = getRelativePosition(e, chart); + // Default axis for index mode is 'x' to match old behaviour + options.axis = options.axis || 'x'; + var distanceMetric = getDistanceMetricForAxis(options.axis); + var items = options.intersect ? getIntersectItems(chart, position) : getNearestItems(chart, position, false, distanceMetric); + var elements = []; + + if (!items.length) { + return []; + } + + chart._getSortedVisibleDatasetMetas().forEach(function(meta) { + var element = meta.data[items[0]._index]; + + // don't count items that are skipped (null data) + if (element && !element._view.skip) { + elements.push(element); + } + }); + + return elements; +} + +/** + * @interface IInteractionOptions + */ +/** + * If true, only consider items that intersect the point + * @name IInterfaceOptions#boolean + * @type Boolean + */ + +/** + * Contains interaction related functions + * @namespace Chart.Interaction + */ +var core_interaction = { + // Helper function for different modes + modes: { + single: function(chart, e) { + var position = getRelativePosition(e, chart); + var elements = []; + + parseVisibleItems(chart, function(element) { + if (element.inRange(position.x, position.y)) { + elements.push(element); + return elements; + } + }); + + return elements.slice(0, 1); + }, + + /** + * @function Chart.Interaction.modes.label + * @deprecated since version 2.4.0 + * @todo remove at version 3 + * @private + */ + label: indexMode, + + /** + * Returns items at the same index. If the options.intersect parameter is true, we only return items if we intersect something + * If the options.intersect mode is false, we find the nearest item and return the items at the same index as that item + * @function Chart.Interaction.modes.index + * @since v2.4.0 + * @param {Chart} chart - the chart we are returning items from + * @param {Event} e - the event we are find things at + * @param {IInteractionOptions} options - options to use during interaction + * @return {Chart.Element[]} Array of elements that are under the point. If none are found, an empty array is returned + */ + index: indexMode, + + /** + * Returns items in the same dataset. If the options.intersect parameter is true, we only return items if we intersect something + * If the options.intersect is false, we find the nearest item and return the items in that dataset + * @function Chart.Interaction.modes.dataset + * @param {Chart} chart - the chart we are returning items from + * @param {Event} e - the event we are find things at + * @param {IInteractionOptions} options - options to use during interaction + * @return {Chart.Element[]} Array of elements that are under the point. If none are found, an empty array is returned + */ + dataset: function(chart, e, options) { + var position = getRelativePosition(e, chart); + options.axis = options.axis || 'xy'; + var distanceMetric = getDistanceMetricForAxis(options.axis); + var items = options.intersect ? getIntersectItems(chart, position) : getNearestItems(chart, position, false, distanceMetric); + + if (items.length > 0) { + items = chart.getDatasetMeta(items[0]._datasetIndex).data; + } + + return items; + }, + + /** + * @function Chart.Interaction.modes.x-axis + * @deprecated since version 2.4.0. Use index mode and intersect == true + * @todo remove at version 3 + * @private + */ + 'x-axis': function(chart, e) { + return indexMode(chart, e, {intersect: false}); + }, + + /** + * Point mode returns all elements that hit test based on the event position + * of the event + * @function Chart.Interaction.modes.intersect + * @param {Chart} chart - the chart we are returning items from + * @param {Event} e - the event we are find things at + * @return {Chart.Element[]} Array of elements that are under the point. If none are found, an empty array is returned + */ + point: function(chart, e) { + var position = getRelativePosition(e, chart); + return getIntersectItems(chart, position); + }, + + /** + * nearest mode returns the element closest to the point + * @function Chart.Interaction.modes.intersect + * @param {Chart} chart - the chart we are returning items from + * @param {Event} e - the event we are find things at + * @param {IInteractionOptions} options - options to use + * @return {Chart.Element[]} Array of elements that are under the point. If none are found, an empty array is returned + */ + nearest: function(chart, e, options) { + var position = getRelativePosition(e, chart); + options.axis = options.axis || 'xy'; + var distanceMetric = getDistanceMetricForAxis(options.axis); + return getNearestItems(chart, position, options.intersect, distanceMetric); + }, + + /** + * x mode returns the elements that hit-test at the current x coordinate + * @function Chart.Interaction.modes.x + * @param {Chart} chart - the chart we are returning items from + * @param {Event} e - the event we are find things at + * @param {IInteractionOptions} options - options to use + * @return {Chart.Element[]} Array of elements that are under the point. If none are found, an empty array is returned + */ + x: function(chart, e, options) { + var position = getRelativePosition(e, chart); + var items = []; + var intersectsItem = false; + + parseVisibleItems(chart, function(element) { + if (element.inXRange(position.x)) { + items.push(element); + } + + if (element.inRange(position.x, position.y)) { + intersectsItem = true; + } + }); + + // If we want to trigger on an intersect and we don't have any items + // that intersect the position, return nothing + if (options.intersect && !intersectsItem) { + items = []; + } + return items; + }, + + /** + * y mode returns the elements that hit-test at the current y coordinate + * @function Chart.Interaction.modes.y + * @param {Chart} chart - the chart we are returning items from + * @param {Event} e - the event we are find things at + * @param {IInteractionOptions} options - options to use + * @return {Chart.Element[]} Array of elements that are under the point. If none are found, an empty array is returned + */ + y: function(chart, e, options) { + var position = getRelativePosition(e, chart); + var items = []; + var intersectsItem = false; + + parseVisibleItems(chart, function(element) { + if (element.inYRange(position.y)) { + items.push(element); + } + + if (element.inRange(position.x, position.y)) { + intersectsItem = true; + } + }); + + // If we want to trigger on an intersect and we don't have any items + // that intersect the position, return nothing + if (options.intersect && !intersectsItem) { + items = []; + } + return items; + } + } +}; + +var extend = helpers$1.extend; + +function filterByPosition(array, position) { + return helpers$1.where(array, function(v) { + return v.pos === position; + }); +} + +function sortByWeight(array, reverse) { + return array.sort(function(a, b) { + var v0 = reverse ? b : a; + var v1 = reverse ? a : b; + return v0.weight === v1.weight ? + v0.index - v1.index : + v0.weight - v1.weight; + }); +} + +function wrapBoxes(boxes) { + var layoutBoxes = []; + var i, ilen, box; + + for (i = 0, ilen = (boxes || []).length; i < ilen; ++i) { + box = boxes[i]; + layoutBoxes.push({ + index: i, + box: box, + pos: box.position, + horizontal: box.isHorizontal(), + weight: box.weight + }); + } + return layoutBoxes; +} + +function setLayoutDims(layouts, params) { + var i, ilen, layout; + for (i = 0, ilen = layouts.length; i < ilen; ++i) { + layout = layouts[i]; + // store width used instead of chartArea.w in fitBoxes + layout.width = layout.horizontal + ? layout.box.fullWidth && params.availableWidth + : params.vBoxMaxWidth; + // store height used instead of chartArea.h in fitBoxes + layout.height = layout.horizontal && params.hBoxMaxHeight; + } +} + +function buildLayoutBoxes(boxes) { + var layoutBoxes = wrapBoxes(boxes); + var left = sortByWeight(filterByPosition(layoutBoxes, 'left'), true); + var right = sortByWeight(filterByPosition(layoutBoxes, 'right')); + var top = sortByWeight(filterByPosition(layoutBoxes, 'top'), true); + var bottom = sortByWeight(filterByPosition(layoutBoxes, 'bottom')); + + return { + leftAndTop: left.concat(top), + rightAndBottom: right.concat(bottom), + chartArea: filterByPosition(layoutBoxes, 'chartArea'), + vertical: left.concat(right), + horizontal: top.concat(bottom) + }; +} + +function getCombinedMax(maxPadding, chartArea, a, b) { + return Math.max(maxPadding[a], chartArea[a]) + Math.max(maxPadding[b], chartArea[b]); +} + +function updateDims(chartArea, params, layout) { + var box = layout.box; + var maxPadding = chartArea.maxPadding; + var newWidth, newHeight; + + if (layout.size) { + // this layout was already counted for, lets first reduce old size + chartArea[layout.pos] -= layout.size; + } + layout.size = layout.horizontal ? box.height : box.width; + chartArea[layout.pos] += layout.size; + + if (box.getPadding) { + var boxPadding = box.getPadding(); + maxPadding.top = Math.max(maxPadding.top, boxPadding.top); + maxPadding.left = Math.max(maxPadding.left, boxPadding.left); + maxPadding.bottom = Math.max(maxPadding.bottom, boxPadding.bottom); + maxPadding.right = Math.max(maxPadding.right, boxPadding.right); + } + + newWidth = params.outerWidth - getCombinedMax(maxPadding, chartArea, 'left', 'right'); + newHeight = params.outerHeight - getCombinedMax(maxPadding, chartArea, 'top', 'bottom'); + + if (newWidth !== chartArea.w || newHeight !== chartArea.h) { + chartArea.w = newWidth; + chartArea.h = newHeight; + + // return true if chart area changed in layout's direction + var sizes = layout.horizontal ? [newWidth, chartArea.w] : [newHeight, chartArea.h]; + return sizes[0] !== sizes[1] && (!isNaN(sizes[0]) || !isNaN(sizes[1])); + } +} + +function handleMaxPadding(chartArea) { + var maxPadding = chartArea.maxPadding; + + function updatePos(pos) { + var change = Math.max(maxPadding[pos] - chartArea[pos], 0); + chartArea[pos] += change; + return change; + } + chartArea.y += updatePos('top'); + chartArea.x += updatePos('left'); + updatePos('right'); + updatePos('bottom'); +} + +function getMargins(horizontal, chartArea) { + var maxPadding = chartArea.maxPadding; + + function marginForPositions(positions) { + var margin = {left: 0, top: 0, right: 0, bottom: 0}; + positions.forEach(function(pos) { + margin[pos] = Math.max(chartArea[pos], maxPadding[pos]); + }); + return margin; + } + + return horizontal + ? marginForPositions(['left', 'right']) + : marginForPositions(['top', 'bottom']); +} + +function fitBoxes(boxes, chartArea, params) { + var refitBoxes = []; + var i, ilen, layout, box, refit, changed; + + for (i = 0, ilen = boxes.length; i < ilen; ++i) { + layout = boxes[i]; + box = layout.box; + + box.update( + layout.width || chartArea.w, + layout.height || chartArea.h, + getMargins(layout.horizontal, chartArea) + ); + if (updateDims(chartArea, params, layout)) { + changed = true; + if (refitBoxes.length) { + // Dimensions changed and there were non full width boxes before this + // -> we have to refit those + refit = true; + } + } + if (!box.fullWidth) { // fullWidth boxes don't need to be re-fitted in any case + refitBoxes.push(layout); + } + } + + return refit ? fitBoxes(refitBoxes, chartArea, params) || changed : changed; +} + +function placeBoxes(boxes, chartArea, params) { + var userPadding = params.padding; + var x = chartArea.x; + var y = chartArea.y; + var i, ilen, layout, box; + + for (i = 0, ilen = boxes.length; i < ilen; ++i) { + layout = boxes[i]; + box = layout.box; + if (layout.horizontal) { + box.left = box.fullWidth ? userPadding.left : chartArea.left; + box.right = box.fullWidth ? params.outerWidth - userPadding.right : chartArea.left + chartArea.w; + box.top = y; + box.bottom = y + box.height; + box.width = box.right - box.left; + y = box.bottom; + } else { + box.left = x; + box.right = x + box.width; + box.top = chartArea.top; + box.bottom = chartArea.top + chartArea.h; + box.height = box.bottom - box.top; + x = box.right; + } + } + + chartArea.x = x; + chartArea.y = y; +} + +core_defaults._set('global', { + layout: { + padding: { + top: 0, + right: 0, + bottom: 0, + left: 0 + } + } +}); + +/** + * @interface ILayoutItem + * @prop {string} position - The position of the item in the chart layout. Possible values are + * 'left', 'top', 'right', 'bottom', and 'chartArea' + * @prop {number} weight - The weight used to sort the item. Higher weights are further away from the chart area + * @prop {boolean} fullWidth - if true, and the item is horizontal, then push vertical boxes down + * @prop {function} isHorizontal - returns true if the layout item is horizontal (ie. top or bottom) + * @prop {function} update - Takes two parameters: width and height. Returns size of item + * @prop {function} getPadding - Returns an object with padding on the edges + * @prop {number} width - Width of item. Must be valid after update() + * @prop {number} height - Height of item. Must be valid after update() + * @prop {number} left - Left edge of the item. Set by layout system and cannot be used in update + * @prop {number} top - Top edge of the item. Set by layout system and cannot be used in update + * @prop {number} right - Right edge of the item. Set by layout system and cannot be used in update + * @prop {number} bottom - Bottom edge of the item. Set by layout system and cannot be used in update + */ + +// The layout service is very self explanatory. It's responsible for the layout within a chart. +// Scales, Legends and Plugins all rely on the layout service and can easily register to be placed anywhere they need +// It is this service's responsibility of carrying out that layout. +var core_layouts = { + defaults: {}, + + /** + * Register a box to a chart. + * A box is simply a reference to an object that requires layout. eg. Scales, Legend, Title. + * @param {Chart} chart - the chart to use + * @param {ILayoutItem} item - the item to add to be layed out + */ + addBox: function(chart, item) { + if (!chart.boxes) { + chart.boxes = []; + } + + // initialize item with default values + item.fullWidth = item.fullWidth || false; + item.position = item.position || 'top'; + item.weight = item.weight || 0; + item._layers = item._layers || function() { + return [{ + z: 0, + draw: function() { + item.draw.apply(item, arguments); + } + }]; + }; + + chart.boxes.push(item); + }, + + /** + * Remove a layoutItem from a chart + * @param {Chart} chart - the chart to remove the box from + * @param {ILayoutItem} layoutItem - the item to remove from the layout + */ + removeBox: function(chart, layoutItem) { + var index = chart.boxes ? chart.boxes.indexOf(layoutItem) : -1; + if (index !== -1) { + chart.boxes.splice(index, 1); + } + }, + + /** + * Sets (or updates) options on the given `item`. + * @param {Chart} chart - the chart in which the item lives (or will be added to) + * @param {ILayoutItem} item - the item to configure with the given options + * @param {object} options - the new item options. + */ + configure: function(chart, item, options) { + var props = ['fullWidth', 'position', 'weight']; + var ilen = props.length; + var i = 0; + var prop; + + for (; i < ilen; ++i) { + prop = props[i]; + if (options.hasOwnProperty(prop)) { + item[prop] = options[prop]; + } + } + }, + + /** + * Fits boxes of the given chart into the given size by having each box measure itself + * then running a fitting algorithm + * @param {Chart} chart - the chart + * @param {number} width - the width to fit into + * @param {number} height - the height to fit into + */ + update: function(chart, width, height) { + if (!chart) { + return; + } + + var layoutOptions = chart.options.layout || {}; + var padding = helpers$1.options.toPadding(layoutOptions.padding); + + var availableWidth = width - padding.width; + var availableHeight = height - padding.height; + var boxes = buildLayoutBoxes(chart.boxes); + var verticalBoxes = boxes.vertical; + var horizontalBoxes = boxes.horizontal; + + // Essentially we now have any number of boxes on each of the 4 sides. + // Our canvas looks like the following. + // The areas L1 and L2 are the left axes. R1 is the right axis, T1 is the top axis and + // B1 is the bottom axis + // There are also 4 quadrant-like locations (left to right instead of clockwise) reserved for chart overlays + // These locations are single-box locations only, when trying to register a chartArea location that is already taken, + // an error will be thrown. + // + // |----------------------------------------------------| + // | T1 (Full Width) | + // |----------------------------------------------------| + // | | | T2 | | + // | |----|-------------------------------------|----| + // | | | C1 | | C2 | | + // | | |----| |----| | + // | | | | | + // | L1 | L2 | ChartArea (C0) | R1 | + // | | | | | + // | | |----| |----| | + // | | | C3 | | C4 | | + // | |----|-------------------------------------|----| + // | | | B1 | | + // |----------------------------------------------------| + // | B2 (Full Width) | + // |----------------------------------------------------| + // + + var params = Object.freeze({ + outerWidth: width, + outerHeight: height, + padding: padding, + availableWidth: availableWidth, + vBoxMaxWidth: availableWidth / 2 / verticalBoxes.length, + hBoxMaxHeight: availableHeight / 2 + }); + var chartArea = extend({ + maxPadding: extend({}, padding), + w: availableWidth, + h: availableHeight, + x: padding.left, + y: padding.top + }, padding); + + setLayoutDims(verticalBoxes.concat(horizontalBoxes), params); + + // First fit vertical boxes + fitBoxes(verticalBoxes, chartArea, params); + + // Then fit horizontal boxes + if (fitBoxes(horizontalBoxes, chartArea, params)) { + // if the area changed, re-fit vertical boxes + fitBoxes(verticalBoxes, chartArea, params); + } + + handleMaxPadding(chartArea); + + // Finally place the boxes to correct coordinates + placeBoxes(boxes.leftAndTop, chartArea, params); + + // Move to opposite side of chart + chartArea.x += chartArea.w; + chartArea.y += chartArea.h; + + placeBoxes(boxes.rightAndBottom, chartArea, params); + + chart.chartArea = { + left: chartArea.left, + top: chartArea.top, + right: chartArea.left + chartArea.w, + bottom: chartArea.top + chartArea.h + }; + + // Finally update boxes in chartArea (radial scale for example) + helpers$1.each(boxes.chartArea, function(layout) { + var box = layout.box; + extend(box, chart.chartArea); + box.update(chartArea.w, chartArea.h); + }); + } +}; + +/** + * Platform fallback implementation (minimal). + * @see https://github.com/chartjs/Chart.js/pull/4591#issuecomment-319575939 + */ + +var platform_basic = { + acquireContext: function(item) { + if (item && item.canvas) { + // Support for any object associated to a canvas (including a context2d) + item = item.canvas; + } + + return item && item.getContext('2d') || null; + } +}; + +var platform_dom = "/*\r\n * DOM element rendering detection\r\n * https://davidwalsh.name/detect-node-insertion\r\n */\r\n@keyframes chartjs-render-animation {\r\n\tfrom { opacity: 0.99; }\r\n\tto { opacity: 1; }\r\n}\r\n\r\n.chartjs-render-monitor {\r\n\tanimation: chartjs-render-animation 0.001s;\r\n}\r\n\r\n/*\r\n * DOM element resizing detection\r\n * https://github.com/marcj/css-element-queries\r\n */\r\n.chartjs-size-monitor,\r\n.chartjs-size-monitor-expand,\r\n.chartjs-size-monitor-shrink {\r\n\tposition: absolute;\r\n\tdirection: ltr;\r\n\tleft: 0;\r\n\ttop: 0;\r\n\tright: 0;\r\n\tbottom: 0;\r\n\toverflow: hidden;\r\n\tpointer-events: none;\r\n\tvisibility: hidden;\r\n\tz-index: -1;\r\n}\r\n\r\n.chartjs-size-monitor-expand > div {\r\n\tposition: absolute;\r\n\twidth: 1000000px;\r\n\theight: 1000000px;\r\n\tleft: 0;\r\n\ttop: 0;\r\n}\r\n\r\n.chartjs-size-monitor-shrink > div {\r\n\tposition: absolute;\r\n\twidth: 200%;\r\n\theight: 200%;\r\n\tleft: 0;\r\n\ttop: 0;\r\n}\r\n"; + +var platform_dom$1 = /*#__PURE__*/Object.freeze({ +__proto__: null, +'default': platform_dom +}); + +var stylesheet = getCjsExportFromNamespace(platform_dom$1); + +var EXPANDO_KEY = '$chartjs'; +var CSS_PREFIX = 'chartjs-'; +var CSS_SIZE_MONITOR = CSS_PREFIX + 'size-monitor'; +var CSS_RENDER_MONITOR = CSS_PREFIX + 'render-monitor'; +var CSS_RENDER_ANIMATION = CSS_PREFIX + 'render-animation'; +var ANIMATION_START_EVENTS = ['animationstart', 'webkitAnimationStart']; + +/** + * DOM event types -> Chart.js event types. + * Note: only events with different types are mapped. + * @see https://developer.mozilla.org/en-US/docs/Web/Events + */ +var EVENT_TYPES = { + touchstart: 'mousedown', + touchmove: 'mousemove', + touchend: 'mouseup', + pointerenter: 'mouseenter', + pointerdown: 'mousedown', + pointermove: 'mousemove', + pointerup: 'mouseup', + pointerleave: 'mouseout', + pointerout: 'mouseout' +}; + +/** + * The "used" size is the final value of a dimension property after all calculations have + * been performed. This method uses the computed style of `element` but returns undefined + * if the computed style is not expressed in pixels. That can happen in some cases where + * `element` has a size relative to its parent and this last one is not yet displayed, + * for example because of `display: none` on a parent node. + * @see https://developer.mozilla.org/en-US/docs/Web/CSS/used_value + * @returns {number} Size in pixels or undefined if unknown. + */ +function readUsedSize(element, property) { + var value = helpers$1.getStyle(element, property); + var matches = value && value.match(/^(\d+)(\.\d+)?px$/); + return matches ? Number(matches[1]) : undefined; +} + +/** + * Initializes the canvas style and render size without modifying the canvas display size, + * since responsiveness is handled by the controller.resize() method. The config is used + * to determine the aspect ratio to apply in case no explicit height has been specified. + */ +function initCanvas(canvas, config) { + var style = canvas.style; + + // NOTE(SB) canvas.getAttribute('width') !== canvas.width: in the first case it + // returns null or '' if no explicit value has been set to the canvas attribute. + var renderHeight = canvas.getAttribute('height'); + var renderWidth = canvas.getAttribute('width'); + + // Chart.js modifies some canvas values that we want to restore on destroy + canvas[EXPANDO_KEY] = { + initial: { + height: renderHeight, + width: renderWidth, + style: { + display: style.display, + height: style.height, + width: style.width + } + } + }; + + // Force canvas to display as block to avoid extra space caused by inline + // elements, which would interfere with the responsive resize process. + // https://github.com/chartjs/Chart.js/issues/2538 + style.display = style.display || 'block'; + + if (renderWidth === null || renderWidth === '') { + var displayWidth = readUsedSize(canvas, 'width'); + if (displayWidth !== undefined) { + canvas.width = displayWidth; + } + } + + if (renderHeight === null || renderHeight === '') { + if (canvas.style.height === '') { + // If no explicit render height and style height, let's apply the aspect ratio, + // which one can be specified by the user but also by charts as default option + // (i.e. options.aspectRatio). If not specified, use canvas aspect ratio of 2. + canvas.height = canvas.width / (config.options.aspectRatio || 2); + } else { + var displayHeight = readUsedSize(canvas, 'height'); + if (displayWidth !== undefined) { + canvas.height = displayHeight; + } + } + } + + return canvas; +} + +/** + * Detects support for options object argument in addEventListener. + * https://developer.mozilla.org/en-US/docs/Web/API/EventTarget/addEventListener#Safely_detecting_option_support + * @private + */ +var supportsEventListenerOptions = (function() { + var supports = false; + try { + var options = Object.defineProperty({}, 'passive', { + // eslint-disable-next-line getter-return + get: function() { + supports = true; + } + }); + window.addEventListener('e', null, options); + } catch (e) { + // continue regardless of error + } + return supports; +}()); + +// Default passive to true as expected by Chrome for 'touchstart' and 'touchend' events. +// https://github.com/chartjs/Chart.js/issues/4287 +var eventListenerOptions = supportsEventListenerOptions ? {passive: true} : false; + +function addListener(node, type, listener) { + node.addEventListener(type, listener, eventListenerOptions); +} + +function removeListener(node, type, listener) { + node.removeEventListener(type, listener, eventListenerOptions); +} + +function createEvent(type, chart, x, y, nativeEvent) { + return { + type: type, + chart: chart, + native: nativeEvent || null, + x: x !== undefined ? x : null, + y: y !== undefined ? y : null, + }; +} + +function fromNativeEvent(event, chart) { + var type = EVENT_TYPES[event.type] || event.type; + var pos = helpers$1.getRelativePosition(event, chart); + return createEvent(type, chart, pos.x, pos.y, event); +} + +function throttled(fn, thisArg) { + var ticking = false; + var args = []; + + return function() { + args = Array.prototype.slice.call(arguments); + thisArg = thisArg || this; + + if (!ticking) { + ticking = true; + helpers$1.requestAnimFrame.call(window, function() { + ticking = false; + fn.apply(thisArg, args); + }); + } + }; +} + +function createDiv(cls) { + var el = document.createElement('div'); + el.className = cls || ''; + return el; +} + +// Implementation based on https://github.com/marcj/css-element-queries +function createResizer(handler) { + var maxSize = 1000000; + + // NOTE(SB) Don't use innerHTML because it could be considered unsafe. + // https://github.com/chartjs/Chart.js/issues/5902 + var resizer = createDiv(CSS_SIZE_MONITOR); + var expand = createDiv(CSS_SIZE_MONITOR + '-expand'); + var shrink = createDiv(CSS_SIZE_MONITOR + '-shrink'); + + expand.appendChild(createDiv()); + shrink.appendChild(createDiv()); + + resizer.appendChild(expand); + resizer.appendChild(shrink); + resizer._reset = function() { + expand.scrollLeft = maxSize; + expand.scrollTop = maxSize; + shrink.scrollLeft = maxSize; + shrink.scrollTop = maxSize; + }; + + var onScroll = function() { + resizer._reset(); + handler(); + }; + + addListener(expand, 'scroll', onScroll.bind(expand, 'expand')); + addListener(shrink, 'scroll', onScroll.bind(shrink, 'shrink')); + + return resizer; +} + +// https://davidwalsh.name/detect-node-insertion +function watchForRender(node, handler) { + var expando = node[EXPANDO_KEY] || (node[EXPANDO_KEY] = {}); + var proxy = expando.renderProxy = function(e) { + if (e.animationName === CSS_RENDER_ANIMATION) { + handler(); + } + }; + + helpers$1.each(ANIMATION_START_EVENTS, function(type) { + addListener(node, type, proxy); + }); + + // #4737: Chrome might skip the CSS animation when the CSS_RENDER_MONITOR class + // is removed then added back immediately (same animation frame?). Accessing the + // `offsetParent` property will force a reflow and re-evaluate the CSS animation. + // https://gist.github.com/paulirish/5d52fb081b3570c81e3a#box-metrics + // https://github.com/chartjs/Chart.js/issues/4737 + expando.reflow = !!node.offsetParent; + + node.classList.add(CSS_RENDER_MONITOR); +} + +function unwatchForRender(node) { + var expando = node[EXPANDO_KEY] || {}; + var proxy = expando.renderProxy; + + if (proxy) { + helpers$1.each(ANIMATION_START_EVENTS, function(type) { + removeListener(node, type, proxy); + }); + + delete expando.renderProxy; + } + + node.classList.remove(CSS_RENDER_MONITOR); +} + +function addResizeListener(node, listener, chart) { + var expando = node[EXPANDO_KEY] || (node[EXPANDO_KEY] = {}); + + // Let's keep track of this added resizer and thus avoid DOM query when removing it. + var resizer = expando.resizer = createResizer(throttled(function() { + if (expando.resizer) { + var container = chart.options.maintainAspectRatio && node.parentNode; + var w = container ? container.clientWidth : 0; + listener(createEvent('resize', chart)); + if (container && container.clientWidth < w && chart.canvas) { + // If the container size shrank during chart resize, let's assume + // scrollbar appeared. So we resize again with the scrollbar visible - + // effectively making chart smaller and the scrollbar hidden again. + // Because we are inside `throttled`, and currently `ticking`, scroll + // events are ignored during this whole 2 resize process. + // If we assumed wrong and something else happened, we are resizing + // twice in a frame (potential performance issue) + listener(createEvent('resize', chart)); + } + } + })); + + // The resizer needs to be attached to the node parent, so we first need to be + // sure that `node` is attached to the DOM before injecting the resizer element. + watchForRender(node, function() { + if (expando.resizer) { + var container = node.parentNode; + if (container && container !== resizer.parentNode) { + container.insertBefore(resizer, container.firstChild); + } + + // The container size might have changed, let's reset the resizer state. + resizer._reset(); + } + }); +} + +function removeResizeListener(node) { + var expando = node[EXPANDO_KEY] || {}; + var resizer = expando.resizer; + + delete expando.resizer; + unwatchForRender(node); + + if (resizer && resizer.parentNode) { + resizer.parentNode.removeChild(resizer); + } +} + +/** + * Injects CSS styles inline if the styles are not already present. + * @param {HTMLDocument|ShadowRoot} rootNode - the node to contain the