From ce236712d228c828a00cedaa3569ba29f902ed18 Mon Sep 17 00:00:00 2001 From: Ylian Saint-Hilaire Date: Wed, 26 Sep 2018 14:58:55 -0700 Subject: [PATCH] Improved server UI, new Auto-Remote agent feature. --- db.js | 21 + meshagent.js | 8 +- meshcentral.js | 9 +- meshuser.js | 42 +- package.json | 2 +- public/images/leftbar-62.png | Bin 26810 -> 33234 bytes public/images/leftbar-90.png | Bin 44120 -> 56718 bytes public/images/server-200.png | Bin 0 -> 51471 bytes public/scripts/charts.js | 18923 +++++++++++++++++++++++++++++++++ public/styles/style.css | 8 + views/default-min.handlebars | 2 +- views/default.handlebars | 152 +- webserver.js | 16 +- 13 files changed, 19154 insertions(+), 29 deletions(-) create mode 100644 public/images/server-200.png create mode 100644 public/scripts/charts.js diff --git a/db.js b/db.js index 5922cb49..55a4c807 100644 --- a/db.js +++ b/db.js @@ -121,6 +121,27 @@ module.exports.CreateDB = function (parent) { obj.getLocalAmtNodes = function (func) { obj.file.find({ type: 'node', host: { $exists: true, $ne: null }, intelamt: { $exists: true } }, func); }; obj.getAmtUuidNode = function (meshid, uuid, func) { obj.file.find({ type: 'node', meshid: meshid, 'intelamt.uuid': uuid }, func); }; + // Get the number of records in the database for various types, this is the slow NeDB way. TODO: MongoDB can use group() to do this faster. + obj.getStats = function (func) { + obj.file.count({ type: 'node' }, function (err, nodeCount) { + obj.file.count({ type: 'mesh' }, function (err, meshCount) { + obj.file.count({ type: 'power' }, function (err, powerCount) { + obj.file.count({ type: 'user' }, function (err, userCount) { + obj.file.count({ type: 'ifinfo' }, function (err, nodeInterfaceCount) { + obj.file.count({ type: 'note' }, function (err, noteCount) { + obj.file.count({ type: 'lastconnect' }, function (err, nodeLastConnectCount) { + obj.file.count({ }, function (err, totalCount) { + func({ nodes: nodeCount, meshes: meshCount, powerEvents: powerCount, users: userCount, nodeInterfaces: nodeInterfaceCount, notes: noteCount, connectEvent: nodeLastConnectCount, total: totalCount }); + }); + }); + }); + }); + }); + }); + }); + }); + } + // This is used to rate limit a number of operation per day. Returns a startValue each new days, but you can substract it and save the value in the db. obj.getValueOfTheDay = function (id, startValue, func) { obj.Get(id, function (err, docs) { var date = new Date(), t = date.toLocaleDateString(); if (docs.length == 1) { var r = docs[0]; if (r.day == t) { func({ _id: id, value: r.value, day: t }); return; } } func({ _id: id, value: startValue, day: t }); }); }; diff --git a/meshagent.js b/meshagent.js index 8074926e..702e3e11 100644 --- a/meshagent.js +++ b/meshagent.js @@ -56,10 +56,14 @@ module.exports.CreateMeshAgent = function (parent, db, ws, req, args, domain) { delete obj.parent.wsagents[obj.dbNodeKey]; obj.parent.parent.ClearConnectivityState(obj.dbMeshKey, obj.dbNodeKey, 1); } + + // Get the current mesh + var mesh = obj.parent.meshes[obj.dbMeshKey]; + // Other clean up may be needed here if (obj.unauth) { delete obj.unauth; } if (obj.agentUpdate != null) { obj.fs.close(obj.agentUpdate.fd); obj.agentUpdate = null; } - if ((obj.agentInfo) && (obj.agentInfo.capabilities) && (obj.agentInfo.capabilities & 0x20)) { // This is a temporary agent, remote it + if (((obj.agentInfo) && (obj.agentInfo.capabilities) && (obj.agentInfo.capabilities & 0x20)) || ((mesh) && (mesh.flags) && (mesh.flags & 1))) { // This is a temporary agent, remote it // Delete this node including network interface information and events obj.db.Remove(obj.dbNodeKey); // Remove node with that id obj.db.Remove('if' + obj.dbNodeKey); // Remove interface information @@ -597,7 +601,7 @@ module.exports.CreateMeshAgent = function (parent, db, ws, req, args, domain) { // Check if anything changes if (command.name && (command.name != device.name)) { change = 1; device.name = command.name; changes.push('name'); } - if (device.agent.core != command.value) { if ((command.value == null) && (device.agent.core != null)) { delete device.agent.core; } else { device.agent.core = command.value; } change = 1; changes.push('agent core'); } + if ((command.caps != null) && (device.agent.core != command.value)) { if ((command.value == null) && (device.agent.core != null)) { delete device.agent.core; } else { device.agent.core = command.value; } change = 1; changes.push('agent core'); } if ((command.caps != null) && ((device.agent.caps & 0xFFFFFFE7) != (command.caps & 0xFFFFFFE7))) { device.agent.caps = ((device.agent.caps & 24) + (command.caps & 0xFFFFFFE7)); change = 1; changes.push('agent capabilities'); } // Allow Javascript on the agent to change all capabilities except console and javascript support if ((command.osdesc != null) && (device.osdesc != command.osdesc)) { device.osdesc = command.osdesc; change = 1; changes.push('os desc'); } if (command.intelamt) { diff --git a/meshcentral.js b/meshcentral.js index c7d26d72..ddd46245 100644 --- a/meshcentral.js +++ b/meshcentral.js @@ -57,7 +57,7 @@ function CreateMeshCentralServer(config, args) { obj.currentVer = null; obj.serverKey = new Buffer(obj.crypto.randomBytes(32), 'binary'); obj.loginCookieEncryptionKey = null; - obj.serverSelfWriteAllowed = false; + obj.serverSelfWriteAllowed = true; try { obj.currentVer = JSON.parse(obj.fs.readFileSync(obj.path.join(__dirname, 'package.json'), 'utf8')).version; } catch (e) { } // Fetch server version // Setup the default configuration and files paths @@ -1096,9 +1096,10 @@ function CreateMeshCentralServer(config, args) { } var r = 'time=' + Date.now() + '\r\n'; for (var i in meshServerState) { r += (i + '=' + meshServerState[i] + '\r\n'); } - obj.fs.writeFileSync(obj.getConfigFilePath('serverstate.txt'), r); // Try to write the server state, this may fail if we don't have permission. - obj.serverSelfWriteAllowed = true; - } catch (ex) { obj.serverSelfWriteAllowed = false; } // Do nothing since this is not a critical feature. + try { + obj.fs.writeFileSync(obj.getConfigFilePath('serverstate.txt'), r); // Try to write the server state, this may fail if we don't have permission. + } catch (ex) { obj.serverSelfWriteAllowed = false; } + } catch (ex) { } // Do nothing since this is not a critical feature. }; // Logging funtions diff --git a/meshuser.js b/meshuser.js index b49ae8a1..0c7eae3b 100644 --- a/meshuser.js +++ b/meshuser.js @@ -24,6 +24,7 @@ module.exports.CreateMeshUser = function (parent, db, ws, req, args, domain) { obj.common = parent.common; obj.fs = require('fs'); obj.path = require('path'); + obj.serverStatsTimer = null; // Send a message to the user //obj.send = function (data) { try { if (typeof data == 'string') { obj.ws.send(new Buffer(data, 'binary')); } else { obj.ws.send(data); } } catch (e) { } } @@ -122,6 +123,19 @@ module.exports.CreateMeshUser = function (parent, db, ws, req, args, domain) { user.subscriptions = obj.parent.subscribe(user._id, ws); // Subscribe to events obj.ws._socket.setKeepAlive(true, 240000); // Set TCP keep alive + // Send current server statistics + obj.SendServerStats = function () { + obj.db.getStats(function (data) { + var os = require('os'); + var stats = { action: 'serverstats', totalmem: os.totalmem(), freemem: os.freemem() }; + if (obj.parent.parent.platform != 'win32') { stats.cpuavg = os.loadavg(); } //else { stats.cpuavg = [ 0.2, 0.5, 0.6 ]; } + var serverStats = { "User Accounts": Object.keys(obj.parent.users).length, "Device Groups": Object.keys(obj.parent.meshes).length, "Connected Agents": Object.keys(obj.parent.wsagents).length, "Connected Users": Object.keys(obj.parent.wssessions2).length }; + if (obj.parent.parent.mpsserver != null) { serverStats['Connected Intel® AMT'] = Object.keys(obj.parent.parent.mpsserver.ciraConnections).length; } + stats.values = { "Server State": serverStats, "Database": { "Records": data.total, "Users": data.users, "Device Groups": data.meshes, "Devices": data.nodes, "Device NetInfo": data.nodeInterfaces, "Device Power Event": data.powerEvents, "Notes": data.notes, "Connection Records": data.connectEvents } } + try { ws.send(JSON.stringify(stats)); } catch (ex) { } + }); + } + // When data is received from the web socket ws.on('message', function (msg) { var command, user = obj.parent.users[req.session.userid], i = 0, mesh = null, meshid = null, nodeid = null, meshlinks = null, change = 0; @@ -130,6 +144,20 @@ module.exports.CreateMeshUser = function (parent, db, ws, req, args, domain) { switch (command.action) { case 'ping': { try { ws.send(JSON.stringify({ action: 'pong' })); } catch (ex) { } break; } + case 'serverstats': + { + if ((user.siteadmin) != 0) { + if (obj.common.validateInt(command.interval, 1000, 1000000) == false) { + // Clear the timer + if (obj.serverStatsTimer != null) { clearInterval(obj.serverStatsTimer); obj.serverStatsTimer = null; } + } else { + // Set the timer + obj.SendServerStats(); + obj.serverStatsTimer = setInterval(obj.SendServerStats, command.interval); + } + } + break; + } case 'meshes': { // Request a list of all meshes this user as rights to @@ -683,9 +711,10 @@ module.exports.CreateMeshUser = function (parent, db, ws, req, args, domain) { if (mesh.links[user._id] == null || ((mesh.links[user._id].rights & 1) == 0)) return; if ((command.meshid.split('/').length != 3) || (command.meshid.split('/')[1] != domain.id)) return; // Invalid domain, operation only valid for current domain - if ((obj.common.validateString(command.meshname, 1, 64) == true) && (command.meshname != mesh.name)) { change = 'Mesh name changed from "' + mesh.name + '" to "' + command.meshname + '"'; mesh.name = command.meshname; } - if ((obj.common.validateString(command.desc, 0, 1024) == true) && (command.desc != mesh.desc)) { if (change != '') change += ' and description changed'; else change += 'Mesh "' + mesh.name + '" description changed'; mesh.desc = command.desc; } - if (change != '') { obj.db.Set(obj.common.escapeLinksFieldName(mesh)); obj.parent.parent.DispatchEvent(['*', mesh._id, user._id], obj, { etype: 'mesh', username: user.name, meshid: mesh._id, name: mesh.name, mtype: mesh.mtype, desc: mesh.desc, action: 'meshchange', links: mesh.links, msg: change, domain: domain.id }); } + if ((obj.common.validateString(command.meshname, 1, 64) == true) && (command.meshname != mesh.name)) { change = 'Group name changed from "' + mesh.name + '" to "' + command.meshname + '"'; mesh.name = command.meshname; } + if ((obj.common.validateString(command.desc, 0, 1024) == true) && (command.desc != mesh.desc)) { if (change != '') change += ' and description changed'; else change += 'Group "' + mesh.name + '" description changed'; mesh.desc = command.desc; } + if ((obj.common.validateInt(command.flags) == true) && (command.flags != mesh.flags)) { if (change != '') change += ' and flags changed'; else change += 'Group "' + mesh.name + '" flags changed'; mesh.flags = command.flags; } + if (change != '') { obj.db.Set(obj.common.escapeLinksFieldName(mesh)); obj.parent.parent.DispatchEvent(['*', mesh._id, user._id], obj, { etype: 'mesh', username: user.name, meshid: mesh._id, name: mesh.name, mtype: mesh.mtype, desc: mesh.desc, flags: mesh.flags, action: 'meshchange', links: mesh.links, msg: change, domain: domain.id }); } } break; } @@ -1180,7 +1209,7 @@ module.exports.CreateMeshUser = function (parent, db, ws, req, args, domain) { if (obj.common.validateString(command.notes, 1) == false) { obj.db.Remove('nt' + command.id); // Delete the note for this node } else { - obj.db.Set({ _id: 'nt' + command.id, value: command.notes }); // Set the note for this node + obj.db.Set({ _id: 'nt' + command.id, type: 'note', value: command.notes }); // Set the note for this node } } } @@ -1196,7 +1225,7 @@ module.exports.CreateMeshUser = function (parent, db, ws, req, args, domain) { if (obj.common.validateString(command.notes, 1) == false) { obj.db.Remove('nt' + command.id); // Delete the note for this node } else { - obj.db.Set({ _id: 'nt' + command.id, value: command.notes }); // Set the note for this node + obj.db.Set({ _id: 'nt' + command.id, type: 'note', value: command.notes }); // Set the note for this mesh } } } else if ((idtype == 'user') && ((user.siteadmin & 2) != 0)) { @@ -1204,7 +1233,7 @@ module.exports.CreateMeshUser = function (parent, db, ws, req, args, domain) { if (obj.common.validateString(command.notes, 1) == false) { obj.db.Remove('nt' + command.id); // Delete the note for this node } else { - obj.db.Set({ _id: 'nt' + command.id, value: command.notes }); // Set the note for this node + obj.db.Set({ _id: 'nt' + command.id, type: 'note', value: command.notes }); // Set the note for this user } } @@ -1276,6 +1305,7 @@ module.exports.CreateMeshUser = function (parent, db, ws, req, args, domain) { // If the web socket is closed ws.on('close', function (req) { obj.parent.parent.RemoveAllEventDispatch(ws); + if (obj.serverStatsTimer != null) { clearInterval(obj.serverStatsTimer); obj.serverStatsTimer = null; } if (req.session && req.session.ws && req.session.ws == ws) { delete req.session.ws; } if (obj.parent.wssessions2[ws.sessionId]) { delete obj.parent.wssessions2[ws.sessionId]; } if (obj.parent.wssessions[ws.userid]) { diff --git a/package.json b/package.json index 67e8f44f..309a3450 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "meshcentral", - "version": "0.2.1-c", + "version": "0.2.1-x", "keywords": [ "Remote Management", "Intel AMT", diff --git a/public/images/leftbar-62.png b/public/images/leftbar-62.png index ff3d096396ed0fdaa8b1de104f51351fd0fb14ef..6055e0a46c8b34d5c96583c045a48956421f2502 100644 GIT binary patch literal 33234 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%0AlI@#>np#@3^9vKx zGAB&CNXsFWV(G>cqZIqg=GOo(;P!-IaRuQqapQRbL8UXN$Q(^dPeBG2{)5#{h#G(l zM2#Cjjb3Cl5H%1oN)!%94SZW=#)I4CR1#!?y`=GVXAs@SIT^$d{gQ4duDJkvV zz57p0U?M=z#K+#fdy9&SB&&Fqo`DQFo+QL)05VQrh}yI}UCD0ZC0KgmYzM99(@*`{T7}B#;$|ruC-K3A#^r4c(RI$eq3)ZeIx1fHGYKI z?u5C)=&%fKHZRDN=PT?VcKa`&Op0Ayel4K&({RWyJagJlRAgMbW45mapiQDY1l+M;URmyC?6 z^3-_53t7lM0-Y|oT3!lpz82yp7vL;wZ*aRcQ-z3 zp>Z(C?cU(P!(s%OR_Om*wrl}v?AWnmkC(jzaNg8KHHHn2Rkx<5JmV1afk3}!Ph&=ZM@oD zgS%XVdt8G1U4jRkgNIx~Mx293wd@DUl+vW6B*+vboAsgvPy=MRyNAtQ=!B}gc%lyDFN@5DRpnzFaT*5A8m$1ti%ra6z zS!!lkY+`vxG{?n{r)^!QsNbBJ-OH;P&1MbxT@hnG@{@&puy>iI#lkZb46h} z{min4*4Eb0(9p>vqqlzm5)GUPB^*IMjTx7cPNFiN4G4bi(}K;fWt3J<$V&W!!+2E! z$-Dk5Ldb}FIx>94jlQD|KE37Ik5)8pV#v6K6+x8-(F6_U!O90lgR6=mLw$s=F+;LNtdhz6C7-gA3Jh<|IR=6?B2Y2^Kat>cmDz$a&mH$bhXbwMo&+VlVj+* zUtNCP=Ka$quO;8OPn%_jr6>MaNdo#+@(;h$WK{Sv_iJQS>#nK_uc|gkt2UieYK)94 z$<50H0}oC zrmj6Eu6-tMea0>WcZ@rjMXcoHl>YvH$!5K1!3Pb!-`q0j&3X0&ROJmusnf^Fgaw&s zcnH;c%Lq&PgyllQ=WUC(_31f_0uw~8kt61Q!#b{=+K$y`?kunH;^<^n2C0%n@ zCabcnoLeQRhIFR2r4@MB*51+9-qYMRKrI$<>PE*8Bv&_!YnsLUMsXT_u$(J|tRy%% z7`95Xeb17xrF}dIfD~XZ$_4e~7e0)~=r9(5iSa>Wmj{G|k4FaFZESjuQrU~wGmzmU zZiMg}sdMitlE;t%M&l+JjXun1AY@=gP-HYPWT2swiOA4GgC;K#8NJ=8@EIQJUq=lQ1gY^L!`Tm+^)MWTXBx}J0X@QT0 zYJjekx8`|unM3!l9MF~BuP3ut;q3@B0I`{TaAywdb^14ushrXpY7QVT2EfK#g}-lCIq4 ztC|Y$n>OAzZN6{Xs$$x%V%DK-(sj?IuBeESnVC6tg?c!u)2LK+P5)QsIAF*KLYdb` ziD(iELI#8ISt(&DkFdOsu(EaGre2p&QH4NUSuHLTh*>-_twKyVWY_4FF4pNIBtWutX51c z7^vjcfi(*W37O>R*U<2AWsPu>P=J`gg}(?&ap@Q=e6Xw}Ff5Y8dqRo+<>{}G0Tu&z z;f*GG)wy;v6_C+DMbH~qZURO_p`SgLn_%N62pRW=x%WqKY5+2@2^y#Z`Vx`REG)%_ zNMwor*K}_0QMi^}f&ImDYxT;<(5Q+s45)H5n zkY(VBMrA$1L5V=j~jR)a>AgERWL zyy}XIipa>wNfxnNJG-CDJ4`|rp?azzXGmrti z@WPPc+|9g+DjGO4u%Qz$8W=LLA}E%d3_ZwA9w0*js!G6N;6c%UXh z8PVZhl$AoH#m#UtIRHnn!DXO^kNOEul|#;oe_lGg4PYVR#5kQeabl8(sAnL9$)KJ% zY5(4Q)42=G-ub}jjoAjTysb}o$C&W06=5cL4L9ia?|d5a^{>gRHl=^FDeId}*uRrDcKiB!r&T|=eEzM&lCK=z`oNkn)0QwB(i0!T2hcym zU-_80Zco868TL_`@}shxqq4jsvQ)QSYi|Pw;qKv;npZP6?TuO~nS_$d7JDXiayVQ%ogN<_KS@{BKQM%* zY(CcECl$QE3Tkm_TuORET2_2&CNU{3IzBlZoA;HGo%i@TY%fVqMaDymA#U&(t##^R z$Yb-zkG#e%1!WbliLUxxY6LKjBq5m$tcQA02$gZ6B$JX0dam;02%g%$L;kl zfTt)*a-$F(I5|PQybx|qP-T{Xd73vn*$vVYW{fp0!jv4WSKzP7h_#=Pm~a_cagHiP z`@1oA%F)(}5#~358sIhjw9a^|9`jJ%Z*+C%jx7>QjMu4CrzSbI`mc~75{a_XlXvX0 zn)SZHta*lS&DMYI9lclH)|)m%52J=9*6p2N|Il;(S78fQMSZd==96z?=dX&Jzbawd z!IbojyqugIum)wN>;nhvmVa&c*%!78mRZkRWbyVq3+(0gh#JUi1Wub5x^jKm_M_zO z$7$P+7i~Mi+IFI3+lkW6N7*(mw7fi0NlD2RjqSl|tN46y8C!pMd~L2HmUE3|OsRxV zvIz^xXfSw5Ibm59VMXJsU$$?R8b~3DOZnomDs*a0c{Pd($TDihl@AgPgpUWMimE1Y zVTCAzT+`IlTv}S{;pvl<*PMk-KgnTg_KV z)KA306XP*1jE|BD!L7+9(^IoaNf|jnjo75rsJNu?7(@+rS;fCI)cRCp;JiQsuAa;8f$+y%32Y;Mu?$wfbMx;FdFv`IV+b@3zIwy{1r05AO!jOT9{ku8d|8`w^3GhyD9H>_N?=fqs}{a z+WofK>Bsf1D^|HL`^InK7a<>i9{Jv9;WHP8zq26p?FFGb_D5%A<`fhZKr^zlayI;8 z@!=wid7qlk`p9I)T*K+_;#S3yfQqRuC>NOP|It@5YqzJb-I2X!XWp8f`D=C-e!r8l zW*5cPyD&2|i^JhaHtR(T%u8iuC5=j5zs2f{AN-MZl9XvsaTg8fJW|f6pZ0m{7kher zVnmE`adD-%m?JLcqKXEK|L`oEvKmyFsHj633W_>$Mv*ALNLW`_m!F^S=@mfC>PjpS zCl!j5DdJ?RIE5xop^JmF#9qW&E|&uq1D-Z{S2ZdUHMVv@dNL6Z5RY+TEG7biSMzuj zSMW1Pg(=y2i5b~(DH$<|sgZGsVbO8%$McvYfQ%ept^WRprx%MxM@vcZ z$)0-2p1O%{nm~;h+uM;A@?bOq^`!hXPI;*ucDc1j`vMwCIk4+D8`;hF*EXx2-L!Gz zZ%Rr^ljP%HA)~*)KQ}8YA~e+3+ug&(#n;o*&)dt}-QCT}*~Y@!+{D7v$lTP>!ra8# z*4o|MC)(9L#o8f9-;{Rs2F=innwgmm1{?PK$nco2zA=4mrv8lgjNtFI8M=hGARbti zS+;~(E`&Lrvla&}{eigjhq$FbBrf?OdC8BdOMXoM{FiiZ|6Dla*3{HUHtR(T4(9BV z5>jr?w?A8-kV<^>Lw9TxIT)LJf>%KE2%pjjpFwWIAuO+f++^PRPCcjLe5RPf7E{Ym z(8|U13N+^eg26>4j3U0c_yIv!0x_|0lw4d_U0t1$lH%zd5}rB~nJJFU5=Ui=qjSX3 zxhUL-;>ctk@GB}RipgY3wr_HR&siXBo&W_r#&dzittQcnL!yYDzJcBW!BO$a%yJ%^ zKamt;!RFO4*p=yd1u0p1iRoF8r$i^DK!%c*Lt>S4YU&%hdU}S3|Ne~C=hl;v@xWp< z;K*;rkh|wbYUX1mRY7L=$Lw=DR)gF`l*bU0io~R1 zaXw3&Un0(DiwjD}otyWdNC7uOvW6%{)jS?AHa6D7E5bKwz%ORVKX$~QC<=%Z1ty3C z;>G6PqSS0od3kwoa4`IpY~Q4XpXWY-5##$mja}&*9A;o_YG7<;WM*MzY3=Ch*4x+D z*44wQZ5aPd2o_foCEV&f3L`DI05X)cocvrejm9cvu*=7ysI0P#%jehBy^ITLJOLTF z#lTY%WHH1-R~#7~fQ$$8$MFdo*ifrNRkqw6xdYq&5*Fii`SRtarYXGQPHuu}tdCTT z&m}$OJ@Qh&-MYNl!%U7E?*b_avKYC+sFsnAY8k9}M`o-IR?E;Q`KlA`6;u>Xwzo-! zy`iH5GLXyg0GFW^=Xn1~F5`E=ge~ST@VI?)(~ce6Cy&LaA_GWKSy54tpHHXJIF*&K zM(DJ?J-xsVAP77Pg3dNDFwoo6)7jbC-rnBS*a$%3^Z6AO<#3QBkw~z-xw+ZS%F5cp z%)~>(-pbm;#m&>*+r`<-*4oX?*g;>{&e+ghL&N8~e9-wzkz4;rShpi%$v27bElGHH ze$*QuhAsXoG?ti^n3yfhRB75np*a2H@M3wrSCitGyVOsA!Ix^!DnaC(FH!h zXe_Il{$=ydi#?%VO3|4z-pK>B;wo?u z5fKrtUWtx@eNI6G&cTDuAw#aABW_`%uAw3gyODfyMM+5sAYUN(J2R#_dwNT&9>K&| z6mta5?%vwE2HJWCdIrWo4P!IQXkt8kkMP35;o++K<}pl62xd9AkinuBv*{&e<6!|o zX0p!Php0dK?*<fa+0E>Z;QDfgpmy;j^RW!h8pxmU7b+d&cuX6L? zwM&PjU6k4v(TGrxno| z<&~VO+PeC-*4Cb$?ty`Ra1*#I83PIk)&mOxB>){NnVg=Qnm{DRK&J@}4fglKAzZz;!zpMeE=v@jBTmQ_C+3M0 zN#Z2DJV6$xP{b)zaVia+(F>745xWq3>BZdA($bKS5C^w(Q`c@Y_Z|z6J`1mYORoVd z??FqiK_#PZI-`WaU<3vRKG(^GV;UG7LK8qA;{|m4$hag8Egc9g9X%aALp?)dLsRqo zf%9@7pq!^15ZVj7XTgI;~7rAUGPHs4ZKz z95`_B??HSrDrd%E$hcsv_UE(?v3BUqU65s`zmyzID6l=?fGN*3%wd=Dg!T1cIM8WTeSNL1t*{-9*d;u{iNL`eXaa&8&`Z0!x&Svo z6CRgKqfme*(Lj>m;K;DBi14tehzMeAERZ5CH8mX*yil@bFIwQ9@^Z4b?=;)E!$(Sn zc=k%d%J01iXyS=GKJf(29KlCV7C?pq;9yQ$QnB!tI&E7p8pGkqqv5Hdh%|KmZWLCV zh=Irz$L5KNBr!2xOe_$G=ZGC6yNgSZg9r)=GO;IXShi_ecWBvkYTI_}*!Ac-^l97n zDd;wpl$7S@=Reof&++OT7%b)2P3Z6t^fIosy_2fCrkaK}qK2-%uD%guC`GIi$tyAS z4-AzF;N{hiNAZI|cX>4wR(Whv+IU_-#HD0Fnj=})6lnr7#w-S^Vn{58xZW9%(LuZ3 zK?P*UV&f)~A}Aok$>7LOKdqY(KmWXb{rU|MpkIIe^|#+P;({9+Hv9sQLPSMHNw(od zOIXc7iHY}lTm1vq&ix{Dc7x2>U*x4Wx#?YHC%fbGxKQDfXvc`Qf~s!WC44h3$&63B4?JvG(UTrQ`$xQIrh@pwGRW=(;r1%k}<^sm3w+p^2|)P<;%Qn8!& zguV8@16Gvv!UkL+Ly07O1l+{z#zGR|Q_AdBC9>*5udq(Ps2=~AzJQqiz}Ue+;!t4x zNKnFPaH1$ANgR?a4oyMf8z;64=qQ1-zo;l6AV9-}c}uTRQNLNqp!K$4>m8$ZWs?pi z!_GSf)yyJRW@ct&WaM*wi(>+0u%CpCd`5|$p~-zU4WNd)rnaUwG8+0uCM?#(lY^Vu zIwvqf5}ZeZQO1R2WCAiEs;Z~9Qurt`9?soD76TyzSquyrwjH!<9h9q`=mLMJ2>M{C z^=6FwX#l|HjlbTxdcsyy*7)uv=`#oR?AQXpn81+qH?Fod8 zGaDdoT-a!>awgG3i%UvI5!>-PD$yzXVwg{tb zZigDnh8kTB#%6!|Yo7&Pc&Qw5yR*;v=FTx*7)x!qd3Z!=+>FM$GOnUo6 zCzRFCiy|yYLnXi^rG#a*Z+z8Z=+yx6~-EzLfN=A{wK-tc=6s^J{Bs8yXs#o0~d2 zI>4krp8<#fN?;{ZYIjRmEMiRjqGg6#fA>3dBJ|+N@IQ`5ezeS0Vl>|K#Y&-&p+uuo z{^zHmYUsj(w^q;(%Lr_}+gyS=U4pvZf_vOT`rJbY+`|Vw!iPK~M!X_Ny`x0l(GX(C zV6l~VdueGIolf`m_P%z%?2>ZL<$HBkRD{y14Odm0WYwB4t2Ap_aOezXT3T9CQqts& z;&?s9WmIRA>1rBUI5N~UwKcSLwRH_3Hz8AKl2<;^(C|oERs94&JRvFt$(gwmcmY8o zPh8;() z^|KPfvZ_}qQHCCMhS!+be9rXC_;A`|ddyxcflhuKi(O|K$71Y&(h3c;6oza}6ha z7>iE%pPxZkm^=Nm!cE624D6e&JX)Y<6K`p_{wc$?K)Zu5pfE=byMCIC-Py)Xmz{3Ux=X*O@t$)9DQ0#oxY| z(7@1eDIblbPKc}m8t_7b3=J&}EeSGG(=#Nm9H*xCu8DwnG>YIaw>pDFnZS$agp}lr zoT*D8kscz$^NCdq3>l6i)z;`r!hj4cH|b(XauaMCsC;p}4Dg~b(z-CxrXa$K6l$Im zWSHrz9crz3=+B+&9)rbLR8>vY`8C4YN-Kjaw)%(F6?R-cx&G3Lbr+AX(^EL)pnkz! z_e!RpE;-DI5^6{e(#sFfO7pyDZ*$SeQ@Wy-HZnSq8nh&TUyYN#>L+QYH3cDTUw&A?6lHRC#^oM(g*LY}M9v*z|QH7@>qqeq2-zfagW6{&+dLn23aXdP@ zE@BYS&k;2Id9^Q_DlTMUSN$v#Q_3Z?o*6uGX0a%vum-Y{nwlE$3jqPd>9cUFf?rmr z06Skp@ah+BH}r-fwLu9^J;F_i0Ih*kI+|HLm_hGJr*-)!H#9XhK^L0j?DxMRfDvRR z#>U7{&-wB{E>!+`k-PUIZ|51Fl}izYLM0N3@Jz|}J&(YbboTVJ`8AIx6qyf^p#_P_ zn9J}D2!ziTeB5|-bam59xl}f1d{EexBi*gfJyGq+$be&SWkvb58(z{kl0RAw9VP%B zVDW2ufR!v*k^83|6HV>Vt@i4&=J~$b?3EfC2Xm4l`f>Hl_iTz|WFffps zl%%5K{n28xpEr4~-|V?=qsI@wdVIUyYwlu4G$S5(HwUa1o}PrFR(yzx8q+?>T>LXt z`W|1!xbD75gQ{7hnnkm^MXQEoJ66~1(y~EyO&$AQZM(jk`hA{3rQp1vEe57RzwOWe zWk2QD1N7ex6>U7s+H|yJ%Q5zbgDh7cT45nMIy$<#x>~Y*lh(-SsIaA-&95Ev0^=jA z5E2t@buArTLlX!+2qRMn0~2#|YrDt8#KPvWuzoxY%`7EhY5}VDd1A{{`&RReUXN6r(@;e;BTwwW#jm##!vp=gGS&Q>R>%@;9ihJrg&waw~laT@5 zvbczG6mU^Nt_dz&1K7W#n4OQ|8%NxGf)0;yRmtffqn~$;^{Ho%R-EivB zgzHpF8(2Yb=~I`Obar;8Bqqw;@cekO`I_~v-~H^m>PP3V);N9fo%4c~POr?e#-_A8 z6XtlL(GzrrYZ$UiXo^?L2di?A%9PzuuaeiSzNuZOpd-Ac+o-75q-4-?+W-|c?-;i$ zn{?hW?mDmB85Lj5WHRI8;uP+NuHKxndUN)-Tl2o#k^lY9!tZxc*6gOP-A(ZiCFkeo zhlhu^KF`m!jE;)hyLvg;_~-;uj71)erDtHQt!Low?d$Qtj<@9X_Go*Yv5CE69eu?<_Ii0O8P+f|Dk`n%@JZ(w zdL$a&m9f6@yPfQ2Yw3;PEC3d&$0V@0e`KxRnRU+BHoK@^2uPy0cRydFo{S7Qs#D1M zo445=I}@?zaO7L)%HWXF2VobUNri4d^DCx?MYCsEmsfb7cjTZ?)R1q?h+nM8KThlr z)L&9ko|BUUj+e*f96#%^`(Vtpx!%awe}rE#lt%choUp8R?z)bk_)!X5Oeqyp%f+-x z$!zN)KAK?-nN9|!p|rG=PN)C9THs{V(9jSc8+%U5_2b1BUw!ZN#kYoD|pz&UGa>O%LrC0 z4AdSUx&9ir4ffr$I|7M`Riw0KgtSMZT6=d>da=+XyjU-ospg(}MZ-l^SxNugdex(A zRgNH1;GuSOt+~`M?l-o2D(oWafVy z^0*+I^3DS6(ikYaNyknMML&b`#@EdT&O=!gaW+ky%@F5clP2*Qllf)h6#8fm4gH=g zP-E(l!R2rwLc{kRbo_9U1(4#i&utejx1PVm>chnr?=Qs9xV6Bj;eefVi%|oet&cL4 zHy0)R^n1Y(Y1T2>(i7LpPs(vl-Qb>AlI$sNvz^vhqsFrvD8gWW@q)Cak19L$oI#FUAuv&&$c7T3{mqxPd-FREMFQ4zOEA-aXZ`h}rJa+xBSN^;){u(-}p84A?*7h-ts?^SUCJy!b0`?B!rI@G>fsu%vR@ zih8O0y)l`hxEyhOE}A`wPVOY5lRHzW;;`)De0nuxB~w?X1~KXMK);|LH<-`<*mV9< ztB)32ez4H|z4@lIJ~V#k1EV+JGnzKj1UpaP<`HTjuMzs%{HQN~&DeF4^2b@mpXZDJ zyil@Fs&xOQ^8J@94_xLRl;$0}T6I{qdjFN`Jr^p9m<&LMrcJWv3 z0YytE9RlnX2DlXf{l#AsD`0MKn?w1=1sFO z88sWvo7+9*x7uIbXnJ9T$%PGeS2u#W@VdFpQ}K6m2XFl#+88n*;D!E~-0r?-7Y(0` zjK)TxolV%M%dH`?|MEM>rQbNcIvb9(PLlJr=0!~VIN_VEh0=GqGAh;A)atHj2(N24 z-Oz54({8nP=YzQ*lga)4{Xv0oyZ3sZyF&bFa}X+mqKXD~ZE^IIYouw5nA^_O`b6~w z#SI2042L9*h9rwZQ$%5DVhGRJ!2%i&y3o{}eGEP`KQAx9FZh!s`mesDH|ryl*&ms_ zJI{E=T%$MV7`!%9|CP7(2yYt@-Z6cI8fbo(AAAn4ei-`ccL^K*%-M7>fAb;omLs$+ zM;Y6W7jHYk+J2I~^HkZ+)8)I)R&GC4dFTp_L8pSeef*8B(#~JY`Sp+VFMdw`bamY9 z&m!LZBCucWotI>bcY=uhK-izWe=i~tBYi)IO2;Z{P z>fVq5h=3u?+A398y*@O6&Vv46zAoa?&MC=Hl;`_7G1wX0c)0#kpa2b^CF`QauY~HFf=fq z9@&Mo$C1(9-Q9wzxw*Nqu_=(=vfH~cfY~K^_(khQAR}5NbVSbLVb$|t6$3+t&4|FP ztq?~B#KUq%HxuP1O(gkDAB_wjtxO;73?I#OFZEP+m1Gyh3!=4bq=}@E;jVUk?RTqI zul`m`OY6B(#Ysz)!w$8Kx?~Y~5yCtIz;VGUOa?p!!bHL~hlJ~3K(LU5a7($9!=sh7 z^st({oucfn>+jL?I;Za~WfbIZ8eU|Ag>hJsVF*K7+tb=J$Y1@$cwTti-fwB=W)M&? z0TUC#Dw@ry?~-iblk@~+0AtEZi#KmJpZS6Ddkf5F&NF`NJ;PUL8W7$!!;ZB0H9G@m zEsg$lU*49(l&!}Y+fT4|oMi7fUB2^7g|#a^BO|>UBoG)H1Z>#szH48=wm*aCEpf(( z1Fyv|MUg~Ukp1aTbanFvW2aUVmkv|cE_3%@bB{ht&jCxXK_lmOI)fb_A3ynX?4DP! z=BV&+UoUSpHPf@_EjMj9U;eex`wNU_yk|K59sOx<0W}_g;~_Py2{Z9A6!&R!ecoLh zyzGbAFV-h~xgqJRU(>$+E#sR_*{e3^uG&gky)FOS?S4?UT-l@u8UNkbsS1~YN)Y&3rRGGFFUTrJ5+(yQcfpU{B zIzk2*jZV6JhUe`Rw|l8>_fp*M0xS}pZpGWn124ubMyTOgQ~5vFe7oxG*)z{IfHipy z4)?hxD_X}~wvM@E6@AGv3Q@x(SjsNu#sgT$fw^!@xZ#9_WBiSP91FUzsi?8jA;MNx zcekeNDQ%By#vxgNjIl5dWn9v54a;LQ1a-;L-jNRX!>ts9%x_z|g&Kq~^+P5y;_)yI zFRpFrecCSg1Z2P=u7E_^v&Uq?rv`7&)_wIYy=kc1=n!V;6W+n8;RJ`-H|7UIS6Q_w zZS|I{@3!TAx1#`EL~A$2)2}cgAs#+uHj8yj(R)O>b7){`8CG><`VR%`hX(Fu|x{jH;pUg4?pgrjxtA`LWN( zUxt0UI%?5(F^j(^ezqol$=bxFKc+1GDQ(%hjO9OPtyrJ6bX}HjP$4)kB9Ryy8(ULd zU0GR4rBG5+Qj!1{3Gwg`Xp)+m3Kj#}`!^D?QISa4(q1m8FXp1E=QvDMjSmv72)H2W zyO;{Ad(VY6W$+Lo_^ktH!kW*9HIrC;$-|hyfHeLs!3!m;7^fG53@{Wxiis%+s$u{y z#I@FdjMl=dtrBFQ*%_#uL6^mm(OYz*Fz5z&3W#|7oAGvXM4M|dmeSE?7ja&M8=bRJ z+OH?G`;YBEGYdi`Z~dah;HO|iBv`~=v5L8D8I7o6872dC7{dgFL!6vL+;w~6H9O*U zA~TjDY_|`$xvITO&GDF~ho)f&6)<5E2EjB9V;Tk1E~&c`X&gvlu;?vn>h{Xu7=;!= z7-E4+9w87D38EW7(ex&1`sCZk6m|4G`B41?WWe_t6BT7^X=UeVWo7BCe8=VJF^6wf zS$zD7>CE?y-+a$v`fQtNvm9TY>+$gyp$k_U*beesKPLwd3NioL+kmaD$goB^TFxcX|5JtEJL+t7Pui$g0*~Q*XGg z(IltUa$U34z>Z7B%)Df?ra*mteQ_}{98VR!C&ZQbp<=u5Y`i`?G+&~f@4JA}Vkj?>?FowL~YgJppqtO!Np zAYVp)^kvjXU&Va6X5!ek#QW{!2_`=3wj(}NFH5QDDCm^G#Yv5FH)9*e_BtfrA za@#h~##BxZhr^LPj0s)>cI%cXgN1wcjHgE{NLGByasfH8MIJ zl52pEs^VcCdl|e2WGH4K7afTTI4SJouG;}AVr6Y&WNc!tT1QDc#@!+Dc{ZVzS2T9q zwLYZf=Y8D2yO;c>oT?4g?Ow z4uFNadxnZjy1Hksx>uftHz}j2_K9Zq$u5J-;S`ec<5Qx8{QUg9z5KjAeZ9QgoIP~3 z9B#-tUbx`$$3Bl=wgfEyE^O|Sh<85;nYlQ4){?L}OT$0=Haa~c56m{~9N8JE>(*N= z`NDSb=eDRIvdHqyx%hx3UJya&dcE~&-1cMCBhu`nG8M} z((LT4srwyPT+q{C&u(mJC@U=mZged==;-O{>gDbZK!LjsHa7Ow*3J%& z_O@elE@?;02!0uv;84AcLd%RtDm$4K$OVn~o7u^1RKj9bQ#F*XO3hP#aGfQ(kM zT%z4YZ2rZ=DHss`Iw!3Z_vv2U1s20rX+Ozdc8ZYE(c9#fq6nBk5pxO8O(e(&1EV1e zq_D+F5sgS;6?w%X;<9=8CG+shVWc1-O z{RE@&wUP$;{Ao_D#35hHJm=nknpbggMtX6#%uG# zf7qF|{TOA(3Hpwc#XC=z>^fWa`?<`Jf@$zYn`jv{O$(6%Fmcf=^rC6#1(T2q#zE)oW7O5`IBks0_i#dv{c3udf!u#@wlKz!S~(V)WoFIGt$zcBO(I*{euDmLqkHMBg0dY z;?mMG6I00P>1iw$3!did6Y|Ck%{ONmygA2EGGK|<5pimu3ZmDm^8%Ou6!-l$bYbxy zc9DMgo&3`uv~_zKUu~twC!w<~y1HIc2YaHxaCLWg@wwc>f`Y{OcpwO*Eo1D!@nL6e z?dI$p;O7etB`zV$!X{l=t3XPg_WKFa7aKC)Uzz;PFYz(N48UkaL_}5rx#eXc1Ju~o ziKirgg$$vvwY8PPX(|ykH8;ce*2LrSB#*sl;qU%}kOAfbvlx$7F@P818jBIW(F0^$ z#*hJlA%lL6mLq@5@9q(o`$w(sUedYdq<6tt<*@n9y?QdcFmtiXL~ai(2{Ab9ZhrZU z)7{%)Hq6>e*!d+J@M0vQaTBd$P$ps-B_V}Hq?AR3lzF(6N$7c_!1Lx|SIonJ6jG)k z7fgcB8wH&+3_NQXaMr;8jA4M3oc^IJYP;l(&#Ss;sd?l;Hlg92r|C`7^3B)s$-AiL z6kS+K7q$iFaU^L=FiKmDL+KbkP{{+a10s}m-z7!OD_6r8@d99>8^nM>b&Vi~b3%Fl zWAnS7jErY~K+u=qyYKDkX>V`m^Z5)qol2qP=aUKx3yX`3!JF{7Tz3~&Ee)F+@{U`# znJrpsI%B5(v^Nb1Z(=hYBwoYumG?Y9`XX$}+SsK(#4r0PdHK(2E7oVO+>o$*lAYGzn=>8Q=waL0}6pb#!${6t}nbOnlnk z7QEwl!IFQBj0h0`q7FyK#3}|tMzuLYMuBw8BgjCP+o5I4U-ncv;;eGi`Th}8<)fN% zz6Pk$3AO@YVIaF3vJyX?lgMf-5i*8GMjBe%+q!#t`uhhU*~G3YjthU%BsKo|73JmL?(Ww1 zrlywqE)Mp=fdN4QAv)S#S1x;Q-D^R6dND_eIo) zU&em;W!(DRNd*OYnVFf-yA2aGJTltY*3sJ8-O~q68-k`iyf7(Ng9J(LJdos-j|m=v zObvGWzP>*2K8YClDrO@=ozek z#$D~Gi^^e7^<$w%XCY7VG`}oouZZ&k4{#;MNxn7hjW6Uo8yOuU7CK<0kV2#gMWiqZ zIS+0E@(!Sbp3foJG=Y(@h&EL z?l|FlJVs#6&pn-VPAOwxgjqM$9 zsP7*f>>n7wy9)e8myLK()h!@}O-0qQ_iR3F`?H|uhX$A(I+Pjjx!4?Vfe z=af;R8=X!|PXk7zB_|_h;G?H$Y3L^!zwM85GdK2m5 zLu6osCN43`XWTVUxoMts(>UR*c0l)*k^YtOwNHp}lM9Ac*UmaBkLSf$$OV}d*Kz;( zw>3085SXbi!9>V8tk-)BU)H+i`W*W9rUF zRUMCBR^2J1bL+N!!X1YsWyfS?j2(BK)9yK^UQ%~*i^((!%tL95XSO=z9gw!5k_SoK z58(r`BY>>!oqNwE9jkg4X!((~{V6B{sHmI)_`nDfT_BiHuqQxj-Q3XyeWk0Xx2e4y zx>J2idu?+obS+L@LwQX-uf7q0($v|RGE}~|+kR)awVEg{T+AektEo&pxL<`F z7LW48Ex}^?MX~>`5&KWN@4YR!I)i%d-S}g3y$*b1ade--l^Z7aJUu;}9URcL#lhHH zS$TQ5M@L3PMkcv=6)Gr`cka#CHwGi0@eh9?4Y*L((h5!M?(J)8?|{IX)&@B{G_6!n zTPCO}=L3c|ePC!1X9s9RG%6Tw3GJaswYu+U zFk2|N^$zRejNDT*Vh+FK_2*2B9kVqzt+qH`Szp!G*;&`r%&O$jStZ%Id0Cm6(3Y&M ztb&39Hk-}mav{6HuUgpB($d-43A6%sOc6MG`UZ+B1y4gpNkjjUz@|)WUPg7>z|o+l z)1l4el1Bzv0VT+UOhN$P@q#5dCdLa^_>Ykhh9SdFGCh2P#i%hK<{3BTUc`}sP0c{a zVCP=H=&5zqL+gw?gytz{_5Iokfzf_M=etqn5^(sNUAk$n2x-d%QjCua-nzwg|Ma&P z28R0F67B&hAO|seKnmPYfR)g6KcVh&OvClKw#P{@6q;_w;WjK$15!90z3*^D#s09W z<8d{sOSXZ&N;X6#+qm0y@fbT2l^v7rI3``zaMpH-#H|OI4m@p9!_pQFuUt* zu4(sN(^WmNffCOG|q@?6tjpy&WAL(7^@GI`!b1xqSb->p@At5xas&RcKvDb5+X`_-uaj#2w-q7W}p0aH|) zA}YTi_FpO1duLebjn3<@Rb6_Wdisro!*6=;dCPq347FcoTwgom!ncd$Hs=)6sVsJC zc5YNGF~HA1(9bV4I2fBo9T68tgoAQ=MkXAY|KV^+LtA@&a|>!&Utc@cG}vPwHm$vr zUn`^(@xp1eugbM%3U0mCqV#<=D^Q&F;UmVo-ZizvYDiABVxrfc?GG zk3zWIhfHJwEXLxhpHW5QKl*_JK!ywPu3-RHq3}Z$3NRFqdVre%9|33pE`X2F07C(! z08BU@!AWt?_K>#A*?>gz5?*0vPaEvaz6mr%>zG^CF^Y&CL?yd8CA;`5nlAEYp<~wL z9#{_-N!l`=9e^0HB(MaVh>;KA0K1{-iw&|qz=wVit)u6W$?^nVeFLL{%d4xe5jM1S zboBJ~!j}q%Rxr%)4-OPOeiO5#{$Q!&S8T20bzb}G-F~gJ`MS>d(|Yaqh4*JQ-<{QQ zXI9Vc_XqBNG<^U2QR8((rYqXi=9S6L$v8dJfB&0iTVB8S(`&NdO+WM18^^w!zHix_ z)ZN{~<2O*5HK$75zGY|~u5mwD=l)xr?W$ViMYY-=)T_>Fx;wM|&aCd+ z@AWBv+;#V>CiS&7n#+oBevp24R^Y*RtaiMq^7Cs~zkTi0m(vffczxgU*Zx@c&cUzT z6CzO4m_<|?4Tu0f0$KrY3PgZ+ha)!hG-$6-D6GMLCJI^&H=u(}5i%OvI&oe+5g8zA zC7PG9-lgFe*G5n36Xs28(-khwj=O zx@U6;Zr`^*qUvx=+vS{z&o%cL9Wpz)cc2@(resAZ@f;3IFH*55Dp*D;SViBmLimu; z@w{mfIc7aj<^t?+!r6gY55R|Ox{_@?U&_m~7P zVp2jrx=b&0u=?iK){f4u-rnvW@Ex68UJHrKofCcGoz#nqDRuxKxKFo2GstS;) z4!u+xe5lrIU7ht(q3#EbsFQ(042 z*VNJiP3!3ed~|kpae2*T4*!!_>9^z0EzH05EnRaswQTXZUwq>u zSnN_hzY1Uh?q+;V1aBQIfqn>Wgh!z_;w=TNjC}|%Ry7R|Cv-g#8IT__>-!D|H0x!w z_h1t#Cd|t~4s@KbqQ|oZ2OIc{An~YP3^Kr004W}(B$6ry@IqW|I?UC_UB(zPvQw^~ zb-s7p>HZ04)srr2z>brSs)u!y@~Y}sEj^D=`XL)PCwbFhYr9*bgiL89dCjBkJzR=hvAeSaZy!cn0k5Izkqzun^UQ?+e5fOQAnTD;{D|2{ z<mCPQ^E>it@bOn7 zPrsIM;q7#p5AzkjU}$YGvAN0#R_773Tx^EvoJxc#|Q$#?@Atfp{lGA{xvcnErLzu)VB*7A$#j+Yip~lc*JGk zf)`FwI2xWNc)~wMM#wm1052X^F@P81YLmy25gjLY#7_CJ-QA-O_l`NJ06UJ^sQ@x^ zc(p~1ov9qIl$RbPEO_Ac4gd#YhnprClM|MA_nIjju(^G}?#@Ab<%14)A>;zg`iB1L z36mnRXk>JF2n^^@-@ssB|6nitg@6Pa77mXLJq;6&YiM|cSJz?{n4x49alS|rVI7NQE+}omt%s6L>`jX(a3wf3#s*w)c0lriz=5$NEc=mZDFnC!hq!v7 zu&9`omYowyObCdL4@*jm$;wMEpyn{xMU_=9=~2_=f1IZB3qgG&L2nzuY!|`iPlC&S zg7;xU(D7Fz&b&^Pnwc!SFk5*I+29YR!#TE(RAs;!zTcs0&uw)MpH*wl=HC1;L2B9U zJxgV`*+%&`x3u;?_@xqD(0M>Fw%fqkU>|L4Y-F+78M%4kaftyj@u7*S(V4kP`P6Ji z3A2J9n3wX7!iI-UGu=h7{)6DWpWtKG4_*|?& zO*>rewWHQ?S+(};61n%|&V44gW6`-^6|A*s3W9C)^HB1}gA6d6q+5B$Y=i-A%SgbabnFwdxka=Vp!yN#~g&QR`P z-1UpO4F=(WwbDVG+lOrL90q*YC?AHzgd-@ZX;2EaKjor++EwGUo5opp&2#Rk-tmwf z4yyai753ZQK4_@=`* zLtb4`SxILwa`W>ua`F-~bHYP+#)iI zQ&h=^B&eyqqZf`m*tjMLX9sK#2>%HFpa5NamW$zVhkBaoK?K@<6&o&Y}fyihDSy_ zyZb4n0%Pwa1@llj6JSROVuxAyO$&sNTUOC>roq>YgXBzt!H<9w0gl|UPk?L%uX>=t zT9-6@o|tz61CDv{#X;YMl|M536ZU4RP!ffUAe@0-pC-|{%}md}Z|{7=me zzqHits+6OpeRQfvd~R?uBO!~QmM>tI3s@X}F^9*ls^K>@30vD+JG<~16M&8v+qLkf z!7dIR1H3o5aW0q3VzKfI3olqHueiNyk^Gio#LeI}S~o8FFE1;PvCS2DVYj?(sSPiR6TAehR6I#;#(PSCU!m zf}#=%tCR`g;0qd>Te^GufQv7g8t`F)*I-r(9!Cb;VOKYIKA4!1&uic3E4&uf(AI;E zFa4zu1|}h#6k~!V{|p(J2J6L*0S^XNAF&u>fst6~1qr_7nJ7OUu}%#5!{o-Frt&Dv z5IgokqM;!lNul2jb~2%MSr64M=9hlZ zIrfd}{$;m!Ex569w)Bs0NUeJ1{8xmFUlXKP5w3mrioy@mm4AL+^|v>)w!Epg^G$=@ zZ+RU3+~EA0d(!LfUf-yubj#5_hf+^1uIJY_@M`KR1vR|-MxL;VTi*!5scWpL6_(f3 zaqAiy+mHu&;UFJt8YBx14Gk0q&4n0v$xCy)<&__GkFQcYu>8)h1@fEbT>a@ysnxGS z)1*=t*<~$ zYv2hRIkm#dT46;^eL0FcPMr|=*wfz+o=UP!Q!Jnc5bJSdj0gN_{ z@%r8H`W~a}dyM7wnA|}4Fp=MePBJTlnz+mM(nUvK4N$Gs68rqoODv% zYb?Ll@)nK^tcoEiWE^rt__*k;-_qS7d7J-jL67e1A8PIFt!?Tk=F~-J6*-4xns_B> zI!4{K3A{&g%z3{K2y9ZoGQ+sD!i)1P)u!Be{%r7k; z54jgBXd3L?kdBp>mKM?|F?p%xab9wM`p2Db@34^mQSbQ5dwV_sd`SQJ)`iutJ<_x_ zugU-PhS84Y>POb7NqvW!cIC8*0W__;7Bx*M6t;J?!|{8pX+Xfb=2pCEm30jX1(b{; z*5FkB-0XM(c}pv~fB*?CFg0`zoDFH*?bDRS>$#JN=4GUFdmxj-VSx)iANcksDQrT9 zMDRihC&uZ8Ap;~D61QifaaM`Nz<5z*H~|^3(>o*u|E@2)TVM8fgKLN#hS&cvlKb<% z>_t1BKgL@R#153U9JINO*kP^whw+WQ77BP`BB_0jg~Q#$PWQ?i{?P=D{{_JT8ICBB zz(P7RnS=ABkO2ZpLGOaE9QFg~575WTD=YFD6k<+_Z*qiTwCg1wtzEV<>x@o)t-9}1 zh0X6t|Mb?mRj;1<;*~RBzNzrj26L(P#!^4&UQ`Y7fE25$wxgxB71H~r#zxp7VV$53 z*fTpj+d4X1+B=%sIvU&Bp@Y%OD)Wj<`v#`)Cuhc*21mt~mX_Mu+KS4`LMAOfH_bmK z$~4A9#!q*T{k8SRXI822Uvz8h+^g$moL@ct)R(WE{_^#kKYXWme1qwwpLC^e`q|~N zI90Ws&8@A?%}tFB4Or9g)^)UZbhLGJw6wQBY#L-P?23+_DfwmKC#tTl9uR=T0>=S} zS}8mo+PujfT`wb?)zv>RSW!_Sc?`=wz=%PLIvK%7H(u~Uj1m*|!jJ(Hjfnd==oB^F zVgN6y3`F(r69yU@J6i8}oA1=Qil3|s?AW7!ZI8;;-}NQd1AGUHhwM<;Z>hM~>?S<= zr|I}=z|Ngu1IpfefSg`}|Tc=H5L z&1ieYAmjZuazAMuTdJ`ALxtZq*031a}$6T z*nz37t+lPKxuvBM3`kQmB>#eXVIjM`yYD4sjCj+at3YCpS99Qy%PeB%k_)18lB^Pa zv|=3YgqR(+yZN)uiRFqrKf1MXt>N*LQJPZGMp2A>Xj*kcYZElBwXL}oYZ}nvDNU=c z7cwijFYWRfAV3CuYd8zWBosEV8~XP7H=%hMeP~H}SsDC=Xa2P$0GOO02gC)pUnDYc z4b+K+9%INDt#un5=!e4qe4DVlN}e)2D@Y4e^EXaGbkZvi+SvcpCK4&7LHeEt7-^xVi8FB~86-M|h6eXOmm4cs`7$77Y2 zkm%%WDv6kv;*t`4JIM5q^Q|3j*KfzzW>9IFRAwf%sGy{*n9DD(uI1JV1r1HY7Sz*P zu|qN5wHg{5g$)gjO-=c%(t#=aP1dIgng+u0(c9Y#T^^2^d_KRVjGd2~mLE?_b59Lb z3AH@xthn9v+LZ{yWHLF6%FLh^<+I9)IQ%jJYFd@BslIt!)7smClhCMop%DHuD>yIn zT=*v>>2~QPHfyqe1iE)C3FCH17kxo?FK#7U|NVPM7 z8jff1cCfXuf5KOZo-#U8URULs9Cpa?<_^uvyEG9yFzX?MTaVqq4hcRGJ0O?Yse2Vs z!{i1UaIsK80qnr>VU6%1<8AoAIcPF<`bWr^AoyZ{AYgjos{$|H*w|QARaIJ6N@LK| z3v>K3qtht4#Vl4)aWRcqlt-l})X&P%9?D5bv$fyJYL0Nejc&jXOo_}U^LSY6p4T8=rBGDL0d4*9Kxrro7 zUNIY*Ml0vAc!J8BI)Gz+b4z_wv!J%Fp}D!3!|R{QpZT1iCn5ubz(We)y&7NmxbWvp zMhFj25Z7r@6^NA>1b$!SEA zRsa88Q-zGjHKE%>uN$ixKt@wnU(exinZ+y;g%+Qc=M|mc5+0jaNTHTj0>%HQkny;l z)-)gp&;)Ehm&Yq+mE==ti8&;n*hHtW=-AwX0(Lo&=4Cc*JTh<=JX077o-GLvCa0-H z21tZvh)z!M(GLs_wsv*ovT51@_J<8`{-GngTML;BAjMXd3tR6)NFi__yMYxxu&T!& z#y1X_-MZ+cm0!k$} z8Z@oGzMfZARZ_{})zm^~d-=$iyl@;mQ#c5oDa?eS3lU5)GXB2$hX%{*s*>q>zUeX6 zF}~XV_R8+2H=XpaJLt$c>D_SFS8z8{^|vvN_VUh*$z)U7d;ZM})BaofpFqZVK^Fqy z2WseP@b_hQs;Soara@{0wC9!I6Txh(BIwH t+tu6C)rUuK-(WwQzl#jY|4E6({~s~8{7C2>nJ54N002ovPDHLkV1nl9H>3an diff --git a/public/images/leftbar-90.png b/public/images/leftbar-90.png index f341c6e2a40b92b571ea77d5a34cdf60923ae181..e033c3dd49853ccbdbc6a0cd8daf302ece550065 100644 GIT binary patch literal 56718 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

  • m%((dY_Dm^Kb7U$k`c7)<1R)WRUGRz4)S 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� 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 bPtX4WZ)$h2qR92O00000NkvXXu0mjfuDGh0 diff --git a/public/images/server-200.png b/public/images/server-200.png new file mode 100644 index 0000000000000000000000000000000000000000..74ae293b7d149ae1ff96fc5e87fd2b469893a339 GIT binary patch literal 51471 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 literal 0 HcmV?d00001 diff --git a/public/scripts/charts.js b/public/scripts/charts.js new file mode 100644 index 00000000..f6083159 --- /dev/null +++ b/public/scripts/charts.js @@ -0,0 +1,18923 @@ +/*! + * Chart.js + * http://chartjs.org/ + * Version: 2.7.2 + * + * Copyright 2018 Chart.js Contributors + * Released under the MIT license + * https://github.com/chartjs/Chart.js/blob/master/LICENSE.md + */ +(function (f) { if (typeof exports === "object" && typeof module !== "undefined") { module.exports = f() } else if (typeof define === "function" && define.amd) { define([], f) } else { var g; if (typeof window !== "undefined") { g = window } else if (typeof global !== "undefined") { g = global } else if (typeof self !== "undefined") { g = self } else { g = this } g.Chart = f() } })(function () { + var define, module, exports; return (function () { function e(t, n, r) { function s(o, u) { if (!n[o]) { if (!t[o]) { var a = typeof require == "function" && require; if (!u && a) return a(o, !0); if (i) return i(o, !0); var f = new Error("Cannot find module '" + o + "'"); throw f.code = "MODULE_NOT_FOUND", f } var l = n[o] = { exports: {} }; t[o][0].call(l.exports, function (e) { var n = t[o][1][e]; return s(n ? n : e) }, l, l.exports, e, t, n, r) } return n[o].exports } var i = typeof require == "function" && require; for (var o = 0; o < r.length; o++)s(r[o]); return s } return e })()({ + 1: [function (require, module, exports) { + /* MIT license */ + var colorNames = require(5); + + module.exports = { + 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})$/i, + hex = /^#([a-fA-F0-9]{6})$/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); + if (match) { + match = match[1]; + for (var i = 0; i < rgb.length; i++) { + rgb[i] = parseInt(match[i] + match[i], 16); + } + } + else if (match = string.match(hex)) { + match = match[1]; + for (var i = 0; i < rgb.length; i++) { + rgb[i] = parseInt(match.slice(i * 2, i * 2 + 2), 16); + } + } + 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 = colorNames[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(rgb) { + return "#" + hexDouble(rgb[0]) + hexDouble(rgb[1]) + + hexDouble(rgb[2]); + } + + 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 colorNames) { + reverseNames[colorNames[name]] = name; + } + + }, { "5": 5 }], 2: [function (require, module, exports) { + /* MIT license */ + var convert = require(4); + var string = require(1); + + 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 = string.getRgba(obj); + if (vals) { + this.setValues('rgb', vals); + } else if (vals = string.getHsla(obj)) { + this.setValues('hsl', vals); + } else if (vals = string.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 string.hexString(this.values.rgb); + }, + rgbString: function () { + return string.rgbString(this.values.rgb, this.values.alpha); + }, + rgbaString: function () { + return string.rgbaString(this.values.rgb, this.values.alpha); + }, + percentString: function () { + return string.percentString(this.values.rgb, this.values.alpha); + }, + hslString: function () { + return string.hslString(this.values.hsl, this.values.alpha); + }, + hslaString: function () { + return string.hslaString(this.values.hsl, this.values.alpha); + }, + hwbString: function () { + return string.hwbString(this.values.hwb, this.values.alpha); + }, + keyword: function () { + return string.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] = convert[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; + } + + module.exports = Color; + + }, { "1": 1, "4": 4 }], 3: [function (require, module, exports) { + /* MIT license */ + + module.exports = { + rgb2hsl: rgb2hsl, + rgb2hsv: rgb2hsv, + rgb2hwb: rgb2hwb, + rgb2cmyk: rgb2cmyk, + rgb2keyword: rgb2keyword, + rgb2xyz: rgb2xyz, + rgb2lab: rgb2lab, + rgb2lch: rgb2lch, + + hsl2rgb: hsl2rgb, + hsl2hsv: hsl2hsv, + hsl2hwb: hsl2hwb, + hsl2cmyk: hsl2cmyk, + hsl2keyword: hsl2keyword, + + hsv2rgb: hsv2rgb, + hsv2hsl: hsv2hsl, + hsv2hwb: hsv2hwb, + hsv2cmyk: hsv2cmyk, + hsv2keyword: hsv2keyword, + + hwb2rgb: hwb2rgb, + hwb2hsl: hwb2hsl, + hwb2hsv: hwb2hsv, + hwb2cmyk: hwb2cmyk, + hwb2keyword: hwb2keyword, + + cmyk2rgb: cmyk2rgb, + cmyk2hsl: cmyk2hsl, + cmyk2hsv: cmyk2hsv, + cmyk2hwb: cmyk2hwb, + cmyk2keyword: cmyk2keyword, + + keyword2rgb: keyword2rgb, + keyword2hsl: keyword2hsl, + keyword2hsv: keyword2hsv, + keyword2hwb: keyword2hwb, + keyword2cmyk: keyword2cmyk, + keyword2lab: keyword2lab, + keyword2xyz: keyword2xyz, + + xyz2rgb: xyz2rgb, + xyz2lab: xyz2lab, + xyz2lch: xyz2lch, + + lab2xyz: lab2xyz, + lab2rgb: lab2rgb, + lab2lch: lab2lch, + + lch2lab: lch2lab, + lch2xyz: lch2xyz, + lch2rgb: lch2rgb + } + + + function rgb2hsl(rgb) { + var r = rgb[0] / 255, + g = rgb[1] / 255, + b = rgb[2] / 255, + min = Math.min(r, g, b), + max = Math.max(r, g, b), + delta = max - min, + h, s, l; + + if (max == min) + h = 0; + else if (r == max) + h = (g - b) / delta; + else if (g == max) + h = 2 + (b - r) / delta; + else if (b == max) + h = 4 + (r - g) / delta; + + h = Math.min(h * 60, 360); + + if (h < 0) + h += 360; + + l = (min + max) / 2; + + if (max == min) + s = 0; + else if (l <= 0.5) + s = delta / (max + min); + else + s = delta / (2 - max - min); + + return [h, s * 100, l * 100]; + } + + function rgb2hsv(rgb) { + var r = rgb[0], + g = rgb[1], + b = rgb[2], + min = Math.min(r, g, b), + max = Math.max(r, g, b), + delta = max - min, + h, s, v; + + if (max == 0) + s = 0; + else + s = (delta / max * 1000) / 10; + + if (max == min) + h = 0; + else if (r == max) + h = (g - b) / delta; + else if (g == max) + h = 2 + (b - r) / delta; + else if (b == max) + h = 4 + (r - g) / delta; + + h = Math.min(h * 60, 360); + + if (h < 0) + h += 360; + + v = ((max / 255) * 1000) / 10; + + return [h, s, v]; + } + + function rgb2hwb(rgb) { + var r = rgb[0], + g = rgb[1], + b = rgb[2], + h = rgb2hsl(rgb)[0], + w = 1 / 255 * Math.min(r, Math.min(g, b)), + b = 1 - 1 / 255 * Math.max(r, Math.max(g, b)); + + return [h, w * 100, b * 100]; + } + + function rgb2cmyk(rgb) { + var r = rgb[0] / 255, + g = rgb[1] / 255, + b = rgb[2] / 255, + c, m, y, 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]; + } + + function rgb2keyword(rgb) { + return reverseKeywords[JSON.stringify(rgb)]; + } + + function rgb2xyz(rgb) { + var r = rgb[0] / 255, + g = rgb[1] / 255, + 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]; + } + + function rgb2lab(rgb) { + var xyz = rgb2xyz(rgb), + x = xyz[0], + y = xyz[1], + z = xyz[2], + l, a, 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]; + } + + function rgb2lch(args) { + return lab2lch(rgb2lab(args)); + } + + function hsl2rgb(hsl) { + var h = hsl[0] / 360, + s = hsl[1] / 100, + l = hsl[2] / 100, + t1, t2, t3, rgb, 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); + t3 < 0 && t3++; + 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; + } + + function hsl2hsv(hsl) { + var h = hsl[0], + s = hsl[1] / 100, + l = hsl[2] / 100, + sv, v; + + if (l === 0) { + // no need to do calc on black + // also avoids divide by 0 error + return [0, 0, 0]; + } + + l *= 2; + s *= (l <= 1) ? l : 2 - l; + v = (l + s) / 2; + sv = (2 * s) / (l + s); + return [h, sv * 100, v * 100]; + } + + function hsl2hwb(args) { + return rgb2hwb(hsl2rgb(args)); + } + + function hsl2cmyk(args) { + return rgb2cmyk(hsl2rgb(args)); + } + + function hsl2keyword(args) { + return rgb2keyword(hsl2rgb(args)); + } + + + function hsv2rgb(hsv) { + var h = hsv[0] / 60, + s = hsv[1] / 100, + v = hsv[2] / 100, + hi = Math.floor(h) % 6; + + var f = h - Math.floor(h), + p = 255 * v * (1 - s), + q = 255 * v * (1 - (s * f)), + t = 255 * v * (1 - (s * (1 - f))), + v = 255 * v; + + switch (hi) { + case 0: + return [v, t, p]; + case 1: + return [q, v, p]; + case 2: + return [p, v, t]; + case 3: + return [p, q, v]; + case 4: + return [t, p, v]; + case 5: + return [v, p, q]; + } + } + + function hsv2hsl(hsv) { + var h = hsv[0], + s = hsv[1] / 100, + v = hsv[2] / 100, + sl, l; + + l = (2 - s) * v; + sl = s * v; + sl /= (l <= 1) ? l : 2 - l; + sl = sl || 0; + l /= 2; + return [h, sl * 100, l * 100]; + } + + function hsv2hwb(args) { + return rgb2hwb(hsv2rgb(args)) + } + + function hsv2cmyk(args) { + return rgb2cmyk(hsv2rgb(args)); + } + + function hsv2keyword(args) { + return rgb2keyword(hsv2rgb(args)); + } + + // http://dev.w3.org/csswg/css-color/#hwb-to-rgb + function hwb2rgb(hwb) { + var h = hwb[0] / 360, + wh = hwb[1] / 100, + bl = hwb[2] / 100, + ratio = wh + bl, + i, v, f, n; + + // 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 + + 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]; + } + + function hwb2hsl(args) { + return rgb2hsl(hwb2rgb(args)); + } + + function hwb2hsv(args) { + return rgb2hsv(hwb2rgb(args)); + } + + function hwb2cmyk(args) { + return rgb2cmyk(hwb2rgb(args)); + } + + function hwb2keyword(args) { + return rgb2keyword(hwb2rgb(args)); + } + + function cmyk2rgb(cmyk) { + var c = cmyk[0] / 100, + m = cmyk[1] / 100, + y = cmyk[2] / 100, + k = cmyk[3] / 100, + r, g, 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]; + } + + function cmyk2hsl(args) { + return rgb2hsl(cmyk2rgb(args)); + } + + function cmyk2hsv(args) { + return rgb2hsv(cmyk2rgb(args)); + } + + function cmyk2hwb(args) { + return rgb2hwb(cmyk2rgb(args)); + } + + function cmyk2keyword(args) { + return rgb2keyword(cmyk2rgb(args)); + } + + + function xyz2rgb(xyz) { + var x = xyz[0] / 100, + y = xyz[1] / 100, + z = xyz[2] / 100, + r, g, 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 = (r * 12.92); + + g = g > 0.0031308 ? ((1.055 * Math.pow(g, 1.0 / 2.4)) - 0.055) + : g = (g * 12.92); + + b = b > 0.0031308 ? ((1.055 * Math.pow(b, 1.0 / 2.4)) - 0.055) + : b = (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]; + } + + function xyz2lab(xyz) { + var x = xyz[0], + y = xyz[1], + z = xyz[2], + l, a, 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]; + } + + function xyz2lch(args) { + return lab2lch(xyz2lab(args)); + } + + function lab2xyz(lab) { + var l = lab[0], + a = lab[1], + b = lab[2], + x, y, z, y2; + + if (l <= 8) { + y = (l * 100) / 903.3; + y2 = (7.787 * (y / 100)) + (16 / 116); + } else { + y = 100 * Math.pow((l + 16) / 116, 3); + y2 = Math.pow(y / 100, 1 / 3); + } + + x = x / 95.047 <= 0.008856 ? x = (95.047 * ((a / 500) + y2 - (16 / 116))) / 7.787 : 95.047 * Math.pow((a / 500) + y2, 3); + + z = z / 108.883 <= 0.008859 ? z = (108.883 * (y2 - (b / 200) - (16 / 116))) / 7.787 : 108.883 * Math.pow(y2 - (b / 200), 3); + + return [x, y, z]; + } + + function lab2lch(lab) { + var l = lab[0], + a = lab[1], + b = lab[2], + hr, h, 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]; + } + + function lab2rgb(args) { + return xyz2rgb(lab2xyz(args)); + } + + function lch2lab(lch) { + var l = lch[0], + c = lch[1], + h = lch[2], + a, b, hr; + + hr = h / 360 * 2 * Math.PI; + a = c * Math.cos(hr); + b = c * Math.sin(hr); + return [l, a, b]; + } + + function lch2xyz(args) { + return lab2xyz(lch2lab(args)); + } + + function lch2rgb(args) { + return lab2rgb(lch2lab(args)); + } + + function keyword2rgb(keyword) { + return cssKeywords[keyword]; + } + + function keyword2hsl(args) { + return rgb2hsl(keyword2rgb(args)); + } + + function keyword2hsv(args) { + return rgb2hsv(keyword2rgb(args)); + } + + function keyword2hwb(args) { + return rgb2hwb(keyword2rgb(args)); + } + + function keyword2cmyk(args) { + return rgb2cmyk(keyword2rgb(args)); + } + + function keyword2lab(args) { + return rgb2lab(keyword2rgb(args)); + } + + function keyword2xyz(args) { + return rgb2xyz(keyword2rgb(args)); + } + + var cssKeywords = { + aliceblue: [240, 248, 255], + antiquewhite: [250, 235, 215], + aqua: [0, 255, 255], + aquamarine: [127, 255, 212], + azure: [240, 255, 255], + beige: [245, 245, 220], + bisque: [255, 228, 196], + black: [0, 0, 0], + blanchedalmond: [255, 235, 205], + blue: [0, 0, 255], + blueviolet: [138, 43, 226], + brown: [165, 42, 42], + burlywood: [222, 184, 135], + cadetblue: [95, 158, 160], + chartreuse: [127, 255, 0], + chocolate: [210, 105, 30], + coral: [255, 127, 80], + cornflowerblue: [100, 149, 237], + cornsilk: [255, 248, 220], + crimson: [220, 20, 60], + cyan: [0, 255, 255], + darkblue: [0, 0, 139], + darkcyan: [0, 139, 139], + darkgoldenrod: [184, 134, 11], + darkgray: [169, 169, 169], + darkgreen: [0, 100, 0], + darkgrey: [169, 169, 169], + darkkhaki: [189, 183, 107], + darkmagenta: [139, 0, 139], + darkolivegreen: [85, 107, 47], + darkorange: [255, 140, 0], + darkorchid: [153, 50, 204], + darkred: [139, 0, 0], + darksalmon: [233, 150, 122], + darkseagreen: [143, 188, 143], + darkslateblue: [72, 61, 139], + darkslategray: [47, 79, 79], + darkslategrey: [47, 79, 79], + darkturquoise: [0, 206, 209], + darkviolet: [148, 0, 211], + deeppink: [255, 20, 147], + deepskyblue: [0, 191, 255], + dimgray: [105, 105, 105], + dimgrey: [105, 105, 105], + dodgerblue: [30, 144, 255], + firebrick: [178, 34, 34], + floralwhite: [255, 250, 240], + forestgreen: [34, 139, 34], + fuchsia: [255, 0, 255], + gainsboro: [220, 220, 220], + ghostwhite: [248, 248, 255], + gold: [255, 215, 0], + goldenrod: [218, 165, 32], + gray: [128, 128, 128], + green: [0, 128, 0], + greenyellow: [173, 255, 47], + grey: [128, 128, 128], + honeydew: [240, 255, 240], + hotpink: [255, 105, 180], + indianred: [205, 92, 92], + indigo: [75, 0, 130], + ivory: [255, 255, 240], + khaki: [240, 230, 140], + lavender: [230, 230, 250], + lavenderblush: [255, 240, 245], + lawngreen: [124, 252, 0], + lemonchiffon: [255, 250, 205], + lightblue: [173, 216, 230], + lightcoral: [240, 128, 128], + lightcyan: [224, 255, 255], + lightgoldenrodyellow: [250, 250, 210], + lightgray: [211, 211, 211], + lightgreen: [144, 238, 144], + lightgrey: [211, 211, 211], + lightpink: [255, 182, 193], + lightsalmon: [255, 160, 122], + lightseagreen: [32, 178, 170], + lightskyblue: [135, 206, 250], + lightslategray: [119, 136, 153], + lightslategrey: [119, 136, 153], + lightsteelblue: [176, 196, 222], + lightyellow: [255, 255, 224], + lime: [0, 255, 0], + limegreen: [50, 205, 50], + linen: [250, 240, 230], + magenta: [255, 0, 255], + maroon: [128, 0, 0], + mediumaquamarine: [102, 205, 170], + mediumblue: [0, 0, 205], + mediumorchid: [186, 85, 211], + mediumpurple: [147, 112, 219], + mediumseagreen: [60, 179, 113], + mediumslateblue: [123, 104, 238], + mediumspringgreen: [0, 250, 154], + mediumturquoise: [72, 209, 204], + mediumvioletred: [199, 21, 133], + midnightblue: [25, 25, 112], + mintcream: [245, 255, 250], + mistyrose: [255, 228, 225], + moccasin: [255, 228, 181], + navajowhite: [255, 222, 173], + navy: [0, 0, 128], + oldlace: [253, 245, 230], + olive: [128, 128, 0], + olivedrab: [107, 142, 35], + orange: [255, 165, 0], + orangered: [255, 69, 0], + orchid: [218, 112, 214], + palegoldenrod: [238, 232, 170], + palegreen: [152, 251, 152], + paleturquoise: [175, 238, 238], + palevioletred: [219, 112, 147], + papayawhip: [255, 239, 213], + peachpuff: [255, 218, 185], + peru: [205, 133, 63], + pink: [255, 192, 203], + plum: [221, 160, 221], + powderblue: [176, 224, 230], + purple: [128, 0, 128], + rebeccapurple: [102, 51, 153], + red: [255, 0, 0], + rosybrown: [188, 143, 143], + royalblue: [65, 105, 225], + saddlebrown: [139, 69, 19], + salmon: [250, 128, 114], + sandybrown: [244, 164, 96], + seagreen: [46, 139, 87], + seashell: [255, 245, 238], + sienna: [160, 82, 45], + silver: [192, 192, 192], + skyblue: [135, 206, 235], + slateblue: [106, 90, 205], + slategray: [112, 128, 144], + slategrey: [112, 128, 144], + snow: [255, 250, 250], + springgreen: [0, 255, 127], + steelblue: [70, 130, 180], + tan: [210, 180, 140], + teal: [0, 128, 128], + thistle: [216, 191, 216], + tomato: [255, 99, 71], + turquoise: [64, 224, 208], + violet: [238, 130, 238], + wheat: [245, 222, 179], + white: [255, 255, 255], + whitesmoke: [245, 245, 245], + yellow: [255, 255, 0], + yellowgreen: [154, 205, 50] + }; + + var reverseKeywords = {}; + for (var key in cssKeywords) { + reverseKeywords[JSON.stringify(cssKeywords[key])] = key; + } + + }, {}], 4: [function (require, module, exports) { + var conversions = require(3); + + var convert = function () { + return new Converter(); + } + + for (var func in conversions) { + // export Raw versions + convert[func + "Raw"] = (function (func) { + // accept array or plain args + return function (arg) { + if (typeof arg == "number") + arg = Array.prototype.slice.call(arguments); + return conversions[func](arg); + } + })(func); + + var pair = /(\w+)2(\w+)/.exec(func), + from = pair[1], + to = pair[2]; + + // export rgb2hsl and ["rgb"]["hsl"] + convert[from] = convert[from] || {}; + + convert[from][to] = convert[func] = (function (func) { + return function (arg) { + if (typeof arg == "number") + arg = Array.prototype.slice.call(arguments); + + var val = conversions[func](arg); + if (typeof val == "string" || val === undefined) + return val; // keyword + + for (var i = 0; i < val.length; i++) + val[i] = Math.round(val[i]); + return val; + } + })(func); + } + + + /* Converter does lazy conversion and caching */ + var Converter = function () { + this.convs = {}; + }; + + /* Either get the values for a space or + set the values for a space, depending on args */ + Converter.prototype.routeSpace = function (space, args) { + var values = args[0]; + if (values === undefined) { + // color.rgb() + return this.getValues(space); + } + // color.rgb(10, 10, 10) + if (typeof values == "number") { + values = Array.prototype.slice.call(args); + } + + return this.setValues(space, values); + }; + + /* Set the values for a space, invalidating cache */ + Converter.prototype.setValues = function (space, values) { + this.space = space; + this.convs = {}; + this.convs[space] = values; + return this; + }; + + /* Get the values for a space. If there's already + a conversion for the space, fetch it, otherwise + compute it */ + Converter.prototype.getValues = function (space) { + var vals = this.convs[space]; + if (!vals) { + var fspace = this.space, + from = this.convs[fspace]; + vals = convert[fspace][space](from); + + this.convs[space] = vals; + } + return vals; + }; + + ["rgb", "hsl", "hsv", "cmyk", "keyword"].forEach(function (space) { + Converter.prototype[space] = function (vals) { + return this.routeSpace(space, arguments); + } + }); + + module.exports = convert; + }, { "3": 3 }], 5: [function (require, module, exports) { + 'use strict' + + module.exports = { + "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] + }; + + }, {}], 6: [function (require, module, exports) { + //! moment.js + //! version : 2.20.1 + //! authors : Tim Wood, Iskren Chernev, Moment.js contributors + //! license : MIT + //! momentjs.com + + ; (function (global, factory) { + typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() : + typeof define === 'function' && define.amd ? define(factory) : + global.moment = factory() + } (this, (function () { + 'use strict'; + + var hookCallback; + + function hooks() { + return hookCallback.apply(null, arguments); + } + + // This is done to register the method called with moment() + // without creating circular dependencies. + function setHookCallback(callback) { + hookCallback = callback; + } + + function isArray(input) { + return input instanceof Array || Object.prototype.toString.call(input) === '[object Array]'; + } + + function isObject(input) { + // IE8 will treat undefined and null as object if it wasn't for + // input != null + return input != null && Object.prototype.toString.call(input) === '[object Object]'; + } + + function isObjectEmpty(obj) { + if (Object.getOwnPropertyNames) { + return (Object.getOwnPropertyNames(obj).length === 0); + } else { + var k; + for (k in obj) { + if (obj.hasOwnProperty(k)) { + return false; + } + } + return true; + } + } + + function isUndefined(input) { + return input === void 0; + } + + function isNumber(input) { + return typeof input === 'number' || Object.prototype.toString.call(input) === '[object Number]'; + } + + function isDate(input) { + return input instanceof Date || Object.prototype.toString.call(input) === '[object Date]'; + } + + function map(arr, fn) { + var res = [], i; + for (i = 0; i < arr.length; ++i) { + res.push(fn(arr[i], i)); + } + return res; + } + + function hasOwnProp(a, b) { + return Object.prototype.hasOwnProperty.call(a, b); + } + + function extend(a, b) { + for (var i in b) { + if (hasOwnProp(b, i)) { + a[i] = b[i]; + } + } + + if (hasOwnProp(b, 'toString')) { + a.toString = b.toString; + } + + if (hasOwnProp(b, 'valueOf')) { + a.valueOf = b.valueOf; + } + + return a; + } + + function createUTC(input, format, locale, strict) { + return createLocalOrUTC(input, format, locale, strict, true).utc(); + } + + function defaultParsingFlags() { + // We need to deep clone this object. + return { + empty: false, + unusedTokens: [], + unusedInput: [], + overflow: -2, + charsLeftOver: 0, + nullInput: false, + invalidMonth: null, + invalidFormat: false, + userInvalidated: false, + iso: false, + parsedDateParts: [], + meridiem: null, + rfc2822: false, + weekdayMismatch: false + }; + } + + function getParsingFlags(m) { + if (m._pf == null) { + m._pf = defaultParsingFlags(); + } + return m._pf; + } + + var some; + if (Array.prototype.some) { + some = Array.prototype.some; + } else { + some = function (fun) { + var t = Object(this); + var len = t.length >>> 0; + + for (var i = 0; i < len; i++) { + if (i in t && fun.call(this, t[i], i, t)) { + return true; + } + } + + return false; + }; + } + + function isValid(m) { + if (m._isValid == null) { + var flags = getParsingFlags(m); + var parsedParts = some.call(flags.parsedDateParts, function (i) { + return i != null; + }); + var isNowValid = !isNaN(m._d.getTime()) && + flags.overflow < 0 && + !flags.empty && + !flags.invalidMonth && + !flags.invalidWeekday && + !flags.weekdayMismatch && + !flags.nullInput && + !flags.invalidFormat && + !flags.userInvalidated && + (!flags.meridiem || (flags.meridiem && parsedParts)); + + if (m._strict) { + isNowValid = isNowValid && + flags.charsLeftOver === 0 && + flags.unusedTokens.length === 0 && + flags.bigHour === undefined; + } + + if (Object.isFrozen == null || !Object.isFrozen(m)) { + m._isValid = isNowValid; + } + else { + return isNowValid; + } + } + return m._isValid; + } + + function createInvalid(flags) { + var m = createUTC(NaN); + if (flags != null) { + extend(getParsingFlags(m), flags); + } + else { + getParsingFlags(m).userInvalidated = true; + } + + return m; + } + + // Plugins that add properties should also add the key here (null value), + // so we can properly clone ourselves. + var momentProperties = hooks.momentProperties = []; + + function copyConfig(to, from) { + var i, prop, val; + + if (!isUndefined(from._isAMomentObject)) { + to._isAMomentObject = from._isAMomentObject; + } + if (!isUndefined(from._i)) { + to._i = from._i; + } + if (!isUndefined(from._f)) { + to._f = from._f; + } + if (!isUndefined(from._l)) { + to._l = from._l; + } + if (!isUndefined(from._strict)) { + to._strict = from._strict; + } + if (!isUndefined(from._tzm)) { + to._tzm = from._tzm; + } + if (!isUndefined(from._isUTC)) { + to._isUTC = from._isUTC; + } + if (!isUndefined(from._offset)) { + to._offset = from._offset; + } + if (!isUndefined(from._pf)) { + to._pf = getParsingFlags(from); + } + if (!isUndefined(from._locale)) { + to._locale = from._locale; + } + + if (momentProperties.length > 0) { + for (i = 0; i < momentProperties.length; i++) { + prop = momentProperties[i]; + val = from[prop]; + if (!isUndefined(val)) { + to[prop] = val; + } + } + } + + return to; + } + + var updateInProgress = false; + + // Moment prototype object + function Moment(config) { + copyConfig(this, config); + this._d = new Date(config._d != null ? config._d.getTime() : NaN); + if (!this.isValid()) { + this._d = new Date(NaN); + } + // Prevent infinite loop in case updateOffset creates new moment + // objects. + if (updateInProgress === false) { + updateInProgress = true; + hooks.updateOffset(this); + updateInProgress = false; + } + } + + function isMoment(obj) { + return obj instanceof Moment || (obj != null && obj._isAMomentObject != null); + } + + function absFloor(number) { + if (number < 0) { + // -0 -> 0 + return Math.ceil(number) || 0; + } else { + return Math.floor(number); + } + } + + function toInt(argumentForCoercion) { + var coercedNumber = +argumentForCoercion, + value = 0; + + if (coercedNumber !== 0 && isFinite(coercedNumber)) { + value = absFloor(coercedNumber); + } + + return value; + } + + // compare two arrays, return the number of differences + function compareArrays(array1, array2, dontConvert) { + var len = Math.min(array1.length, array2.length), + lengthDiff = Math.abs(array1.length - array2.length), + diffs = 0, + i; + for (i = 0; i < len; i++) { + if ((dontConvert && array1[i] !== array2[i]) || + (!dontConvert && toInt(array1[i]) !== toInt(array2[i]))) { + diffs++; + } + } + return diffs + lengthDiff; + } + + function warn(msg) { + if (hooks.suppressDeprecationWarnings === false && + (typeof console !== 'undefined') && console.warn) { + console.warn('Deprecation warning: ' + msg); + } + } + + function deprecate(msg, fn) { + var firstTime = true; + + return extend(function () { + if (hooks.deprecationHandler != null) { + hooks.deprecationHandler(null, msg); + } + if (firstTime) { + var args = []; + var arg; + for (var i = 0; i < arguments.length; i++) { + arg = ''; + if (typeof arguments[i] === 'object') { + arg += '\n[' + i + '] '; + for (var key in arguments[0]) { + arg += key + ': ' + arguments[0][key] + ', '; + } + arg = arg.slice(0, -2); // Remove trailing comma and space + } else { + arg = arguments[i]; + } + args.push(arg); + } + warn(msg + '\nArguments: ' + Array.prototype.slice.call(args).join('') + '\n' + (new Error()).stack); + firstTime = false; + } + return fn.apply(this, arguments); + }, fn); + } + + var deprecations = {}; + + function deprecateSimple(name, msg) { + if (hooks.deprecationHandler != null) { + hooks.deprecationHandler(name, msg); + } + if (!deprecations[name]) { + warn(msg); + deprecations[name] = true; + } + } + + hooks.suppressDeprecationWarnings = false; + hooks.deprecationHandler = null; + + function isFunction(input) { + return input instanceof Function || Object.prototype.toString.call(input) === '[object Function]'; + } + + function set(config) { + var prop, i; + for (i in config) { + prop = config[i]; + if (isFunction(prop)) { + this[i] = prop; + } else { + this['_' + i] = prop; + } + } + this._config = config; + // Lenient ordinal parsing accepts just a number in addition to + // number + (possibly) stuff coming from _dayOfMonthOrdinalParse. + // TODO: Remove "ordinalParse" fallback in next major release. + this._dayOfMonthOrdinalParseLenient = new RegExp( + (this._dayOfMonthOrdinalParse.source || this._ordinalParse.source) + + '|' + (/\d{1,2}/).source); + } + + function mergeConfigs(parentConfig, childConfig) { + var res = extend({}, parentConfig), prop; + for (prop in childConfig) { + if (hasOwnProp(childConfig, prop)) { + if (isObject(parentConfig[prop]) && isObject(childConfig[prop])) { + res[prop] = {}; + extend(res[prop], parentConfig[prop]); + extend(res[prop], childConfig[prop]); + } else if (childConfig[prop] != null) { + res[prop] = childConfig[prop]; + } else { + delete res[prop]; + } + } + } + for (prop in parentConfig) { + if (hasOwnProp(parentConfig, prop) && + !hasOwnProp(childConfig, prop) && + isObject(parentConfig[prop])) { + // make sure changes to properties don't modify parent config + res[prop] = extend({}, res[prop]); + } + } + return res; + } + + function Locale(config) { + if (config != null) { + this.set(config); + } + } + + var keys; + + if (Object.keys) { + keys = Object.keys; + } else { + keys = function (obj) { + var i, res = []; + for (i in obj) { + if (hasOwnProp(obj, i)) { + res.push(i); + } + } + return res; + }; + } + + var defaultCalendar = { + sameDay: '[Today at] LT', + nextDay: '[Tomorrow at] LT', + nextWeek: 'dddd [at] LT', + lastDay: '[Yesterday at] LT', + lastWeek: '[Last] dddd [at] LT', + sameElse: 'L' + }; + + function calendar(key, mom, now) { + var output = this._calendar[key] || this._calendar['sameElse']; + return isFunction(output) ? output.call(mom, now) : output; + } + + var defaultLongDateFormat = { + LTS: 'h:mm:ss A', + LT: 'h:mm A', + L: 'MM/DD/YYYY', + LL: 'MMMM D, YYYY', + LLL: 'MMMM D, YYYY h:mm A', + LLLL: 'dddd, MMMM D, YYYY h:mm A' + }; + + function longDateFormat(key) { + var format = this._longDateFormat[key], + formatUpper = this._longDateFormat[key.toUpperCase()]; + + if (format || !formatUpper) { + return format; + } + + this._longDateFormat[key] = formatUpper.replace(/MMMM|MM|DD|dddd/g, function (val) { + return val.slice(1); + }); + + return this._longDateFormat[key]; + } + + var defaultInvalidDate = 'Invalid date'; + + function invalidDate() { + return this._invalidDate; + } + + var defaultOrdinal = '%d'; + var defaultDayOfMonthOrdinalParse = /\d{1,2}/; + + function ordinal(number) { + return this._ordinal.replace('%d', number); + } + + var defaultRelativeTime = { + future: 'in %s', + past: '%s ago', + s: 'a few seconds', + ss: '%d seconds', + m: 'a minute', + mm: '%d minutes', + h: 'an hour', + hh: '%d hours', + d: 'a day', + dd: '%d days', + M: 'a month', + MM: '%d months', + y: 'a year', + yy: '%d years' + }; + + function relativeTime(number, withoutSuffix, string, isFuture) { + var output = this._relativeTime[string]; + return (isFunction(output)) ? + output(number, withoutSuffix, string, isFuture) : + output.replace(/%d/i, number); + } + + function pastFuture(diff, output) { + var format = this._relativeTime[diff > 0 ? 'future' : 'past']; + return isFunction(format) ? format(output) : format.replace(/%s/i, output); + } + + var aliases = {}; + + function addUnitAlias(unit, shorthand) { + var lowerCase = unit.toLowerCase(); + aliases[lowerCase] = aliases[lowerCase + 's'] = aliases[shorthand] = unit; + } + + function normalizeUnits(units) { + return typeof units === 'string' ? aliases[units] || aliases[units.toLowerCase()] : undefined; + } + + function normalizeObjectUnits(inputObject) { + var normalizedInput = {}, + normalizedProp, + prop; + + for (prop in inputObject) { + if (hasOwnProp(inputObject, prop)) { + normalizedProp = normalizeUnits(prop); + if (normalizedProp) { + normalizedInput[normalizedProp] = inputObject[prop]; + } + } + } + + return normalizedInput; + } + + var priorities = {}; + + function addUnitPriority(unit, priority) { + priorities[unit] = priority; + } + + function getPrioritizedUnits(unitsObj) { + var units = []; + for (var u in unitsObj) { + units.push({ unit: u, priority: priorities[u] }); + } + units.sort(function (a, b) { + return a.priority - b.priority; + }); + return units; + } + + function zeroFill(number, targetLength, forceSign) { + var absNumber = '' + Math.abs(number), + zerosToFill = targetLength - absNumber.length, + sign = number >= 0; + return (sign ? (forceSign ? '+' : '') : '-') + + Math.pow(10, Math.max(0, zerosToFill)).toString().substr(1) + absNumber; + } + + var formattingTokens = /(\[[^\[]*\])|(\\)?([Hh]mm(ss)?|Mo|MM?M?M?|Do|DDDo|DD?D?D?|ddd?d?|do?|w[o|w]?|W[o|W]?|Qo?|YYYYYY|YYYYY|YYYY|YY|gg(ggg?)?|GG(GGG?)?|e|E|a|A|hh?|HH?|kk?|mm?|ss?|S{1,9}|x|X|zz?|ZZ?|.)/g; + + var localFormattingTokens = /(\[[^\[]*\])|(\\)?(LTS|LT|LL?L?L?|l{1,4})/g; + + var formatFunctions = {}; + + var formatTokenFunctions = {}; + + // token: 'M' + // padded: ['MM', 2] + // ordinal: 'Mo' + // callback: function () { this.month() + 1 } + function addFormatToken(token, padded, ordinal, callback) { + var func = callback; + if (typeof callback === 'string') { + func = function () { + return this[callback](); + }; + } + if (token) { + formatTokenFunctions[token] = func; + } + if (padded) { + formatTokenFunctions[padded[0]] = function () { + return zeroFill(func.apply(this, arguments), padded[1], padded[2]); + }; + } + if (ordinal) { + formatTokenFunctions[ordinal] = function () { + return this.localeData().ordinal(func.apply(this, arguments), token); + }; + } + } + + function removeFormattingTokens(input) { + if (input.match(/\[[\s\S]/)) { + return input.replace(/^\[|\]$/g, ''); + } + return input.replace(/\\/g, ''); + } + + function makeFormatFunction(format) { + var array = format.match(formattingTokens), i, length; + + for (i = 0, length = array.length; i < length; i++) { + if (formatTokenFunctions[array[i]]) { + array[i] = formatTokenFunctions[array[i]]; + } else { + array[i] = removeFormattingTokens(array[i]); + } + } + + return function (mom) { + var output = '', i; + for (i = 0; i < length; i++) { + output += isFunction(array[i]) ? array[i].call(mom, format) : array[i]; + } + return output; + }; + } + + // format date using native date object + function formatMoment(m, format) { + if (!m.isValid()) { + return m.localeData().invalidDate(); + } + + format = expandFormat(format, m.localeData()); + formatFunctions[format] = formatFunctions[format] || makeFormatFunction(format); + + return formatFunctions[format](m); + } + + function expandFormat(format, locale) { + var i = 5; + + function replaceLongDateFormatTokens(input) { + return locale.longDateFormat(input) || input; + } + + localFormattingTokens.lastIndex = 0; + while (i >= 0 && localFormattingTokens.test(format)) { + format = format.replace(localFormattingTokens, replaceLongDateFormatTokens); + localFormattingTokens.lastIndex = 0; + i -= 1; + } + + return format; + } + + var match1 = /\d/; // 0 - 9 + var match2 = /\d\d/; // 00 - 99 + var match3 = /\d{3}/; // 000 - 999 + var match4 = /\d{4}/; // 0000 - 9999 + var match6 = /[+-]?\d{6}/; // -999999 - 999999 + var match1to2 = /\d\d?/; // 0 - 99 + var match3to4 = /\d\d\d\d?/; // 999 - 9999 + var match5to6 = /\d\d\d\d\d\d?/; // 99999 - 999999 + var match1to3 = /\d{1,3}/; // 0 - 999 + var match1to4 = /\d{1,4}/; // 0 - 9999 + var match1to6 = /[+-]?\d{1,6}/; // -999999 - 999999 + + var matchUnsigned = /\d+/; // 0 - inf + var matchSigned = /[+-]?\d+/; // -inf - inf + + var matchOffset = /Z|[+-]\d\d:?\d\d/gi; // +00:00 -00:00 +0000 -0000 or Z + var matchShortOffset = /Z|[+-]\d\d(?::?\d\d)?/gi; // +00 -00 +00:00 -00:00 +0000 -0000 or Z + + var matchTimestamp = /[+-]?\d+(\.\d{1,3})?/; // 123456789 123456789.123 + + // any word (or two) characters or numbers including two/three word month in arabic. + // includes scottish gaelic two word and hyphenated months + var matchWord = /[0-9]{0,256}['a-z\u00A0-\u05FF\u0700-\uD7FF\uF900-\uFDCF\uFDF0-\uFF07\uFF10-\uFFEF]{1,256}|[\u0600-\u06FF\/]{1,256}(\s*?[\u0600-\u06FF]{1,256}){1,2}/i; + + + var regexes = {}; + + function addRegexToken(token, regex, strictRegex) { + regexes[token] = isFunction(regex) ? regex : function (isStrict, localeData) { + return (isStrict && strictRegex) ? strictRegex : regex; + }; + } + + function getParseRegexForToken(token, config) { + if (!hasOwnProp(regexes, token)) { + return new RegExp(unescapeFormat(token)); + } + + return regexes[token](config._strict, config._locale); + } + + // Code from http://stackoverflow.com/questions/3561493/is-there-a-regexp-escape-function-in-javascript + function unescapeFormat(s) { + return regexEscape(s.replace('\\', '').replace(/\\(\[)|\\(\])|\[([^\]\[]*)\]|\\(.)/g, function (matched, p1, p2, p3, p4) { + return p1 || p2 || p3 || p4; + })); + } + + function regexEscape(s) { + return s.replace(/[-\/\\^$*+?.()|[\]{}]/g, '\\$&'); + } + + var tokens = {}; + + function addParseToken(token, callback) { + var i, func = callback; + if (typeof token === 'string') { + token = [token]; + } + if (isNumber(callback)) { + func = function (input, array) { + array[callback] = toInt(input); + }; + } + for (i = 0; i < token.length; i++) { + tokens[token[i]] = func; + } + } + + function addWeekParseToken(token, callback) { + addParseToken(token, function (input, array, config, token) { + config._w = config._w || {}; + callback(input, config._w, config, token); + }); + } + + function addTimeToArrayFromToken(token, input, config) { + if (input != null && hasOwnProp(tokens, token)) { + tokens[token](input, config._a, config, token); + } + } + + var YEAR = 0; + var MONTH = 1; + var DATE = 2; + var HOUR = 3; + var MINUTE = 4; + var SECOND = 5; + var MILLISECOND = 6; + var WEEK = 7; + var WEEKDAY = 8; + + // FORMATTING + + addFormatToken('Y', 0, 0, function () { + var y = this.year(); + return y <= 9999 ? '' + y : '+' + y; + }); + + addFormatToken(0, ['YY', 2], 0, function () { + return this.year() % 100; + }); + + addFormatToken(0, ['YYYY', 4], 0, 'year'); + addFormatToken(0, ['YYYYY', 5], 0, 'year'); + addFormatToken(0, ['YYYYYY', 6, true], 0, 'year'); + + // ALIASES + + addUnitAlias('year', 'y'); + + // PRIORITIES + + addUnitPriority('year', 1); + + // PARSING + + addRegexToken('Y', matchSigned); + addRegexToken('YY', match1to2, match2); + addRegexToken('YYYY', match1to4, match4); + addRegexToken('YYYYY', match1to6, match6); + addRegexToken('YYYYYY', match1to6, match6); + + addParseToken(['YYYYY', 'YYYYYY'], YEAR); + addParseToken('YYYY', function (input, array) { + array[YEAR] = input.length === 2 ? hooks.parseTwoDigitYear(input) : toInt(input); + }); + addParseToken('YY', function (input, array) { + array[YEAR] = hooks.parseTwoDigitYear(input); + }); + addParseToken('Y', function (input, array) { + array[YEAR] = parseInt(input, 10); + }); + + // HELPERS + + function daysInYear(year) { + return isLeapYear(year) ? 366 : 365; + } + + function isLeapYear(year) { + return (year % 4 === 0 && year % 100 !== 0) || year % 400 === 0; + } + + // HOOKS + + hooks.parseTwoDigitYear = function (input) { + return toInt(input) + (toInt(input) > 68 ? 1900 : 2000); + }; + + // MOMENTS + + var getSetYear = makeGetSet('FullYear', true); + + function getIsLeapYear() { + return isLeapYear(this.year()); + } + + function makeGetSet(unit, keepTime) { + return function (value) { + if (value != null) { + set$1(this, unit, value); + hooks.updateOffset(this, keepTime); + return this; + } else { + return get(this, unit); + } + }; + } + + function get(mom, unit) { + return mom.isValid() ? + mom._d['get' + (mom._isUTC ? 'UTC' : '') + unit]() : NaN; + } + + function set$1(mom, unit, value) { + if (mom.isValid() && !isNaN(value)) { + if (unit === 'FullYear' && isLeapYear(mom.year()) && mom.month() === 1 && mom.date() === 29) { + mom._d['set' + (mom._isUTC ? 'UTC' : '') + unit](value, mom.month(), daysInMonth(value, mom.month())); + } + else { + mom._d['set' + (mom._isUTC ? 'UTC' : '') + unit](value); + } + } + } + + // MOMENTS + + function stringGet(units) { + units = normalizeUnits(units); + if (isFunction(this[units])) { + return this[units](); + } + return this; + } + + + function stringSet(units, value) { + if (typeof units === 'object') { + units = normalizeObjectUnits(units); + var prioritized = getPrioritizedUnits(units); + for (var i = 0; i < prioritized.length; i++) { + this[prioritized[i].unit](units[prioritized[i].unit]); + } + } else { + units = normalizeUnits(units); + if (isFunction(this[units])) { + return this[units](value); + } + } + return this; + } + + function mod(n, x) { + return ((n % x) + x) % x; + } + + var indexOf; + + if (Array.prototype.indexOf) { + indexOf = Array.prototype.indexOf; + } else { + indexOf = function (o) { + // I know + var i; + for (i = 0; i < this.length; ++i) { + if (this[i] === o) { + return i; + } + } + return -1; + }; + } + + function daysInMonth(year, month) { + if (isNaN(year) || isNaN(month)) { + return NaN; + } + var modMonth = mod(month, 12); + year += (month - modMonth) / 12; + return modMonth === 1 ? (isLeapYear(year) ? 29 : 28) : (31 - modMonth % 7 % 2); + } + + // FORMATTING + + addFormatToken('M', ['MM', 2], 'Mo', function () { + return this.month() + 1; + }); + + addFormatToken('MMM', 0, 0, function (format) { + return this.localeData().monthsShort(this, format); + }); + + addFormatToken('MMMM', 0, 0, function (format) { + return this.localeData().months(this, format); + }); + + // ALIASES + + addUnitAlias('month', 'M'); + + // PRIORITY + + addUnitPriority('month', 8); + + // PARSING + + addRegexToken('M', match1to2); + addRegexToken('MM', match1to2, match2); + addRegexToken('MMM', function (isStrict, locale) { + return locale.monthsShortRegex(isStrict); + }); + addRegexToken('MMMM', function (isStrict, locale) { + return locale.monthsRegex(isStrict); + }); + + addParseToken(['M', 'MM'], function (input, array) { + array[MONTH] = toInt(input) - 1; + }); + + addParseToken(['MMM', 'MMMM'], function (input, array, config, token) { + var month = config._locale.monthsParse(input, token, config._strict); + // if we didn't find a month name, mark the date as invalid. + if (month != null) { + array[MONTH] = month; + } else { + getParsingFlags(config).invalidMonth = input; + } + }); + + // LOCALES + + var MONTHS_IN_FORMAT = /D[oD]?(\[[^\[\]]*\]|\s)+MMMM?/; + var defaultLocaleMonths = 'January_February_March_April_May_June_July_August_September_October_November_December'.split('_'); + function localeMonths(m, format) { + if (!m) { + return isArray(this._months) ? this._months : + this._months['standalone']; + } + return isArray(this._months) ? this._months[m.month()] : + this._months[(this._months.isFormat || MONTHS_IN_FORMAT).test(format) ? 'format' : 'standalone'][m.month()]; + } + + var defaultLocaleMonthsShort = 'Jan_Feb_Mar_Apr_May_Jun_Jul_Aug_Sep_Oct_Nov_Dec'.split('_'); + function localeMonthsShort(m, format) { + if (!m) { + return isArray(this._monthsShort) ? this._monthsShort : + this._monthsShort['standalone']; + } + return isArray(this._monthsShort) ? this._monthsShort[m.month()] : + this._monthsShort[MONTHS_IN_FORMAT.test(format) ? 'format' : 'standalone'][m.month()]; + } + + function handleStrictParse(monthName, format, strict) { + var i, ii, mom, llc = monthName.toLocaleLowerCase(); + if (!this._monthsParse) { + // this is not used + this._monthsParse = []; + this._longMonthsParse = []; + this._shortMonthsParse = []; + for (i = 0; i < 12; ++i) { + mom = createUTC([2000, i]); + this._shortMonthsParse[i] = this.monthsShort(mom, '').toLocaleLowerCase(); + this._longMonthsParse[i] = this.months(mom, '').toLocaleLowerCase(); + } + } + + if (strict) { + if (format === 'MMM') { + ii = indexOf.call(this._shortMonthsParse, llc); + return ii !== -1 ? ii : null; + } else { + ii = indexOf.call(this._longMonthsParse, llc); + return ii !== -1 ? ii : null; + } + } else { + if (format === 'MMM') { + ii = indexOf.call(this._shortMonthsParse, llc); + if (ii !== -1) { + return ii; + } + ii = indexOf.call(this._longMonthsParse, llc); + return ii !== -1 ? ii : null; + } else { + ii = indexOf.call(this._longMonthsParse, llc); + if (ii !== -1) { + return ii; + } + ii = indexOf.call(this._shortMonthsParse, llc); + return ii !== -1 ? ii : null; + } + } + } + + function localeMonthsParse(monthName, format, strict) { + var i, mom, regex; + + if (this._monthsParseExact) { + return handleStrictParse.call(this, monthName, format, strict); + } + + if (!this._monthsParse) { + this._monthsParse = []; + this._longMonthsParse = []; + this._shortMonthsParse = []; + } + + // TODO: add sorting + // Sorting makes sure if one month (or abbr) is a prefix of another + // see sorting in computeMonthsParse + for (i = 0; i < 12; i++) { + // make the regex if we don't have it already + mom = createUTC([2000, i]); + if (strict && !this._longMonthsParse[i]) { + this._longMonthsParse[i] = new RegExp('^' + this.months(mom, '').replace('.', '') + '$', 'i'); + this._shortMonthsParse[i] = new RegExp('^' + this.monthsShort(mom, '').replace('.', '') + '$', 'i'); + } + if (!strict && !this._monthsParse[i]) { + regex = '^' + this.months(mom, '') + '|^' + this.monthsShort(mom, ''); + this._monthsParse[i] = new RegExp(regex.replace('.', ''), 'i'); + } + // test the regex + if (strict && format === 'MMMM' && this._longMonthsParse[i].test(monthName)) { + return i; + } else if (strict && format === 'MMM' && this._shortMonthsParse[i].test(monthName)) { + return i; + } else if (!strict && this._monthsParse[i].test(monthName)) { + return i; + } + } + } + + // MOMENTS + + function setMonth(mom, value) { + var dayOfMonth; + + if (!mom.isValid()) { + // No op + return mom; + } + + if (typeof value === 'string') { + if (/^\d+$/.test(value)) { + value = toInt(value); + } else { + value = mom.localeData().monthsParse(value); + // TODO: Another silent failure? + if (!isNumber(value)) { + return mom; + } + } + } + + dayOfMonth = Math.min(mom.date(), daysInMonth(mom.year(), value)); + mom._d['set' + (mom._isUTC ? 'UTC' : '') + 'Month'](value, dayOfMonth); + return mom; + } + + function getSetMonth(value) { + if (value != null) { + setMonth(this, value); + hooks.updateOffset(this, true); + return this; + } else { + return get(this, 'Month'); + } + } + + function getDaysInMonth() { + return daysInMonth(this.year(), this.month()); + } + + var defaultMonthsShortRegex = matchWord; + function monthsShortRegex(isStrict) { + if (this._monthsParseExact) { + if (!hasOwnProp(this, '_monthsRegex')) { + computeMonthsParse.call(this); + } + if (isStrict) { + return this._monthsShortStrictRegex; + } else { + return this._monthsShortRegex; + } + } else { + if (!hasOwnProp(this, '_monthsShortRegex')) { + this._monthsShortRegex = defaultMonthsShortRegex; + } + return this._monthsShortStrictRegex && isStrict ? + this._monthsShortStrictRegex : this._monthsShortRegex; + } + } + + var defaultMonthsRegex = matchWord; + function monthsRegex(isStrict) { + if (this._monthsParseExact) { + if (!hasOwnProp(this, '_monthsRegex')) { + computeMonthsParse.call(this); + } + if (isStrict) { + return this._monthsStrictRegex; + } else { + return this._monthsRegex; + } + } else { + if (!hasOwnProp(this, '_monthsRegex')) { + this._monthsRegex = defaultMonthsRegex; + } + return this._monthsStrictRegex && isStrict ? + this._monthsStrictRegex : this._monthsRegex; + } + } + + function computeMonthsParse() { + function cmpLenRev(a, b) { + return b.length - a.length; + } + + var shortPieces = [], longPieces = [], mixedPieces = [], + i, mom; + for (i = 0; i < 12; i++) { + // make the regex if we don't have it already + mom = createUTC([2000, i]); + shortPieces.push(this.monthsShort(mom, '')); + longPieces.push(this.months(mom, '')); + mixedPieces.push(this.months(mom, '')); + mixedPieces.push(this.monthsShort(mom, '')); + } + // Sorting makes sure if one month (or abbr) is a prefix of another it + // will match the longer piece. + shortPieces.sort(cmpLenRev); + longPieces.sort(cmpLenRev); + mixedPieces.sort(cmpLenRev); + for (i = 0; i < 12; i++) { + shortPieces[i] = regexEscape(shortPieces[i]); + longPieces[i] = regexEscape(longPieces[i]); + } + for (i = 0; i < 24; i++) { + mixedPieces[i] = regexEscape(mixedPieces[i]); + } + + this._monthsRegex = new RegExp('^(' + mixedPieces.join('|') + ')', 'i'); + this._monthsShortRegex = this._monthsRegex; + this._monthsStrictRegex = new RegExp('^(' + longPieces.join('|') + ')', 'i'); + this._monthsShortStrictRegex = new RegExp('^(' + shortPieces.join('|') + ')', 'i'); + } + + function createDate(y, m, d, h, M, s, ms) { + // can't just apply() to create a date: + // https://stackoverflow.com/q/181348 + var date = new Date(y, m, d, h, M, s, ms); + + // the date constructor remaps years 0-99 to 1900-1999 + if (y < 100 && y >= 0 && isFinite(date.getFullYear())) { + date.setFullYear(y); + } + return date; + } + + function createUTCDate(y) { + var date = new Date(Date.UTC.apply(null, arguments)); + + // the Date.UTC function remaps years 0-99 to 1900-1999 + if (y < 100 && y >= 0 && isFinite(date.getUTCFullYear())) { + date.setUTCFullYear(y); + } + return date; + } + + // start-of-first-week - start-of-year + function firstWeekOffset(year, dow, doy) { + var // first-week day -- which january is always in the first week (4 for iso, 1 for other) + fwd = 7 + dow - doy, + // first-week day local weekday -- which local weekday is fwd + fwdlw = (7 + createUTCDate(year, 0, fwd).getUTCDay() - dow) % 7; + + return -fwdlw + fwd - 1; + } + + // https://en.wikipedia.org/wiki/ISO_week_date#Calculating_a_date_given_the_year.2C_week_number_and_weekday + function dayOfYearFromWeeks(year, week, weekday, dow, doy) { + var localWeekday = (7 + weekday - dow) % 7, + weekOffset = firstWeekOffset(year, dow, doy), + dayOfYear = 1 + 7 * (week - 1) + localWeekday + weekOffset, + resYear, resDayOfYear; + + if (dayOfYear <= 0) { + resYear = year - 1; + resDayOfYear = daysInYear(resYear) + dayOfYear; + } else if (dayOfYear > daysInYear(year)) { + resYear = year + 1; + resDayOfYear = dayOfYear - daysInYear(year); + } else { + resYear = year; + resDayOfYear = dayOfYear; + } + + return { + year: resYear, + dayOfYear: resDayOfYear + }; + } + + function weekOfYear(mom, dow, doy) { + var weekOffset = firstWeekOffset(mom.year(), dow, doy), + week = Math.floor((mom.dayOfYear() - weekOffset - 1) / 7) + 1, + resWeek, resYear; + + if (week < 1) { + resYear = mom.year() - 1; + resWeek = week + weeksInYear(resYear, dow, doy); + } else if (week > weeksInYear(mom.year(), dow, doy)) { + resWeek = week - weeksInYear(mom.year(), dow, doy); + resYear = mom.year() + 1; + } else { + resYear = mom.year(); + resWeek = week; + } + + return { + week: resWeek, + year: resYear + }; + } + + function weeksInYear(year, dow, doy) { + var weekOffset = firstWeekOffset(year, dow, doy), + weekOffsetNext = firstWeekOffset(year + 1, dow, doy); + return (daysInYear(year) - weekOffset + weekOffsetNext) / 7; + } + + // FORMATTING + + addFormatToken('w', ['ww', 2], 'wo', 'week'); + addFormatToken('W', ['WW', 2], 'Wo', 'isoWeek'); + + // ALIASES + + addUnitAlias('week', 'w'); + addUnitAlias('isoWeek', 'W'); + + // PRIORITIES + + addUnitPriority('week', 5); + addUnitPriority('isoWeek', 5); + + // PARSING + + addRegexToken('w', match1to2); + addRegexToken('ww', match1to2, match2); + addRegexToken('W', match1to2); + addRegexToken('WW', match1to2, match2); + + addWeekParseToken(['w', 'ww', 'W', 'WW'], function (input, week, config, token) { + week[token.substr(0, 1)] = toInt(input); + }); + + // HELPERS + + // LOCALES + + function localeWeek(mom) { + return weekOfYear(mom, this._week.dow, this._week.doy).week; + } + + var defaultLocaleWeek = { + dow: 0, // Sunday is the first day of the week. + doy: 6 // The week that contains Jan 1st is the first week of the year. + }; + + function localeFirstDayOfWeek() { + return this._week.dow; + } + + function localeFirstDayOfYear() { + return this._week.doy; + } + + // MOMENTS + + function getSetWeek(input) { + var week = this.localeData().week(this); + return input == null ? week : this.add((input - week) * 7, 'd'); + } + + function getSetISOWeek(input) { + var week = weekOfYear(this, 1, 4).week; + return input == null ? week : this.add((input - week) * 7, 'd'); + } + + // FORMATTING + + addFormatToken('d', 0, 'do', 'day'); + + addFormatToken('dd', 0, 0, function (format) { + return this.localeData().weekdaysMin(this, format); + }); + + addFormatToken('ddd', 0, 0, function (format) { + return this.localeData().weekdaysShort(this, format); + }); + + addFormatToken('dddd', 0, 0, function (format) { + return this.localeData().weekdays(this, format); + }); + + addFormatToken('e', 0, 0, 'weekday'); + addFormatToken('E', 0, 0, 'isoWeekday'); + + // ALIASES + + addUnitAlias('day', 'd'); + addUnitAlias('weekday', 'e'); + addUnitAlias('isoWeekday', 'E'); + + // PRIORITY + addUnitPriority('day', 11); + addUnitPriority('weekday', 11); + addUnitPriority('isoWeekday', 11); + + // PARSING + + addRegexToken('d', match1to2); + addRegexToken('e', match1to2); + addRegexToken('E', match1to2); + addRegexToken('dd', function (isStrict, locale) { + return locale.weekdaysMinRegex(isStrict); + }); + addRegexToken('ddd', function (isStrict, locale) { + return locale.weekdaysShortRegex(isStrict); + }); + addRegexToken('dddd', function (isStrict, locale) { + return locale.weekdaysRegex(isStrict); + }); + + addWeekParseToken(['dd', 'ddd', 'dddd'], function (input, week, config, token) { + var weekday = config._locale.weekdaysParse(input, token, config._strict); + // if we didn't get a weekday name, mark the date as invalid + if (weekday != null) { + week.d = weekday; + } else { + getParsingFlags(config).invalidWeekday = input; + } + }); + + addWeekParseToken(['d', 'e', 'E'], function (input, week, config, token) { + week[token] = toInt(input); + }); + + // HELPERS + + function parseWeekday(input, locale) { + if (typeof input !== 'string') { + return input; + } + + if (!isNaN(input)) { + return parseInt(input, 10); + } + + input = locale.weekdaysParse(input); + if (typeof input === 'number') { + return input; + } + + return null; + } + + function parseIsoWeekday(input, locale) { + if (typeof input === 'string') { + return locale.weekdaysParse(input) % 7 || 7; + } + return isNaN(input) ? null : input; + } + + // LOCALES + + var defaultLocaleWeekdays = 'Sunday_Monday_Tuesday_Wednesday_Thursday_Friday_Saturday'.split('_'); + function localeWeekdays(m, format) { + if (!m) { + return isArray(this._weekdays) ? this._weekdays : + this._weekdays['standalone']; + } + return isArray(this._weekdays) ? this._weekdays[m.day()] : + this._weekdays[this._weekdays.isFormat.test(format) ? 'format' : 'standalone'][m.day()]; + } + + var defaultLocaleWeekdaysShort = 'Sun_Mon_Tue_Wed_Thu_Fri_Sat'.split('_'); + function localeWeekdaysShort(m) { + return (m) ? this._weekdaysShort[m.day()] : this._weekdaysShort; + } + + var defaultLocaleWeekdaysMin = 'Su_Mo_Tu_We_Th_Fr_Sa'.split('_'); + function localeWeekdaysMin(m) { + return (m) ? this._weekdaysMin[m.day()] : this._weekdaysMin; + } + + function handleStrictParse$1(weekdayName, format, strict) { + var i, ii, mom, llc = weekdayName.toLocaleLowerCase(); + if (!this._weekdaysParse) { + this._weekdaysParse = []; + this._shortWeekdaysParse = []; + this._minWeekdaysParse = []; + + for (i = 0; i < 7; ++i) { + mom = createUTC([2000, 1]).day(i); + this._minWeekdaysParse[i] = this.weekdaysMin(mom, '').toLocaleLowerCase(); + this._shortWeekdaysParse[i] = this.weekdaysShort(mom, '').toLocaleLowerCase(); + this._weekdaysParse[i] = this.weekdays(mom, '').toLocaleLowerCase(); + } + } + + if (strict) { + if (format === 'dddd') { + ii = indexOf.call(this._weekdaysParse, llc); + return ii !== -1 ? ii : null; + } else if (format === 'ddd') { + ii = indexOf.call(this._shortWeekdaysParse, llc); + return ii !== -1 ? ii : null; + } else { + ii = indexOf.call(this._minWeekdaysParse, llc); + return ii !== -1 ? ii : null; + } + } else { + if (format === 'dddd') { + ii = indexOf.call(this._weekdaysParse, llc); + if (ii !== -1) { + return ii; + } + ii = indexOf.call(this._shortWeekdaysParse, llc); + if (ii !== -1) { + return ii; + } + ii = indexOf.call(this._minWeekdaysParse, llc); + return ii !== -1 ? ii : null; + } else if (format === 'ddd') { + ii = indexOf.call(this._shortWeekdaysParse, llc); + if (ii !== -1) { + return ii; + } + ii = indexOf.call(this._weekdaysParse, llc); + if (ii !== -1) { + return ii; + } + ii = indexOf.call(this._minWeekdaysParse, llc); + return ii !== -1 ? ii : null; + } else { + ii = indexOf.call(this._minWeekdaysParse, llc); + if (ii !== -1) { + return ii; + } + ii = indexOf.call(this._weekdaysParse, llc); + if (ii !== -1) { + return ii; + } + ii = indexOf.call(this._shortWeekdaysParse, llc); + return ii !== -1 ? ii : null; + } + } + } + + function localeWeekdaysParse(weekdayName, format, strict) { + var i, mom, regex; + + if (this._weekdaysParseExact) { + return handleStrictParse$1.call(this, weekdayName, format, strict); + } + + if (!this._weekdaysParse) { + this._weekdaysParse = []; + this._minWeekdaysParse = []; + this._shortWeekdaysParse = []; + this._fullWeekdaysParse = []; + } + + for (i = 0; i < 7; i++) { + // make the regex if we don't have it already + + mom = createUTC([2000, 1]).day(i); + if (strict && !this._fullWeekdaysParse[i]) { + this._fullWeekdaysParse[i] = new RegExp('^' + this.weekdays(mom, '').replace('.', '\.?') + '$', 'i'); + this._shortWeekdaysParse[i] = new RegExp('^' + this.weekdaysShort(mom, '').replace('.', '\.?') + '$', 'i'); + this._minWeekdaysParse[i] = new RegExp('^' + this.weekdaysMin(mom, '').replace('.', '\.?') + '$', 'i'); + } + if (!this._weekdaysParse[i]) { + regex = '^' + this.weekdays(mom, '') + '|^' + this.weekdaysShort(mom, '') + '|^' + this.weekdaysMin(mom, ''); + this._weekdaysParse[i] = new RegExp(regex.replace('.', ''), 'i'); + } + // test the regex + if (strict && format === 'dddd' && this._fullWeekdaysParse[i].test(weekdayName)) { + return i; + } else if (strict && format === 'ddd' && this._shortWeekdaysParse[i].test(weekdayName)) { + return i; + } else if (strict && format === 'dd' && this._minWeekdaysParse[i].test(weekdayName)) { + return i; + } else if (!strict && this._weekdaysParse[i].test(weekdayName)) { + return i; + } + } + } + + // MOMENTS + + function getSetDayOfWeek(input) { + if (!this.isValid()) { + return input != null ? this : NaN; + } + var day = this._isUTC ? this._d.getUTCDay() : this._d.getDay(); + if (input != null) { + input = parseWeekday(input, this.localeData()); + return this.add(input - day, 'd'); + } else { + return day; + } + } + + function getSetLocaleDayOfWeek(input) { + if (!this.isValid()) { + return input != null ? this : NaN; + } + var weekday = (this.day() + 7 - this.localeData()._week.dow) % 7; + return input == null ? weekday : this.add(input - weekday, 'd'); + } + + function getSetISODayOfWeek(input) { + if (!this.isValid()) { + return input != null ? this : NaN; + } + + // behaves the same as moment#day except + // as a getter, returns 7 instead of 0 (1-7 range instead of 0-6) + // as a setter, sunday should belong to the previous week. + + if (input != null) { + var weekday = parseIsoWeekday(input, this.localeData()); + return this.day(this.day() % 7 ? weekday : weekday - 7); + } else { + return this.day() || 7; + } + } + + var defaultWeekdaysRegex = matchWord; + function weekdaysRegex(isStrict) { + if (this._weekdaysParseExact) { + if (!hasOwnProp(this, '_weekdaysRegex')) { + computeWeekdaysParse.call(this); + } + if (isStrict) { + return this._weekdaysStrictRegex; + } else { + return this._weekdaysRegex; + } + } else { + if (!hasOwnProp(this, '_weekdaysRegex')) { + this._weekdaysRegex = defaultWeekdaysRegex; + } + return this._weekdaysStrictRegex && isStrict ? + this._weekdaysStrictRegex : this._weekdaysRegex; + } + } + + var defaultWeekdaysShortRegex = matchWord; + function weekdaysShortRegex(isStrict) { + if (this._weekdaysParseExact) { + if (!hasOwnProp(this, '_weekdaysRegex')) { + computeWeekdaysParse.call(this); + } + if (isStrict) { + return this._weekdaysShortStrictRegex; + } else { + return this._weekdaysShortRegex; + } + } else { + if (!hasOwnProp(this, '_weekdaysShortRegex')) { + this._weekdaysShortRegex = defaultWeekdaysShortRegex; + } + return this._weekdaysShortStrictRegex && isStrict ? + this._weekdaysShortStrictRegex : this._weekdaysShortRegex; + } + } + + var defaultWeekdaysMinRegex = matchWord; + function weekdaysMinRegex(isStrict) { + if (this._weekdaysParseExact) { + if (!hasOwnProp(this, '_weekdaysRegex')) { + computeWeekdaysParse.call(this); + } + if (isStrict) { + return this._weekdaysMinStrictRegex; + } else { + return this._weekdaysMinRegex; + } + } else { + if (!hasOwnProp(this, '_weekdaysMinRegex')) { + this._weekdaysMinRegex = defaultWeekdaysMinRegex; + } + return this._weekdaysMinStrictRegex && isStrict ? + this._weekdaysMinStrictRegex : this._weekdaysMinRegex; + } + } + + + function computeWeekdaysParse() { + function cmpLenRev(a, b) { + return b.length - a.length; + } + + var minPieces = [], shortPieces = [], longPieces = [], mixedPieces = [], + i, mom, minp, shortp, longp; + for (i = 0; i < 7; i++) { + // make the regex if we don't have it already + mom = createUTC([2000, 1]).day(i); + minp = this.weekdaysMin(mom, ''); + shortp = this.weekdaysShort(mom, ''); + longp = this.weekdays(mom, ''); + minPieces.push(minp); + shortPieces.push(shortp); + longPieces.push(longp); + mixedPieces.push(minp); + mixedPieces.push(shortp); + mixedPieces.push(longp); + } + // Sorting makes sure if one weekday (or abbr) is a prefix of another it + // will match the longer piece. + minPieces.sort(cmpLenRev); + shortPieces.sort(cmpLenRev); + longPieces.sort(cmpLenRev); + mixedPieces.sort(cmpLenRev); + for (i = 0; i < 7; i++) { + shortPieces[i] = regexEscape(shortPieces[i]); + longPieces[i] = regexEscape(longPieces[i]); + mixedPieces[i] = regexEscape(mixedPieces[i]); + } + + this._weekdaysRegex = new RegExp('^(' + mixedPieces.join('|') + ')', 'i'); + this._weekdaysShortRegex = this._weekdaysRegex; + this._weekdaysMinRegex = this._weekdaysRegex; + + this._weekdaysStrictRegex = new RegExp('^(' + longPieces.join('|') + ')', 'i'); + this._weekdaysShortStrictRegex = new RegExp('^(' + shortPieces.join('|') + ')', 'i'); + this._weekdaysMinStrictRegex = new RegExp('^(' + minPieces.join('|') + ')', 'i'); + } + + // FORMATTING + + function hFormat() { + return this.hours() % 12 || 12; + } + + function kFormat() { + return this.hours() || 24; + } + + addFormatToken('H', ['HH', 2], 0, 'hour'); + addFormatToken('h', ['hh', 2], 0, hFormat); + addFormatToken('k', ['kk', 2], 0, kFormat); + + addFormatToken('hmm', 0, 0, function () { + return '' + hFormat.apply(this) + zeroFill(this.minutes(), 2); + }); + + addFormatToken('hmmss', 0, 0, function () { + return '' + hFormat.apply(this) + zeroFill(this.minutes(), 2) + + zeroFill(this.seconds(), 2); + }); + + addFormatToken('Hmm', 0, 0, function () { + return '' + this.hours() + zeroFill(this.minutes(), 2); + }); + + addFormatToken('Hmmss', 0, 0, function () { + return '' + this.hours() + zeroFill(this.minutes(), 2) + + zeroFill(this.seconds(), 2); + }); + + function meridiem(token, lowercase) { + addFormatToken(token, 0, 0, function () { + return this.localeData().meridiem(this.hours(), this.minutes(), lowercase); + }); + } + + meridiem('a', true); + meridiem('A', false); + + // ALIASES + + addUnitAlias('hour', 'h'); + + // PRIORITY + addUnitPriority('hour', 13); + + // PARSING + + function matchMeridiem(isStrict, locale) { + return locale._meridiemParse; + } + + addRegexToken('a', matchMeridiem); + addRegexToken('A', matchMeridiem); + addRegexToken('H', match1to2); + addRegexToken('h', match1to2); + addRegexToken('k', match1to2); + addRegexToken('HH', match1to2, match2); + addRegexToken('hh', match1to2, match2); + addRegexToken('kk', match1to2, match2); + + addRegexToken('hmm', match3to4); + addRegexToken('hmmss', match5to6); + addRegexToken('Hmm', match3to4); + addRegexToken('Hmmss', match5to6); + + addParseToken(['H', 'HH'], HOUR); + addParseToken(['k', 'kk'], function (input, array, config) { + var kInput = toInt(input); + array[HOUR] = kInput === 24 ? 0 : kInput; + }); + addParseToken(['a', 'A'], function (input, array, config) { + config._isPm = config._locale.isPM(input); + config._meridiem = input; + }); + addParseToken(['h', 'hh'], function (input, array, config) { + array[HOUR] = toInt(input); + getParsingFlags(config).bigHour = true; + }); + addParseToken('hmm', function (input, array, config) { + var pos = input.length - 2; + array[HOUR] = toInt(input.substr(0, pos)); + array[MINUTE] = toInt(input.substr(pos)); + getParsingFlags(config).bigHour = true; + }); + addParseToken('hmmss', function (input, array, config) { + var pos1 = input.length - 4; + var pos2 = input.length - 2; + array[HOUR] = toInt(input.substr(0, pos1)); + array[MINUTE] = toInt(input.substr(pos1, 2)); + array[SECOND] = toInt(input.substr(pos2)); + getParsingFlags(config).bigHour = true; + }); + addParseToken('Hmm', function (input, array, config) { + var pos = input.length - 2; + array[HOUR] = toInt(input.substr(0, pos)); + array[MINUTE] = toInt(input.substr(pos)); + }); + addParseToken('Hmmss', function (input, array, config) { + var pos1 = input.length - 4; + var pos2 = input.length - 2; + array[HOUR] = toInt(input.substr(0, pos1)); + array[MINUTE] = toInt(input.substr(pos1, 2)); + array[SECOND] = toInt(input.substr(pos2)); + }); + + // LOCALES + + function localeIsPM(input) { + // IE8 Quirks Mode & IE7 Standards Mode do not allow accessing strings like arrays + // Using charAt should be more compatible. + return ((input + '').toLowerCase().charAt(0) === 'p'); + } + + var defaultLocaleMeridiemParse = /[ap]\.?m?\.?/i; + function localeMeridiem(hours, minutes, isLower) { + if (hours > 11) { + return isLower ? 'pm' : 'PM'; + } else { + return isLower ? 'am' : 'AM'; + } + } + + + // MOMENTS + + // Setting the hour should keep the time, because the user explicitly + // specified which hour he wants. So trying to maintain the same hour (in + // a new timezone) makes sense. Adding/subtracting hours does not follow + // this rule. + var getSetHour = makeGetSet('Hours', true); + + // months + // week + // weekdays + // meridiem + var baseConfig = { + calendar: defaultCalendar, + longDateFormat: defaultLongDateFormat, + invalidDate: defaultInvalidDate, + ordinal: defaultOrdinal, + dayOfMonthOrdinalParse: defaultDayOfMonthOrdinalParse, + relativeTime: defaultRelativeTime, + + months: defaultLocaleMonths, + monthsShort: defaultLocaleMonthsShort, + + week: defaultLocaleWeek, + + weekdays: defaultLocaleWeekdays, + weekdaysMin: defaultLocaleWeekdaysMin, + weekdaysShort: defaultLocaleWeekdaysShort, + + meridiemParse: defaultLocaleMeridiemParse + }; + + // internal storage for locale config files + var locales = {}; + var localeFamilies = {}; + var globalLocale; + + function normalizeLocale(key) { + return key ? key.toLowerCase().replace('_', '-') : key; + } + + // pick the locale from the array + // try ['en-au', 'en-gb'] as 'en-au', 'en-gb', 'en', as in move through the list trying each + // substring from most specific to least, but move to the next array item if it's a more specific variant than the current root + function chooseLocale(names) { + var i = 0, j, next, locale, split; + + while (i < names.length) { + split = normalizeLocale(names[i]).split('-'); + j = split.length; + next = normalizeLocale(names[i + 1]); + next = next ? next.split('-') : null; + while (j > 0) { + locale = loadLocale(split.slice(0, j).join('-')); + if (locale) { + return locale; + } + if (next && next.length >= j && compareArrays(split, next, true) >= j - 1) { + //the next array item is better than a shallower substring of this one + break; + } + j--; + } + i++; + } + return null; + } + + function loadLocale(name) { + var oldLocale = null; + // TODO: Find a better way to register and load all the locales in Node + if (!locales[name] && (typeof module !== 'undefined') && + module && module.exports) { + try { + oldLocale = globalLocale._abbr; + var aliasedRequire = require; + aliasedRequire('./locale/' + name); + getSetGlobalLocale(oldLocale); + } catch (e) { } + } + return locales[name]; + } + + // This function will load locale and then set the global locale. If + // no arguments are passed in, it will simply return the current global + // locale key. + function getSetGlobalLocale(key, values) { + var data; + if (key) { + if (isUndefined(values)) { + data = getLocale(key); + } + else { + data = defineLocale(key, values); + } + + if (data) { + // moment.duration._locale = moment._locale = data; + globalLocale = data; + } + } + + return globalLocale._abbr; + } + + function defineLocale(name, config) { + if (config !== null) { + var parentConfig = baseConfig; + config.abbr = name; + if (locales[name] != null) { + deprecateSimple('defineLocaleOverride', + 'use moment.updateLocale(localeName, config) to change ' + + 'an existing locale. moment.defineLocale(localeName, ' + + 'config) should only be used for creating a new locale ' + + 'See http://momentjs.com/guides/#/warnings/define-locale/ for more info.'); + parentConfig = locales[name]._config; + } else if (config.parentLocale != null) { + if (locales[config.parentLocale] != null) { + parentConfig = locales[config.parentLocale]._config; + } else { + if (!localeFamilies[config.parentLocale]) { + localeFamilies[config.parentLocale] = []; + } + localeFamilies[config.parentLocale].push({ + name: name, + config: config + }); + return null; + } + } + locales[name] = new Locale(mergeConfigs(parentConfig, config)); + + if (localeFamilies[name]) { + localeFamilies[name].forEach(function (x) { + defineLocale(x.name, x.config); + }); + } + + // backwards compat for now: also set the locale + // make sure we set the locale AFTER all child locales have been + // created, so we won't end up with the child locale set. + getSetGlobalLocale(name); + + + return locales[name]; + } else { + // useful for testing + delete locales[name]; + return null; + } + } + + function updateLocale(name, config) { + if (config != null) { + var locale, tmpLocale, parentConfig = baseConfig; + // MERGE + tmpLocale = loadLocale(name); + if (tmpLocale != null) { + parentConfig = tmpLocale._config; + } + config = mergeConfigs(parentConfig, config); + locale = new Locale(config); + locale.parentLocale = locales[name]; + locales[name] = locale; + + // backwards compat for now: also set the locale + getSetGlobalLocale(name); + } else { + // pass null for config to unupdate, useful for tests + if (locales[name] != null) { + if (locales[name].parentLocale != null) { + locales[name] = locales[name].parentLocale; + } else if (locales[name] != null) { + delete locales[name]; + } + } + } + return locales[name]; + } + + // returns locale data + function getLocale(key) { + var locale; + + if (key && key._locale && key._locale._abbr) { + key = key._locale._abbr; + } + + if (!key) { + return globalLocale; + } + + if (!isArray(key)) { + //short-circuit everything else + locale = loadLocale(key); + if (locale) { + return locale; + } + key = [key]; + } + + return chooseLocale(key); + } + + function listLocales() { + return keys(locales); + } + + function checkOverflow(m) { + var overflow; + var a = m._a; + + if (a && getParsingFlags(m).overflow === -2) { + overflow = + a[MONTH] < 0 || a[MONTH] > 11 ? MONTH : + a[DATE] < 1 || a[DATE] > daysInMonth(a[YEAR], a[MONTH]) ? DATE : + a[HOUR] < 0 || a[HOUR] > 24 || (a[HOUR] === 24 && (a[MINUTE] !== 0 || a[SECOND] !== 0 || a[MILLISECOND] !== 0)) ? HOUR : + a[MINUTE] < 0 || a[MINUTE] > 59 ? MINUTE : + a[SECOND] < 0 || a[SECOND] > 59 ? SECOND : + a[MILLISECOND] < 0 || a[MILLISECOND] > 999 ? MILLISECOND : + -1; + + if (getParsingFlags(m)._overflowDayOfYear && (overflow < YEAR || overflow > DATE)) { + overflow = DATE; + } + if (getParsingFlags(m)._overflowWeeks && overflow === -1) { + overflow = WEEK; + } + if (getParsingFlags(m)._overflowWeekday && overflow === -1) { + overflow = WEEKDAY; + } + + getParsingFlags(m).overflow = overflow; + } + + return m; + } + + // Pick the first defined of two or three arguments. + function defaults(a, b, c) { + if (a != null) { + return a; + } + if (b != null) { + return b; + } + return c; + } + + function currentDateArray(config) { + // hooks is actually the exported moment object + var nowValue = new Date(hooks.now()); + if (config._useUTC) { + return [nowValue.getUTCFullYear(), nowValue.getUTCMonth(), nowValue.getUTCDate()]; + } + return [nowValue.getFullYear(), nowValue.getMonth(), nowValue.getDate()]; + } + + // convert an array to a date. + // the array should mirror the parameters below + // note: all values past the year are optional and will default to the lowest possible value. + // [year, month, day , hour, minute, second, millisecond] + function configFromArray(config) { + var i, date, input = [], currentDate, expectedWeekday, yearToUse; + + if (config._d) { + return; + } + + currentDate = currentDateArray(config); + + //compute day of the year from weeks and weekdays + if (config._w && config._a[DATE] == null && config._a[MONTH] == null) { + dayOfYearFromWeekInfo(config); + } + + //if the day of the year is set, figure out what it is + if (config._dayOfYear != null) { + yearToUse = defaults(config._a[YEAR], currentDate[YEAR]); + + if (config._dayOfYear > daysInYear(yearToUse) || config._dayOfYear === 0) { + getParsingFlags(config)._overflowDayOfYear = true; + } + + date = createUTCDate(yearToUse, 0, config._dayOfYear); + config._a[MONTH] = date.getUTCMonth(); + config._a[DATE] = date.getUTCDate(); + } + + // Default to current date. + // * if no year, month, day of month are given, default to today + // * if day of month is given, default month and year + // * if month is given, default only year + // * if year is given, don't default anything + for (i = 0; i < 3 && config._a[i] == null; ++i) { + config._a[i] = input[i] = currentDate[i]; + } + + // Zero out whatever was not defaulted, including time + for (; i < 7; i++) { + config._a[i] = input[i] = (config._a[i] == null) ? (i === 2 ? 1 : 0) : config._a[i]; + } + + // Check for 24:00:00.000 + if (config._a[HOUR] === 24 && + config._a[MINUTE] === 0 && + config._a[SECOND] === 0 && + config._a[MILLISECOND] === 0) { + config._nextDay = true; + config._a[HOUR] = 0; + } + + config._d = (config._useUTC ? createUTCDate : createDate).apply(null, input); + expectedWeekday = config._useUTC ? config._d.getUTCDay() : config._d.getDay(); + + // Apply timezone offset from input. The actual utcOffset can be changed + // with parseZone. + if (config._tzm != null) { + config._d.setUTCMinutes(config._d.getUTCMinutes() - config._tzm); + } + + if (config._nextDay) { + config._a[HOUR] = 24; + } + + // check for mismatching day of week + if (config._w && typeof config._w.d !== 'undefined' && config._w.d !== expectedWeekday) { + getParsingFlags(config).weekdayMismatch = true; + } + } + + function dayOfYearFromWeekInfo(config) { + var w, weekYear, week, weekday, dow, doy, temp, weekdayOverflow; + + w = config._w; + if (w.GG != null || w.W != null || w.E != null) { + dow = 1; + doy = 4; + + // TODO: We need to take the current isoWeekYear, but that depends on + // how we interpret now (local, utc, fixed offset). So create + // a now version of current config (take local/utc/offset flags, and + // create now). + weekYear = defaults(w.GG, config._a[YEAR], weekOfYear(createLocal(), 1, 4).year); + week = defaults(w.W, 1); + weekday = defaults(w.E, 1); + if (weekday < 1 || weekday > 7) { + weekdayOverflow = true; + } + } else { + dow = config._locale._week.dow; + doy = config._locale._week.doy; + + var curWeek = weekOfYear(createLocal(), dow, doy); + + weekYear = defaults(w.gg, config._a[YEAR], curWeek.year); + + // Default to current week. + week = defaults(w.w, curWeek.week); + + if (w.d != null) { + // weekday -- low day numbers are considered next week + weekday = w.d; + if (weekday < 0 || weekday > 6) { + weekdayOverflow = true; + } + } else if (w.e != null) { + // local weekday -- counting starts from begining of week + weekday = w.e + dow; + if (w.e < 0 || w.e > 6) { + weekdayOverflow = true; + } + } else { + // default to begining of week + weekday = dow; + } + } + if (week < 1 || week > weeksInYear(weekYear, dow, doy)) { + getParsingFlags(config)._overflowWeeks = true; + } else if (weekdayOverflow != null) { + getParsingFlags(config)._overflowWeekday = true; + } else { + temp = dayOfYearFromWeeks(weekYear, week, weekday, dow, doy); + config._a[YEAR] = temp.year; + config._dayOfYear = temp.dayOfYear; + } + } + + // iso 8601 regex + // 0000-00-00 0000-W00 or 0000-W00-0 + T + 00 or 00:00 or 00:00:00 or 00:00:00.000 + +00:00 or +0000 or +00) + var extendedIsoRegex = /^\s*((?:[+-]\d{6}|\d{4})-(?:\d\d-\d\d|W\d\d-\d|W\d\d|\d\d\d|\d\d))(?:(T| )(\d\d(?::\d\d(?::\d\d(?:[.,]\d+)?)?)?)([\+\-]\d\d(?::?\d\d)?|\s*Z)?)?$/; + var basicIsoRegex = /^\s*((?:[+-]\d{6}|\d{4})(?:\d\d\d\d|W\d\d\d|W\d\d|\d\d\d|\d\d))(?:(T| )(\d\d(?:\d\d(?:\d\d(?:[.,]\d+)?)?)?)([\+\-]\d\d(?::?\d\d)?|\s*Z)?)?$/; + + var tzRegex = /Z|[+-]\d\d(?::?\d\d)?/; + + var isoDates = [ + ['YYYYYY-MM-DD', /[+-]\d{6}-\d\d-\d\d/], + ['YYYY-MM-DD', /\d{4}-\d\d-\d\d/], + ['GGGG-[W]WW-E', /\d{4}-W\d\d-\d/], + ['GGGG-[W]WW', /\d{4}-W\d\d/, false], + ['YYYY-DDD', /\d{4}-\d{3}/], + ['YYYY-MM', /\d{4}-\d\d/, false], + ['YYYYYYMMDD', /[+-]\d{10}/], + ['YYYYMMDD', /\d{8}/], + // YYYYMM is NOT allowed by the standard + ['GGGG[W]WWE', /\d{4}W\d{3}/], + ['GGGG[W]WW', /\d{4}W\d{2}/, false], + ['YYYYDDD', /\d{7}/] + ]; + + // iso time formats and regexes + var isoTimes = [ + ['HH:mm:ss.SSSS', /\d\d:\d\d:\d\d\.\d+/], + ['HH:mm:ss,SSSS', /\d\d:\d\d:\d\d,\d+/], + ['HH:mm:ss', /\d\d:\d\d:\d\d/], + ['HH:mm', /\d\d:\d\d/], + ['HHmmss.SSSS', /\d\d\d\d\d\d\.\d+/], + ['HHmmss,SSSS', /\d\d\d\d\d\d,\d+/], + ['HHmmss', /\d\d\d\d\d\d/], + ['HHmm', /\d\d\d\d/], + ['HH', /\d\d/] + ]; + + var aspNetJsonRegex = /^\/?Date\((\-?\d+)/i; + + // date from iso format + function configFromISO(config) { + var i, l, + string = config._i, + match = extendedIsoRegex.exec(string) || basicIsoRegex.exec(string), + allowTime, dateFormat, timeFormat, tzFormat; + + if (match) { + getParsingFlags(config).iso = true; + + for (i = 0, l = isoDates.length; i < l; i++) { + if (isoDates[i][1].exec(match[1])) { + dateFormat = isoDates[i][0]; + allowTime = isoDates[i][2] !== false; + break; + } + } + if (dateFormat == null) { + config._isValid = false; + return; + } + if (match[3]) { + for (i = 0, l = isoTimes.length; i < l; i++) { + if (isoTimes[i][1].exec(match[3])) { + // match[2] should be 'T' or space + timeFormat = (match[2] || ' ') + isoTimes[i][0]; + break; + } + } + if (timeFormat == null) { + config._isValid = false; + return; + } + } + if (!allowTime && timeFormat != null) { + config._isValid = false; + return; + } + if (match[4]) { + if (tzRegex.exec(match[4])) { + tzFormat = 'Z'; + } else { + config._isValid = false; + return; + } + } + config._f = dateFormat + (timeFormat || '') + (tzFormat || ''); + configFromStringAndFormat(config); + } else { + config._isValid = false; + } + } + + // RFC 2822 regex: For details see https://tools.ietf.org/html/rfc2822#section-3.3 + var rfc2822 = /^(?:(Mon|Tue|Wed|Thu|Fri|Sat|Sun),?\s)?(\d{1,2})\s(Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec)\s(\d{2,4})\s(\d\d):(\d\d)(?::(\d\d))?\s(?:(UT|GMT|[ECMP][SD]T)|([Zz])|([+-]\d{4}))$/; + + function extractFromRFC2822Strings(yearStr, monthStr, dayStr, hourStr, minuteStr, secondStr) { + var result = [ + untruncateYear(yearStr), + defaultLocaleMonthsShort.indexOf(monthStr), + parseInt(dayStr, 10), + parseInt(hourStr, 10), + parseInt(minuteStr, 10) + ]; + + if (secondStr) { + result.push(parseInt(secondStr, 10)); + } + + return result; + } + + function untruncateYear(yearStr) { + var year = parseInt(yearStr, 10); + if (year <= 49) { + return 2000 + year; + } else if (year <= 999) { + return 1900 + year; + } + return year; + } + + function preprocessRFC2822(s) { + // Remove comments and folding whitespace and replace multiple-spaces with a single space + return s.replace(/\([^)]*\)|[\n\t]/g, ' ').replace(/(\s\s+)/g, ' ').trim(); + } + + function checkWeekday(weekdayStr, parsedInput, config) { + if (weekdayStr) { + // TODO: Replace the vanilla JS Date object with an indepentent day-of-week check. + var weekdayProvided = defaultLocaleWeekdaysShort.indexOf(weekdayStr), + weekdayActual = new Date(parsedInput[0], parsedInput[1], parsedInput[2]).getDay(); + if (weekdayProvided !== weekdayActual) { + getParsingFlags(config).weekdayMismatch = true; + config._isValid = false; + return false; + } + } + return true; + } + + var obsOffsets = { + UT: 0, + GMT: 0, + EDT: -4 * 60, + EST: -5 * 60, + CDT: -5 * 60, + CST: -6 * 60, + MDT: -6 * 60, + MST: -7 * 60, + PDT: -7 * 60, + PST: -8 * 60 + }; + + function calculateOffset(obsOffset, militaryOffset, numOffset) { + if (obsOffset) { + return obsOffsets[obsOffset]; + } else if (militaryOffset) { + // the only allowed military tz is Z + return 0; + } else { + var hm = parseInt(numOffset, 10); + var m = hm % 100, h = (hm - m) / 100; + return h * 60 + m; + } + } + + // date and time from ref 2822 format + function configFromRFC2822(config) { + var match = rfc2822.exec(preprocessRFC2822(config._i)); + if (match) { + var parsedArray = extractFromRFC2822Strings(match[4], match[3], match[2], match[5], match[6], match[7]); + if (!checkWeekday(match[1], parsedArray, config)) { + return; + } + + config._a = parsedArray; + config._tzm = calculateOffset(match[8], match[9], match[10]); + + config._d = createUTCDate.apply(null, config._a); + config._d.setUTCMinutes(config._d.getUTCMinutes() - config._tzm); + + getParsingFlags(config).rfc2822 = true; + } else { + config._isValid = false; + } + } + + // date from iso format or fallback + function configFromString(config) { + var matched = aspNetJsonRegex.exec(config._i); + + if (matched !== null) { + config._d = new Date(+matched[1]); + return; + } + + configFromISO(config); + if (config._isValid === false) { + delete config._isValid; + } else { + return; + } + + configFromRFC2822(config); + if (config._isValid === false) { + delete config._isValid; + } else { + return; + } + + // Final attempt, use Input Fallback + hooks.createFromInputFallback(config); + } + + hooks.createFromInputFallback = deprecate( + 'value provided is not in a recognized RFC2822 or ISO format. moment construction falls back to js Date(), ' + + 'which is not reliable across all browsers and versions. Non RFC2822/ISO date formats are ' + + 'discouraged and will be removed in an upcoming major release. Please refer to ' + + 'http://momentjs.com/guides/#/warnings/js-date/ for more info.', + function (config) { + config._d = new Date(config._i + (config._useUTC ? ' UTC' : '')); + } + ); + + // constant that refers to the ISO standard + hooks.ISO_8601 = function () { }; + + // constant that refers to the RFC 2822 form + hooks.RFC_2822 = function () { }; + + // date from string and format string + function configFromStringAndFormat(config) { + // TODO: Move this to another part of the creation flow to prevent circular deps + if (config._f === hooks.ISO_8601) { + configFromISO(config); + return; + } + if (config._f === hooks.RFC_2822) { + configFromRFC2822(config); + return; + } + config._a = []; + getParsingFlags(config).empty = true; + + // This array is used to make a Date, either with `new Date` or `Date.UTC` + var string = '' + config._i, + i, parsedInput, tokens, token, skipped, + stringLength = string.length, + totalParsedInputLength = 0; + + tokens = expandFormat(config._f, config._locale).match(formattingTokens) || []; + + for (i = 0; i < tokens.length; i++) { + token = tokens[i]; + parsedInput = (string.match(getParseRegexForToken(token, config)) || [])[0]; + // console.log('token', token, 'parsedInput', parsedInput, + // 'regex', getParseRegexForToken(token, config)); + if (parsedInput) { + skipped = string.substr(0, string.indexOf(parsedInput)); + if (skipped.length > 0) { + getParsingFlags(config).unusedInput.push(skipped); + } + string = string.slice(string.indexOf(parsedInput) + parsedInput.length); + totalParsedInputLength += parsedInput.length; + } + // don't parse if it's not a known token + if (formatTokenFunctions[token]) { + if (parsedInput) { + getParsingFlags(config).empty = false; + } + else { + getParsingFlags(config).unusedTokens.push(token); + } + addTimeToArrayFromToken(token, parsedInput, config); + } + else if (config._strict && !parsedInput) { + getParsingFlags(config).unusedTokens.push(token); + } + } + + // add remaining unparsed input length to the string + getParsingFlags(config).charsLeftOver = stringLength - totalParsedInputLength; + if (string.length > 0) { + getParsingFlags(config).unusedInput.push(string); + } + + // clear _12h flag if hour is <= 12 + if (config._a[HOUR] <= 12 && + getParsingFlags(config).bigHour === true && + config._a[HOUR] > 0) { + getParsingFlags(config).bigHour = undefined; + } + + getParsingFlags(config).parsedDateParts = config._a.slice(0); + getParsingFlags(config).meridiem = config._meridiem; + // handle meridiem + config._a[HOUR] = meridiemFixWrap(config._locale, config._a[HOUR], config._meridiem); + + configFromArray(config); + checkOverflow(config); + } + + + function meridiemFixWrap(locale, hour, meridiem) { + var isPm; + + if (meridiem == null) { + // nothing to do + return hour; + } + if (locale.meridiemHour != null) { + return locale.meridiemHour(hour, meridiem); + } else if (locale.isPM != null) { + // Fallback + isPm = locale.isPM(meridiem); + if (isPm && hour < 12) { + hour += 12; + } + if (!isPm && hour === 12) { + hour = 0; + } + return hour; + } else { + // this is not supposed to happen + return hour; + } + } + + // date from string and array of format strings + function configFromStringAndArray(config) { + var tempConfig, + bestMoment, + + scoreToBeat, + i, + currentScore; + + if (config._f.length === 0) { + getParsingFlags(config).invalidFormat = true; + config._d = new Date(NaN); + return; + } + + for (i = 0; i < config._f.length; i++) { + currentScore = 0; + tempConfig = copyConfig({}, config); + if (config._useUTC != null) { + tempConfig._useUTC = config._useUTC; + } + tempConfig._f = config._f[i]; + configFromStringAndFormat(tempConfig); + + if (!isValid(tempConfig)) { + continue; + } + + // if there is any input that was not parsed add a penalty for that format + currentScore += getParsingFlags(tempConfig).charsLeftOver; + + //or tokens + currentScore += getParsingFlags(tempConfig).unusedTokens.length * 10; + + getParsingFlags(tempConfig).score = currentScore; + + if (scoreToBeat == null || currentScore < scoreToBeat) { + scoreToBeat = currentScore; + bestMoment = tempConfig; + } + } + + extend(config, bestMoment || tempConfig); + } + + function configFromObject(config) { + if (config._d) { + return; + } + + var i = normalizeObjectUnits(config._i); + config._a = map([i.year, i.month, i.day || i.date, i.hour, i.minute, i.second, i.millisecond], function (obj) { + return obj && parseInt(obj, 10); + }); + + configFromArray(config); + } + + function createFromConfig(config) { + var res = new Moment(checkOverflow(prepareConfig(config))); + if (res._nextDay) { + // Adding is smart enough around DST + res.add(1, 'd'); + res._nextDay = undefined; + } + + return res; + } + + function prepareConfig(config) { + var input = config._i, + format = config._f; + + config._locale = config._locale || getLocale(config._l); + + if (input === null || (format === undefined && input === '')) { + return createInvalid({ nullInput: true }); + } + + if (typeof input === 'string') { + config._i = input = config._locale.preparse(input); + } + + if (isMoment(input)) { + return new Moment(checkOverflow(input)); + } else if (isDate(input)) { + config._d = input; + } else if (isArray(format)) { + configFromStringAndArray(config); + } else if (format) { + configFromStringAndFormat(config); + } else { + configFromInput(config); + } + + if (!isValid(config)) { + config._d = null; + } + + return config; + } + + function configFromInput(config) { + var input = config._i; + if (isUndefined(input)) { + config._d = new Date(hooks.now()); + } else if (isDate(input)) { + config._d = new Date(input.valueOf()); + } else if (typeof input === 'string') { + configFromString(config); + } else if (isArray(input)) { + config._a = map(input.slice(0), function (obj) { + return parseInt(obj, 10); + }); + configFromArray(config); + } else if (isObject(input)) { + configFromObject(config); + } else if (isNumber(input)) { + // from milliseconds + config._d = new Date(input); + } else { + hooks.createFromInputFallback(config); + } + } + + function createLocalOrUTC(input, format, locale, strict, isUTC) { + var c = {}; + + if (locale === true || locale === false) { + strict = locale; + locale = undefined; + } + + if ((isObject(input) && isObjectEmpty(input)) || + (isArray(input) && input.length === 0)) { + input = undefined; + } + // object construction must be done this way. + // https://github.com/moment/moment/issues/1423 + c._isAMomentObject = true; + c._useUTC = c._isUTC = isUTC; + c._l = locale; + c._i = input; + c._f = format; + c._strict = strict; + + return createFromConfig(c); + } + + function createLocal(input, format, locale, strict) { + return createLocalOrUTC(input, format, locale, strict, false); + } + + var prototypeMin = deprecate( + 'moment().min is deprecated, use moment.max instead. http://momentjs.com/guides/#/warnings/min-max/', + function () { + var other = createLocal.apply(null, arguments); + if (this.isValid() && other.isValid()) { + return other < this ? this : other; + } else { + return createInvalid(); + } + } + ); + + var prototypeMax = deprecate( + 'moment().max is deprecated, use moment.min instead. http://momentjs.com/guides/#/warnings/min-max/', + function () { + var other = createLocal.apply(null, arguments); + if (this.isValid() && other.isValid()) { + return other > this ? this : other; + } else { + return createInvalid(); + } + } + ); + + // Pick a moment m from moments so that m[fn](other) is true for all + // other. This relies on the function fn to be transitive. + // + // moments should either be an array of moment objects or an array, whose + // first element is an array of moment objects. + function pickBy(fn, moments) { + var res, i; + if (moments.length === 1 && isArray(moments[0])) { + moments = moments[0]; + } + if (!moments.length) { + return createLocal(); + } + res = moments[0]; + for (i = 1; i < moments.length; ++i) { + if (!moments[i].isValid() || moments[i][fn](res)) { + res = moments[i]; + } + } + return res; + } + + // TODO: Use [].sort instead? + function min() { + var args = [].slice.call(arguments, 0); + + return pickBy('isBefore', args); + } + + function max() { + var args = [].slice.call(arguments, 0); + + return pickBy('isAfter', args); + } + + var now = function () { + return Date.now ? Date.now() : +(new Date()); + }; + + var ordering = ['year', 'quarter', 'month', 'week', 'day', 'hour', 'minute', 'second', 'millisecond']; + + function isDurationValid(m) { + for (var key in m) { + if (!(indexOf.call(ordering, key) !== -1 && (m[key] == null || !isNaN(m[key])))) { + return false; + } + } + + var unitHasDecimal = false; + for (var i = 0; i < ordering.length; ++i) { + if (m[ordering[i]]) { + if (unitHasDecimal) { + return false; // only allow non-integers for smallest unit + } + if (parseFloat(m[ordering[i]]) !== toInt(m[ordering[i]])) { + unitHasDecimal = true; + } + } + } + + return true; + } + + function isValid$1() { + return this._isValid; + } + + function createInvalid$1() { + return createDuration(NaN); + } + + function Duration(duration) { + var normalizedInput = normalizeObjectUnits(duration), + years = normalizedInput.year || 0, + quarters = normalizedInput.quarter || 0, + months = normalizedInput.month || 0, + weeks = normalizedInput.week || 0, + days = normalizedInput.day || 0, + hours = normalizedInput.hour || 0, + minutes = normalizedInput.minute || 0, + seconds = normalizedInput.second || 0, + milliseconds = normalizedInput.millisecond || 0; + + this._isValid = isDurationValid(normalizedInput); + + // representation for dateAddRemove + this._milliseconds = +milliseconds + + seconds * 1e3 + // 1000 + minutes * 6e4 + // 1000 * 60 + hours * 1000 * 60 * 60; //using 1000 * 60 * 60 instead of 36e5 to avoid floating point rounding errors https://github.com/moment/moment/issues/2978 + // Because of dateAddRemove treats 24 hours as different from a + // day when working around DST, we need to store them separately + this._days = +days + + weeks * 7; + // It is impossible to translate months into days without knowing + // which months you are are talking about, so we have to store + // it separately. + this._months = +months + + quarters * 3 + + years * 12; + + this._data = {}; + + this._locale = getLocale(); + + this._bubble(); + } + + function isDuration(obj) { + return obj instanceof Duration; + } + + function absRound(number) { + if (number < 0) { + return Math.round(-1 * number) * -1; + } else { + return Math.round(number); + } + } + + // FORMATTING + + function offset(token, separator) { + addFormatToken(token, 0, 0, function () { + var offset = this.utcOffset(); + var sign = '+'; + if (offset < 0) { + offset = -offset; + sign = '-'; + } + return sign + zeroFill(~~(offset / 60), 2) + separator + zeroFill(~~(offset) % 60, 2); + }); + } + + offset('Z', ':'); + offset('ZZ', ''); + + // PARSING + + addRegexToken('Z', matchShortOffset); + addRegexToken('ZZ', matchShortOffset); + addParseToken(['Z', 'ZZ'], function (input, array, config) { + config._useUTC = true; + config._tzm = offsetFromString(matchShortOffset, input); + }); + + // HELPERS + + // timezone chunker + // '+10:00' > ['10', '00'] + // '-1530' > ['-15', '30'] + var chunkOffset = /([\+\-]|\d\d)/gi; + + function offsetFromString(matcher, string) { + var matches = (string || '').match(matcher); + + if (matches === null) { + return null; + } + + var chunk = matches[matches.length - 1] || []; + var parts = (chunk + '').match(chunkOffset) || ['-', 0, 0]; + var minutes = +(parts[1] * 60) + toInt(parts[2]); + + return minutes === 0 ? + 0 : + parts[0] === '+' ? minutes : -minutes; + } + + // Return a moment from input, that is local/utc/zone equivalent to model. + function cloneWithOffset(input, model) { + var res, diff; + if (model._isUTC) { + res = model.clone(); + diff = (isMoment(input) || isDate(input) ? input.valueOf() : createLocal(input).valueOf()) - res.valueOf(); + // Use low-level api, because this fn is low-level api. + res._d.setTime(res._d.valueOf() + diff); + hooks.updateOffset(res, false); + return res; + } else { + return createLocal(input).local(); + } + } + + function getDateOffset(m) { + // On Firefox.24 Date#getTimezoneOffset returns a floating point. + // https://github.com/moment/moment/pull/1871 + return -Math.round(m._d.getTimezoneOffset() / 15) * 15; + } + + // HOOKS + + // This function will be called whenever a moment is mutated. + // It is intended to keep the offset in sync with the timezone. + hooks.updateOffset = function () { }; + + // MOMENTS + + // keepLocalTime = true means only change the timezone, without + // affecting the local hour. So 5:31:26 +0300 --[utcOffset(2, true)]--> + // 5:31:26 +0200 It is possible that 5:31:26 doesn't exist with offset + // +0200, so we adjust the time as needed, to be valid. + // + // Keeping the time actually adds/subtracts (one hour) + // from the actual represented time. That is why we call updateOffset + // a second time. In case it wants us to change the offset again + // _changeInProgress == true case, then we have to adjust, because + // there is no such time in the given timezone. + function getSetOffset(input, keepLocalTime, keepMinutes) { + var offset = this._offset || 0, + localAdjust; + if (!this.isValid()) { + return input != null ? this : NaN; + } + if (input != null) { + if (typeof input === 'string') { + input = offsetFromString(matchShortOffset, input); + if (input === null) { + return this; + } + } else if (Math.abs(input) < 16 && !keepMinutes) { + input = input * 60; + } + if (!this._isUTC && keepLocalTime) { + localAdjust = getDateOffset(this); + } + this._offset = input; + this._isUTC = true; + if (localAdjust != null) { + this.add(localAdjust, 'm'); + } + if (offset !== input) { + if (!keepLocalTime || this._changeInProgress) { + addSubtract(this, createDuration(input - offset, 'm'), 1, false); + } else if (!this._changeInProgress) { + this._changeInProgress = true; + hooks.updateOffset(this, true); + this._changeInProgress = null; + } + } + return this; + } else { + return this._isUTC ? offset : getDateOffset(this); + } + } + + function getSetZone(input, keepLocalTime) { + if (input != null) { + if (typeof input !== 'string') { + input = -input; + } + + this.utcOffset(input, keepLocalTime); + + return this; + } else { + return -this.utcOffset(); + } + } + + function setOffsetToUTC(keepLocalTime) { + return this.utcOffset(0, keepLocalTime); + } + + function setOffsetToLocal(keepLocalTime) { + if (this._isUTC) { + this.utcOffset(0, keepLocalTime); + this._isUTC = false; + + if (keepLocalTime) { + this.subtract(getDateOffset(this), 'm'); + } + } + return this; + } + + function setOffsetToParsedOffset() { + if (this._tzm != null) { + this.utcOffset(this._tzm, false, true); + } else if (typeof this._i === 'string') { + var tZone = offsetFromString(matchOffset, this._i); + if (tZone != null) { + this.utcOffset(tZone); + } + else { + this.utcOffset(0, true); + } + } + return this; + } + + function hasAlignedHourOffset(input) { + if (!this.isValid()) { + return false; + } + input = input ? createLocal(input).utcOffset() : 0; + + return (this.utcOffset() - input) % 60 === 0; + } + + function isDaylightSavingTime() { + return ( + this.utcOffset() > this.clone().month(0).utcOffset() || + this.utcOffset() > this.clone().month(5).utcOffset() + ); + } + + function isDaylightSavingTimeShifted() { + if (!isUndefined(this._isDSTShifted)) { + return this._isDSTShifted; + } + + var c = {}; + + copyConfig(c, this); + c = prepareConfig(c); + + if (c._a) { + var other = c._isUTC ? createUTC(c._a) : createLocal(c._a); + this._isDSTShifted = this.isValid() && + compareArrays(c._a, other.toArray()) > 0; + } else { + this._isDSTShifted = false; + } + + return this._isDSTShifted; + } + + function isLocal() { + return this.isValid() ? !this._isUTC : false; + } + + function isUtcOffset() { + return this.isValid() ? this._isUTC : false; + } + + function isUtc() { + return this.isValid() ? this._isUTC && this._offset === 0 : false; + } + + // ASP.NET json date format regex + var aspNetRegex = /^(\-|\+)?(?:(\d*)[. ])?(\d+)\:(\d+)(?:\:(\d+)(\.\d*)?)?$/; + + // from http://docs.closure-library.googlecode.com/git/closure_goog_date_date.js.source.html + // somewhat more in line with 4.4.3.2 2004 spec, but allows decimal anywhere + // and further modified to allow for strings containing both week and day + var isoRegex = /^(-|\+)?P(?:([-+]?[0-9,.]*)Y)?(?:([-+]?[0-9,.]*)M)?(?:([-+]?[0-9,.]*)W)?(?:([-+]?[0-9,.]*)D)?(?:T(?:([-+]?[0-9,.]*)H)?(?:([-+]?[0-9,.]*)M)?(?:([-+]?[0-9,.]*)S)?)?$/; + + function createDuration(input, key) { + var duration = input, + // matching against regexp is expensive, do it on demand + match = null, + sign, + ret, + diffRes; + + if (isDuration(input)) { + duration = { + ms: input._milliseconds, + d: input._days, + M: input._months + }; + } else if (isNumber(input)) { + duration = {}; + if (key) { + duration[key] = input; + } else { + duration.milliseconds = input; + } + } else if (!!(match = aspNetRegex.exec(input))) { + sign = (match[1] === '-') ? -1 : 1; + duration = { + y: 0, + d: toInt(match[DATE]) * sign, + h: toInt(match[HOUR]) * sign, + m: toInt(match[MINUTE]) * sign, + s: toInt(match[SECOND]) * sign, + ms: toInt(absRound(match[MILLISECOND] * 1000)) * sign // the millisecond decimal point is included in the match + }; + } else if (!!(match = isoRegex.exec(input))) { + sign = (match[1] === '-') ? -1 : (match[1] === '+') ? 1 : 1; + duration = { + y: parseIso(match[2], sign), + M: parseIso(match[3], sign), + w: parseIso(match[4], sign), + d: parseIso(match[5], sign), + h: parseIso(match[6], sign), + m: parseIso(match[7], sign), + s: parseIso(match[8], sign) + }; + } else if (duration == null) {// checks for null or undefined + duration = {}; + } else if (typeof duration === 'object' && ('from' in duration || 'to' in duration)) { + diffRes = momentsDifference(createLocal(duration.from), createLocal(duration.to)); + + duration = {}; + duration.ms = diffRes.milliseconds; + duration.M = diffRes.months; + } + + ret = new Duration(duration); + + if (isDuration(input) && hasOwnProp(input, '_locale')) { + ret._locale = input._locale; + } + + return ret; + } + + createDuration.fn = Duration.prototype; + createDuration.invalid = createInvalid$1; + + function parseIso(inp, sign) { + // We'd normally use ~~inp for this, but unfortunately it also + // converts floats to ints. + // inp may be undefined, so careful calling replace on it. + var res = inp && parseFloat(inp.replace(',', '.')); + // apply sign while we're at it + return (isNaN(res) ? 0 : res) * sign; + } + + function positiveMomentsDifference(base, other) { + var res = { milliseconds: 0, months: 0 }; + + res.months = other.month() - base.month() + + (other.year() - base.year()) * 12; + if (base.clone().add(res.months, 'M').isAfter(other)) { + --res.months; + } + + res.milliseconds = +other - +(base.clone().add(res.months, 'M')); + + return res; + } + + function momentsDifference(base, other) { + var res; + if (!(base.isValid() && other.isValid())) { + return { milliseconds: 0, months: 0 }; + } + + other = cloneWithOffset(other, base); + if (base.isBefore(other)) { + res = positiveMomentsDifference(base, other); + } else { + res = positiveMomentsDifference(other, base); + res.milliseconds = -res.milliseconds; + res.months = -res.months; + } + + return res; + } + + // TODO: remove 'name' arg after deprecation is removed + function createAdder(direction, name) { + return function (val, period) { + var dur, tmp; + //invert the arguments, but complain about it + if (period !== null && !isNaN(+period)) { + deprecateSimple(name, 'moment().' + name + '(period, number) is deprecated. Please use moment().' + name + '(number, period). ' + + 'See http://momentjs.com/guides/#/warnings/add-inverted-param/ for more info.'); + tmp = val; val = period; period = tmp; + } + + val = typeof val === 'string' ? +val : val; + dur = createDuration(val, period); + addSubtract(this, dur, direction); + return this; + }; + } + + function addSubtract(mom, duration, isAdding, updateOffset) { + var milliseconds = duration._milliseconds, + days = absRound(duration._days), + months = absRound(duration._months); + + if (!mom.isValid()) { + // No op + return; + } + + updateOffset = updateOffset == null ? true : updateOffset; + + if (months) { + setMonth(mom, get(mom, 'Month') + months * isAdding); + } + if (days) { + set$1(mom, 'Date', get(mom, 'Date') + days * isAdding); + } + if (milliseconds) { + mom._d.setTime(mom._d.valueOf() + milliseconds * isAdding); + } + if (updateOffset) { + hooks.updateOffset(mom, days || months); + } + } + + var add = createAdder(1, 'add'); + var subtract = createAdder(-1, 'subtract'); + + function getCalendarFormat(myMoment, now) { + var diff = myMoment.diff(now, 'days', true); + return diff < -6 ? 'sameElse' : + diff < -1 ? 'lastWeek' : + diff < 0 ? 'lastDay' : + diff < 1 ? 'sameDay' : + diff < 2 ? 'nextDay' : + diff < 7 ? 'nextWeek' : 'sameElse'; + } + + function calendar$1(time, formats) { + // We want to compare the start of today, vs this. + // Getting start-of-today depends on whether we're local/utc/offset or not. + var now = time || createLocal(), + sod = cloneWithOffset(now, this).startOf('day'), + format = hooks.calendarFormat(this, sod) || 'sameElse'; + + var output = formats && (isFunction(formats[format]) ? formats[format].call(this, now) : formats[format]); + + return this.format(output || this.localeData().calendar(format, this, createLocal(now))); + } + + function clone() { + return new Moment(this); + } + + function isAfter(input, units) { + var localInput = isMoment(input) ? input : createLocal(input); + if (!(this.isValid() && localInput.isValid())) { + return false; + } + units = normalizeUnits(!isUndefined(units) ? units : 'millisecond'); + if (units === 'millisecond') { + return this.valueOf() > localInput.valueOf(); + } else { + return localInput.valueOf() < this.clone().startOf(units).valueOf(); + } + } + + function isBefore(input, units) { + var localInput = isMoment(input) ? input : createLocal(input); + if (!(this.isValid() && localInput.isValid())) { + return false; + } + units = normalizeUnits(!isUndefined(units) ? units : 'millisecond'); + if (units === 'millisecond') { + return this.valueOf() < localInput.valueOf(); + } else { + return this.clone().endOf(units).valueOf() < localInput.valueOf(); + } + } + + function isBetween(from, to, units, inclusivity) { + inclusivity = inclusivity || '()'; + return (inclusivity[0] === '(' ? this.isAfter(from, units) : !this.isBefore(from, units)) && + (inclusivity[1] === ')' ? this.isBefore(to, units) : !this.isAfter(to, units)); + } + + function isSame(input, units) { + var localInput = isMoment(input) ? input : createLocal(input), + inputMs; + if (!(this.isValid() && localInput.isValid())) { + return false; + } + units = normalizeUnits(units || 'millisecond'); + if (units === 'millisecond') { + return this.valueOf() === localInput.valueOf(); + } else { + inputMs = localInput.valueOf(); + return this.clone().startOf(units).valueOf() <= inputMs && inputMs <= this.clone().endOf(units).valueOf(); + } + } + + function isSameOrAfter(input, units) { + return this.isSame(input, units) || this.isAfter(input, units); + } + + function isSameOrBefore(input, units) { + return this.isSame(input, units) || this.isBefore(input, units); + } + + function diff(input, units, asFloat) { + var that, + zoneDelta, + delta, output; + + if (!this.isValid()) { + return NaN; + } + + that = cloneWithOffset(input, this); + + if (!that.isValid()) { + return NaN; + } + + zoneDelta = (that.utcOffset() - this.utcOffset()) * 6e4; + + units = normalizeUnits(units); + + switch (units) { + case 'year': output = monthDiff(this, that) / 12; break; + case 'month': output = monthDiff(this, that); break; + case 'quarter': output = monthDiff(this, that) / 3; break; + case 'second': output = (this - that) / 1e3; break; // 1000 + case 'minute': output = (this - that) / 6e4; break; // 1000 * 60 + case 'hour': output = (this - that) / 36e5; break; // 1000 * 60 * 60 + case 'day': output = (this - that - zoneDelta) / 864e5; break; // 1000 * 60 * 60 * 24, negate dst + case 'week': output = (this - that - zoneDelta) / 6048e5; break; // 1000 * 60 * 60 * 24 * 7, negate dst + default: output = this - that; + } + + return asFloat ? output : absFloor(output); + } + + function monthDiff(a, b) { + // difference in months + var wholeMonthDiff = ((b.year() - a.year()) * 12) + (b.month() - a.month()), + // b is in (anchor - 1 month, anchor + 1 month) + anchor = a.clone().add(wholeMonthDiff, 'months'), + anchor2, adjust; + + if (b - anchor < 0) { + anchor2 = a.clone().add(wholeMonthDiff - 1, 'months'); + // linear across the month + adjust = (b - anchor) / (anchor - anchor2); + } else { + anchor2 = a.clone().add(wholeMonthDiff + 1, 'months'); + // linear across the month + adjust = (b - anchor) / (anchor2 - anchor); + } + + //check for negative zero, return zero if negative zero + return -(wholeMonthDiff + adjust) || 0; + } + + hooks.defaultFormat = 'YYYY-MM-DDTHH:mm:ssZ'; + hooks.defaultFormatUtc = 'YYYY-MM-DDTHH:mm:ss[Z]'; + + function toString() { + return this.clone().locale('en').format('ddd MMM DD YYYY HH:mm:ss [GMT]ZZ'); + } + + function toISOString(keepOffset) { + if (!this.isValid()) { + return null; + } + var utc = keepOffset !== true; + var m = utc ? this.clone().utc() : this; + if (m.year() < 0 || m.year() > 9999) { + return formatMoment(m, utc ? 'YYYYYY-MM-DD[T]HH:mm:ss.SSS[Z]' : 'YYYYYY-MM-DD[T]HH:mm:ss.SSSZ'); + } + if (isFunction(Date.prototype.toISOString)) { + // native implementation is ~50x faster, use it when we can + if (utc) { + return this.toDate().toISOString(); + } else { + return new Date(this._d.valueOf()).toISOString().replace('Z', formatMoment(m, 'Z')); + } + } + return formatMoment(m, utc ? 'YYYY-MM-DD[T]HH:mm:ss.SSS[Z]' : 'YYYY-MM-DD[T]HH:mm:ss.SSSZ'); + } + + /** + * Return a human readable representation of a moment that can + * also be evaluated to get a new moment which is the same + * + * @link https://nodejs.org/dist/latest/docs/api/util.html#util_custom_inspect_function_on_objects + */ + function inspect() { + if (!this.isValid()) { + return 'moment.invalid(/* ' + this._i + ' */)'; + } + var func = 'moment'; + var zone = ''; + if (!this.isLocal()) { + func = this.utcOffset() === 0 ? 'moment.utc' : 'moment.parseZone'; + zone = 'Z'; + } + var prefix = '[' + func + '("]'; + var year = (0 <= this.year() && this.year() <= 9999) ? 'YYYY' : 'YYYYYY'; + var datetime = '-MM-DD[T]HH:mm:ss.SSS'; + var suffix = zone + '[")]'; + + return this.format(prefix + year + datetime + suffix); + } + + function format(inputString) { + if (!inputString) { + inputString = this.isUtc() ? hooks.defaultFormatUtc : hooks.defaultFormat; + } + var output = formatMoment(this, inputString); + return this.localeData().postformat(output); + } + + function from(time, withoutSuffix) { + if (this.isValid() && + ((isMoment(time) && time.isValid()) || + createLocal(time).isValid())) { + return createDuration({ to: this, from: time }).locale(this.locale()).humanize(!withoutSuffix); + } else { + return this.localeData().invalidDate(); + } + } + + function fromNow(withoutSuffix) { + return this.from(createLocal(), withoutSuffix); + } + + function to(time, withoutSuffix) { + if (this.isValid() && + ((isMoment(time) && time.isValid()) || + createLocal(time).isValid())) { + return createDuration({ from: this, to: time }).locale(this.locale()).humanize(!withoutSuffix); + } else { + return this.localeData().invalidDate(); + } + } + + function toNow(withoutSuffix) { + return this.to(createLocal(), withoutSuffix); + } + + // If passed a locale key, it will set the locale for this + // instance. Otherwise, it will return the locale configuration + // variables for this instance. + function locale(key) { + var newLocaleData; + + if (key === undefined) { + return this._locale._abbr; + } else { + newLocaleData = getLocale(key); + if (newLocaleData != null) { + this._locale = newLocaleData; + } + return this; + } + } + + var lang = deprecate( + 'moment().lang() is deprecated. Instead, use moment().localeData() to get the language configuration. Use moment().locale() to change languages.', + function (key) { + if (key === undefined) { + return this.localeData(); + } else { + return this.locale(key); + } + } + ); + + function localeData() { + return this._locale; + } + + function startOf(units) { + units = normalizeUnits(units); + // the following switch intentionally omits break keywords + // to utilize falling through the cases. + switch (units) { + case 'year': + this.month(0); + /* falls through */ + case 'quarter': + case 'month': + this.date(1); + /* falls through */ + case 'week': + case 'isoWeek': + case 'day': + case 'date': + this.hours(0); + /* falls through */ + case 'hour': + this.minutes(0); + /* falls through */ + case 'minute': + this.seconds(0); + /* falls through */ + case 'second': + this.milliseconds(0); + } + + // weeks are a special case + if (units === 'week') { + this.weekday(0); + } + if (units === 'isoWeek') { + this.isoWeekday(1); + } + + // quarters are also special + if (units === 'quarter') { + this.month(Math.floor(this.month() / 3) * 3); + } + + return this; + } + + function endOf(units) { + units = normalizeUnits(units); + if (units === undefined || units === 'millisecond') { + return this; + } + + // 'date' is an alias for 'day', so it should be considered as such. + if (units === 'date') { + units = 'day'; + } + + return this.startOf(units).add(1, (units === 'isoWeek' ? 'week' : units)).subtract(1, 'ms'); + } + + function valueOf() { + return this._d.valueOf() - ((this._offset || 0) * 60000); + } + + function unix() { + return Math.floor(this.valueOf() / 1000); + } + + function toDate() { + return new Date(this.valueOf()); + } + + function toArray() { + var m = this; + return [m.year(), m.month(), m.date(), m.hour(), m.minute(), m.second(), m.millisecond()]; + } + + function toObject() { + var m = this; + return { + years: m.year(), + months: m.month(), + date: m.date(), + hours: m.hours(), + minutes: m.minutes(), + seconds: m.seconds(), + milliseconds: m.milliseconds() + }; + } + + function toJSON() { + // new Date(NaN).toJSON() === null + return this.isValid() ? this.toISOString() : null; + } + + function isValid$2() { + return isValid(this); + } + + function parsingFlags() { + return extend({}, getParsingFlags(this)); + } + + function invalidAt() { + return getParsingFlags(this).overflow; + } + + function creationData() { + return { + input: this._i, + format: this._f, + locale: this._locale, + isUTC: this._isUTC, + strict: this._strict + }; + } + + // FORMATTING + + addFormatToken(0, ['gg', 2], 0, function () { + return this.weekYear() % 100; + }); + + addFormatToken(0, ['GG', 2], 0, function () { + return this.isoWeekYear() % 100; + }); + + function addWeekYearFormatToken(token, getter) { + addFormatToken(0, [token, token.length], 0, getter); + } + + addWeekYearFormatToken('gggg', 'weekYear'); + addWeekYearFormatToken('ggggg', 'weekYear'); + addWeekYearFormatToken('GGGG', 'isoWeekYear'); + addWeekYearFormatToken('GGGGG', 'isoWeekYear'); + + // ALIASES + + addUnitAlias('weekYear', 'gg'); + addUnitAlias('isoWeekYear', 'GG'); + + // PRIORITY + + addUnitPriority('weekYear', 1); + addUnitPriority('isoWeekYear', 1); + + + // PARSING + + addRegexToken('G', matchSigned); + addRegexToken('g', matchSigned); + addRegexToken('GG', match1to2, match2); + addRegexToken('gg', match1to2, match2); + addRegexToken('GGGG', match1to4, match4); + addRegexToken('gggg', match1to4, match4); + addRegexToken('GGGGG', match1to6, match6); + addRegexToken('ggggg', match1to6, match6); + + addWeekParseToken(['gggg', 'ggggg', 'GGGG', 'GGGGG'], function (input, week, config, token) { + week[token.substr(0, 2)] = toInt(input); + }); + + addWeekParseToken(['gg', 'GG'], function (input, week, config, token) { + week[token] = hooks.parseTwoDigitYear(input); + }); + + // MOMENTS + + function getSetWeekYear(input) { + return getSetWeekYearHelper.call(this, + input, + this.week(), + this.weekday(), + this.localeData()._week.dow, + this.localeData()._week.doy); + } + + function getSetISOWeekYear(input) { + return getSetWeekYearHelper.call(this, + input, this.isoWeek(), this.isoWeekday(), 1, 4); + } + + function getISOWeeksInYear() { + return weeksInYear(this.year(), 1, 4); + } + + function getWeeksInYear() { + var weekInfo = this.localeData()._week; + return weeksInYear(this.year(), weekInfo.dow, weekInfo.doy); + } + + function getSetWeekYearHelper(input, week, weekday, dow, doy) { + var weeksTarget; + if (input == null) { + return weekOfYear(this, dow, doy).year; + } else { + weeksTarget = weeksInYear(input, dow, doy); + if (week > weeksTarget) { + week = weeksTarget; + } + return setWeekAll.call(this, input, week, weekday, dow, doy); + } + } + + function setWeekAll(weekYear, week, weekday, dow, doy) { + var dayOfYearData = dayOfYearFromWeeks(weekYear, week, weekday, dow, doy), + date = createUTCDate(dayOfYearData.year, 0, dayOfYearData.dayOfYear); + + this.year(date.getUTCFullYear()); + this.month(date.getUTCMonth()); + this.date(date.getUTCDate()); + return this; + } + + // FORMATTING + + addFormatToken('Q', 0, 'Qo', 'quarter'); + + // ALIASES + + addUnitAlias('quarter', 'Q'); + + // PRIORITY + + addUnitPriority('quarter', 7); + + // PARSING + + addRegexToken('Q', match1); + addParseToken('Q', function (input, array) { + array[MONTH] = (toInt(input) - 1) * 3; + }); + + // MOMENTS + + function getSetQuarter(input) { + return input == null ? Math.ceil((this.month() + 1) / 3) : this.month((input - 1) * 3 + this.month() % 3); + } + + // FORMATTING + + addFormatToken('D', ['DD', 2], 'Do', 'date'); + + // ALIASES + + addUnitAlias('date', 'D'); + + // PRIOROITY + addUnitPriority('date', 9); + + // PARSING + + addRegexToken('D', match1to2); + addRegexToken('DD', match1to2, match2); + addRegexToken('Do', function (isStrict, locale) { + // TODO: Remove "ordinalParse" fallback in next major release. + return isStrict ? + (locale._dayOfMonthOrdinalParse || locale._ordinalParse) : + locale._dayOfMonthOrdinalParseLenient; + }); + + addParseToken(['D', 'DD'], DATE); + addParseToken('Do', function (input, array) { + array[DATE] = toInt(input.match(match1to2)[0]); + }); + + // MOMENTS + + var getSetDayOfMonth = makeGetSet('Date', true); + + // FORMATTING + + addFormatToken('DDD', ['DDDD', 3], 'DDDo', 'dayOfYear'); + + // ALIASES + + addUnitAlias('dayOfYear', 'DDD'); + + // PRIORITY + addUnitPriority('dayOfYear', 4); + + // PARSING + + addRegexToken('DDD', match1to3); + addRegexToken('DDDD', match3); + addParseToken(['DDD', 'DDDD'], function (input, array, config) { + config._dayOfYear = toInt(input); + }); + + // HELPERS + + // MOMENTS + + function getSetDayOfYear(input) { + var dayOfYear = Math.round((this.clone().startOf('day') - this.clone().startOf('year')) / 864e5) + 1; + return input == null ? dayOfYear : this.add((input - dayOfYear), 'd'); + } + + // FORMATTING + + addFormatToken('m', ['mm', 2], 0, 'minute'); + + // ALIASES + + addUnitAlias('minute', 'm'); + + // PRIORITY + + addUnitPriority('minute', 14); + + // PARSING + + addRegexToken('m', match1to2); + addRegexToken('mm', match1to2, match2); + addParseToken(['m', 'mm'], MINUTE); + + // MOMENTS + + var getSetMinute = makeGetSet('Minutes', false); + + // FORMATTING + + addFormatToken('s', ['ss', 2], 0, 'second'); + + // ALIASES + + addUnitAlias('second', 's'); + + // PRIORITY + + addUnitPriority('second', 15); + + // PARSING + + addRegexToken('s', match1to2); + addRegexToken('ss', match1to2, match2); + addParseToken(['s', 'ss'], SECOND); + + // MOMENTS + + var getSetSecond = makeGetSet('Seconds', false); + + // FORMATTING + + addFormatToken('S', 0, 0, function () { + return ~~(this.millisecond() / 100); + }); + + addFormatToken(0, ['SS', 2], 0, function () { + return ~~(this.millisecond() / 10); + }); + + addFormatToken(0, ['SSS', 3], 0, 'millisecond'); + addFormatToken(0, ['SSSS', 4], 0, function () { + return this.millisecond() * 10; + }); + addFormatToken(0, ['SSSSS', 5], 0, function () { + return this.millisecond() * 100; + }); + addFormatToken(0, ['SSSSSS', 6], 0, function () { + return this.millisecond() * 1000; + }); + addFormatToken(0, ['SSSSSSS', 7], 0, function () { + return this.millisecond() * 10000; + }); + addFormatToken(0, ['SSSSSSSS', 8], 0, function () { + return this.millisecond() * 100000; + }); + addFormatToken(0, ['SSSSSSSSS', 9], 0, function () { + return this.millisecond() * 1000000; + }); + + + // ALIASES + + addUnitAlias('millisecond', 'ms'); + + // PRIORITY + + addUnitPriority('millisecond', 16); + + // PARSING + + addRegexToken('S', match1to3, match1); + addRegexToken('SS', match1to3, match2); + addRegexToken('SSS', match1to3, match3); + + var token; + for (token = 'SSSS'; token.length <= 9; token += 'S') { + addRegexToken(token, matchUnsigned); + } + + function parseMs(input, array) { + array[MILLISECOND] = toInt(('0.' + input) * 1000); + } + + for (token = 'S'; token.length <= 9; token += 'S') { + addParseToken(token, parseMs); + } + // MOMENTS + + var getSetMillisecond = makeGetSet('Milliseconds', false); + + // FORMATTING + + addFormatToken('z', 0, 0, 'zoneAbbr'); + addFormatToken('zz', 0, 0, 'zoneName'); + + // MOMENTS + + function getZoneAbbr() { + return this._isUTC ? 'UTC' : ''; + } + + function getZoneName() { + return this._isUTC ? 'Coordinated Universal Time' : ''; + } + + var proto = Moment.prototype; + + proto.add = add; + proto.calendar = calendar$1; + proto.clone = clone; + proto.diff = diff; + proto.endOf = endOf; + proto.format = format; + proto.from = from; + proto.fromNow = fromNow; + proto.to = to; + proto.toNow = toNow; + proto.get = stringGet; + proto.invalidAt = invalidAt; + proto.isAfter = isAfter; + proto.isBefore = isBefore; + proto.isBetween = isBetween; + proto.isSame = isSame; + proto.isSameOrAfter = isSameOrAfter; + proto.isSameOrBefore = isSameOrBefore; + proto.isValid = isValid$2; + proto.lang = lang; + proto.locale = locale; + proto.localeData = localeData; + proto.max = prototypeMax; + proto.min = prototypeMin; + proto.parsingFlags = parsingFlags; + proto.set = stringSet; + proto.startOf = startOf; + proto.subtract = subtract; + proto.toArray = toArray; + proto.toObject = toObject; + proto.toDate = toDate; + proto.toISOString = toISOString; + proto.inspect = inspect; + proto.toJSON = toJSON; + proto.toString = toString; + proto.unix = unix; + proto.valueOf = valueOf; + proto.creationData = creationData; + + // Year + proto.year = getSetYear; + proto.isLeapYear = getIsLeapYear; + + // Week Year + proto.weekYear = getSetWeekYear; + proto.isoWeekYear = getSetISOWeekYear; + + // Quarter + proto.quarter = proto.quarters = getSetQuarter; + + // Month + proto.month = getSetMonth; + proto.daysInMonth = getDaysInMonth; + + // Week + proto.week = proto.weeks = getSetWeek; + proto.isoWeek = proto.isoWeeks = getSetISOWeek; + proto.weeksInYear = getWeeksInYear; + proto.isoWeeksInYear = getISOWeeksInYear; + + // Day + proto.date = getSetDayOfMonth; + proto.day = proto.days = getSetDayOfWeek; + proto.weekday = getSetLocaleDayOfWeek; + proto.isoWeekday = getSetISODayOfWeek; + proto.dayOfYear = getSetDayOfYear; + + // Hour + proto.hour = proto.hours = getSetHour; + + // Minute + proto.minute = proto.minutes = getSetMinute; + + // Second + proto.second = proto.seconds = getSetSecond; + + // Millisecond + proto.millisecond = proto.milliseconds = getSetMillisecond; + + // Offset + proto.utcOffset = getSetOffset; + proto.utc = setOffsetToUTC; + proto.local = setOffsetToLocal; + proto.parseZone = setOffsetToParsedOffset; + proto.hasAlignedHourOffset = hasAlignedHourOffset; + proto.isDST = isDaylightSavingTime; + proto.isLocal = isLocal; + proto.isUtcOffset = isUtcOffset; + proto.isUtc = isUtc; + proto.isUTC = isUtc; + + // Timezone + proto.zoneAbbr = getZoneAbbr; + proto.zoneName = getZoneName; + + // Deprecations + proto.dates = deprecate('dates accessor is deprecated. Use date instead.', getSetDayOfMonth); + proto.months = deprecate('months accessor is deprecated. Use month instead', getSetMonth); + proto.years = deprecate('years accessor is deprecated. Use year instead', getSetYear); + proto.zone = deprecate('moment().zone is deprecated, use moment().utcOffset instead. http://momentjs.com/guides/#/warnings/zone/', getSetZone); + proto.isDSTShifted = deprecate('isDSTShifted is deprecated. See http://momentjs.com/guides/#/warnings/dst-shifted/ for more information', isDaylightSavingTimeShifted); + + function createUnix(input) { + return createLocal(input * 1000); + } + + function createInZone() { + return createLocal.apply(null, arguments).parseZone(); + } + + function preParsePostFormat(string) { + return string; + } + + var proto$1 = Locale.prototype; + + proto$1.calendar = calendar; + proto$1.longDateFormat = longDateFormat; + proto$1.invalidDate = invalidDate; + proto$1.ordinal = ordinal; + proto$1.preparse = preParsePostFormat; + proto$1.postformat = preParsePostFormat; + proto$1.relativeTime = relativeTime; + proto$1.pastFuture = pastFuture; + proto$1.set = set; + + // Month + proto$1.months = localeMonths; + proto$1.monthsShort = localeMonthsShort; + proto$1.monthsParse = localeMonthsParse; + proto$1.monthsRegex = monthsRegex; + proto$1.monthsShortRegex = monthsShortRegex; + + // Week + proto$1.week = localeWeek; + proto$1.firstDayOfYear = localeFirstDayOfYear; + proto$1.firstDayOfWeek = localeFirstDayOfWeek; + + // Day of Week + proto$1.weekdays = localeWeekdays; + proto$1.weekdaysMin = localeWeekdaysMin; + proto$1.weekdaysShort = localeWeekdaysShort; + proto$1.weekdaysParse = localeWeekdaysParse; + + proto$1.weekdaysRegex = weekdaysRegex; + proto$1.weekdaysShortRegex = weekdaysShortRegex; + proto$1.weekdaysMinRegex = weekdaysMinRegex; + + // Hours + proto$1.isPM = localeIsPM; + proto$1.meridiem = localeMeridiem; + + function get$1(format, index, field, setter) { + var locale = getLocale(); + var utc = createUTC().set(setter, index); + return locale[field](utc, format); + } + + function listMonthsImpl(format, index, field) { + if (isNumber(format)) { + index = format; + format = undefined; + } + + format = format || ''; + + if (index != null) { + return get$1(format, index, field, 'month'); + } + + var i; + var out = []; + for (i = 0; i < 12; i++) { + out[i] = get$1(format, i, field, 'month'); + } + return out; + } + + // () + // (5) + // (fmt, 5) + // (fmt) + // (true) + // (true, 5) + // (true, fmt, 5) + // (true, fmt) + function listWeekdaysImpl(localeSorted, format, index, field) { + if (typeof localeSorted === 'boolean') { + if (isNumber(format)) { + index = format; + format = undefined; + } + + format = format || ''; + } else { + format = localeSorted; + index = format; + localeSorted = false; + + if (isNumber(format)) { + index = format; + format = undefined; + } + + format = format || ''; + } + + var locale = getLocale(), + shift = localeSorted ? locale._week.dow : 0; + + if (index != null) { + return get$1(format, (index + shift) % 7, field, 'day'); + } + + var i; + var out = []; + for (i = 0; i < 7; i++) { + out[i] = get$1(format, (i + shift) % 7, field, 'day'); + } + return out; + } + + function listMonths(format, index) { + return listMonthsImpl(format, index, 'months'); + } + + function listMonthsShort(format, index) { + return listMonthsImpl(format, index, 'monthsShort'); + } + + function listWeekdays(localeSorted, format, index) { + return listWeekdaysImpl(localeSorted, format, index, 'weekdays'); + } + + function listWeekdaysShort(localeSorted, format, index) { + return listWeekdaysImpl(localeSorted, format, index, 'weekdaysShort'); + } + + function listWeekdaysMin(localeSorted, format, index) { + return listWeekdaysImpl(localeSorted, format, index, 'weekdaysMin'); + } + + getSetGlobalLocale('en', { + dayOfMonthOrdinalParse: /\d{1,2}(th|st|nd|rd)/, + ordinal: function (number) { + var b = number % 10, + output = (toInt(number % 100 / 10) === 1) ? 'th' : + (b === 1) ? 'st' : + (b === 2) ? 'nd' : + (b === 3) ? 'rd' : 'th'; + return number + output; + } + }); + + // Side effect imports + hooks.lang = deprecate('moment.lang is deprecated. Use moment.locale instead.', getSetGlobalLocale); + hooks.langData = deprecate('moment.langData is deprecated. Use moment.localeData instead.', getLocale); + + var mathAbs = Math.abs; + + function abs() { + var data = this._data; + + this._milliseconds = mathAbs(this._milliseconds); + this._days = mathAbs(this._days); + this._months = mathAbs(this._months); + + data.milliseconds = mathAbs(data.milliseconds); + data.seconds = mathAbs(data.seconds); + data.minutes = mathAbs(data.minutes); + data.hours = mathAbs(data.hours); + data.months = mathAbs(data.months); + data.years = mathAbs(data.years); + + return this; + } + + function addSubtract$1(duration, input, value, direction) { + var other = createDuration(input, value); + + duration._milliseconds += direction * other._milliseconds; + duration._days += direction * other._days; + duration._months += direction * other._months; + + return duration._bubble(); + } + + // supports only 2.0-style add(1, 's') or add(duration) + function add$1(input, value) { + return addSubtract$1(this, input, value, 1); + } + + // supports only 2.0-style subtract(1, 's') or subtract(duration) + function subtract$1(input, value) { + return addSubtract$1(this, input, value, -1); + } + + function absCeil(number) { + if (number < 0) { + return Math.floor(number); + } else { + return Math.ceil(number); + } + } + + function bubble() { + var milliseconds = this._milliseconds; + var days = this._days; + var months = this._months; + var data = this._data; + var seconds, minutes, hours, years, monthsFromDays; + + // if we have a mix of positive and negative values, bubble down first + // check: https://github.com/moment/moment/issues/2166 + if (!((milliseconds >= 0 && days >= 0 && months >= 0) || + (milliseconds <= 0 && days <= 0 && months <= 0))) { + milliseconds += absCeil(monthsToDays(months) + days) * 864e5; + days = 0; + months = 0; + } + + // The following code bubbles up values, see the tests for + // examples of what that means. + data.milliseconds = milliseconds % 1000; + + seconds = absFloor(milliseconds / 1000); + data.seconds = seconds % 60; + + minutes = absFloor(seconds / 60); + data.minutes = minutes % 60; + + hours = absFloor(minutes / 60); + data.hours = hours % 24; + + days += absFloor(hours / 24); + + // convert days to months + monthsFromDays = absFloor(daysToMonths(days)); + months += monthsFromDays; + days -= absCeil(monthsToDays(monthsFromDays)); + + // 12 months -> 1 year + years = absFloor(months / 12); + months %= 12; + + data.days = days; + data.months = months; + data.years = years; + + return this; + } + + function daysToMonths(days) { + // 400 years have 146097 days (taking into account leap year rules) + // 400 years have 12 months === 4800 + return days * 4800 / 146097; + } + + function monthsToDays(months) { + // the reverse of daysToMonths + return months * 146097 / 4800; + } + + function as(units) { + if (!this.isValid()) { + return NaN; + } + var days; + var months; + var milliseconds = this._milliseconds; + + units = normalizeUnits(units); + + if (units === 'month' || units === 'year') { + days = this._days + milliseconds / 864e5; + months = this._months + daysToMonths(days); + return units === 'month' ? months : months / 12; + } else { + // handle milliseconds separately because of floating point math errors (issue #1867) + days = this._days + Math.round(monthsToDays(this._months)); + switch (units) { + case 'week': return days / 7 + milliseconds / 6048e5; + case 'day': return days + milliseconds / 864e5; + case 'hour': return days * 24 + milliseconds / 36e5; + case 'minute': return days * 1440 + milliseconds / 6e4; + case 'second': return days * 86400 + milliseconds / 1000; + // Math.floor prevents floating point math errors here + case 'millisecond': return Math.floor(days * 864e5) + milliseconds; + default: throw new Error('Unknown unit ' + units); + } + } + } + + // TODO: Use this.as('ms')? + function valueOf$1() { + if (!this.isValid()) { + return NaN; + } + return ( + this._milliseconds + + this._days * 864e5 + + (this._months % 12) * 2592e6 + + toInt(this._months / 12) * 31536e6 + ); + } + + function makeAs(alias) { + return function () { + return this.as(alias); + }; + } + + var asMilliseconds = makeAs('ms'); + var asSeconds = makeAs('s'); + var asMinutes = makeAs('m'); + var asHours = makeAs('h'); + var asDays = makeAs('d'); + var asWeeks = makeAs('w'); + var asMonths = makeAs('M'); + var asYears = makeAs('y'); + + function clone$1() { + return createDuration(this); + } + + function get$2(units) { + units = normalizeUnits(units); + return this.isValid() ? this[units + 's']() : NaN; + } + + function makeGetter(name) { + return function () { + return this.isValid() ? this._data[name] : NaN; + }; + } + + var milliseconds = makeGetter('milliseconds'); + var seconds = makeGetter('seconds'); + var minutes = makeGetter('minutes'); + var hours = makeGetter('hours'); + var days = makeGetter('days'); + var months = makeGetter('months'); + var years = makeGetter('years'); + + function weeks() { + return absFloor(this.days() / 7); + } + + var round = Math.round; + var thresholds = { + ss: 44, // a few seconds to seconds + s: 45, // seconds to minute + m: 45, // minutes to hour + h: 22, // hours to day + d: 26, // days to month + M: 11 // months to year + }; + + // helper function for moment.fn.from, moment.fn.fromNow, and moment.duration.fn.humanize + function substituteTimeAgo(string, number, withoutSuffix, isFuture, locale) { + return locale.relativeTime(number || 1, !!withoutSuffix, string, isFuture); + } + + function relativeTime$1(posNegDuration, withoutSuffix, locale) { + var duration = createDuration(posNegDuration).abs(); + var seconds = round(duration.as('s')); + var minutes = round(duration.as('m')); + var hours = round(duration.as('h')); + var days = round(duration.as('d')); + var months = round(duration.as('M')); + var years = round(duration.as('y')); + + var a = seconds <= thresholds.ss && ['s', seconds] || + seconds < thresholds.s && ['ss', seconds] || + minutes <= 1 && ['m'] || + minutes < thresholds.m && ['mm', minutes] || + hours <= 1 && ['h'] || + hours < thresholds.h && ['hh', hours] || + days <= 1 && ['d'] || + days < thresholds.d && ['dd', days] || + months <= 1 && ['M'] || + months < thresholds.M && ['MM', months] || + years <= 1 && ['y'] || ['yy', years]; + + a[2] = withoutSuffix; + a[3] = +posNegDuration > 0; + a[4] = locale; + return substituteTimeAgo.apply(null, a); + } + + // This function allows you to set the rounding function for relative time strings + function getSetRelativeTimeRounding(roundingFunction) { + if (roundingFunction === undefined) { + return round; + } + if (typeof (roundingFunction) === 'function') { + round = roundingFunction; + return true; + } + return false; + } + + // This function allows you to set a threshold for relative time strings + function getSetRelativeTimeThreshold(threshold, limit) { + if (thresholds[threshold] === undefined) { + return false; + } + if (limit === undefined) { + return thresholds[threshold]; + } + thresholds[threshold] = limit; + if (threshold === 's') { + thresholds.ss = limit - 1; + } + return true; + } + + function humanize(withSuffix) { + if (!this.isValid()) { + return this.localeData().invalidDate(); + } + + var locale = this.localeData(); + var output = relativeTime$1(this, !withSuffix, locale); + + if (withSuffix) { + output = locale.pastFuture(+this, output); + } + + return locale.postformat(output); + } + + var abs$1 = Math.abs; + + function sign(x) { + return ((x > 0) - (x < 0)) || +x; + } + + function toISOString$1() { + // for ISO strings we do not use the normal bubbling rules: + // * milliseconds bubble up until they become hours + // * days do not bubble at all + // * months bubble up until they become years + // This is because there is no context-free conversion between hours and days + // (think of clock changes) + // and also not between days and months (28-31 days per month) + if (!this.isValid()) { + return this.localeData().invalidDate(); + } + + var seconds = abs$1(this._milliseconds) / 1000; + var days = abs$1(this._days); + var months = abs$1(this._months); + var minutes, hours, years; + + // 3600 seconds -> 60 minutes -> 1 hour + minutes = absFloor(seconds / 60); + hours = absFloor(minutes / 60); + seconds %= 60; + minutes %= 60; + + // 12 months -> 1 year + years = absFloor(months / 12); + months %= 12; + + + // inspired by https://github.com/dordille/moment-isoduration/blob/master/moment.isoduration.js + var Y = years; + var M = months; + var D = days; + var h = hours; + var m = minutes; + var s = seconds ? seconds.toFixed(3).replace(/\.?0+$/, '') : ''; + var total = this.asSeconds(); + + if (!total) { + // this is the same as C#'s (Noda) and python (isodate)... + // but not other JS (goog.date) + return 'P0D'; + } + + var totalSign = total < 0 ? '-' : ''; + var ymSign = sign(this._months) !== sign(total) ? '-' : ''; + var daysSign = sign(this._days) !== sign(total) ? '-' : ''; + var hmsSign = sign(this._milliseconds) !== sign(total) ? '-' : ''; + + return totalSign + 'P' + + (Y ? ymSign + Y + 'Y' : '') + + (M ? ymSign + M + 'M' : '') + + (D ? daysSign + D + 'D' : '') + + ((h || m || s) ? 'T' : '') + + (h ? hmsSign + h + 'H' : '') + + (m ? hmsSign + m + 'M' : '') + + (s ? hmsSign + s + 'S' : ''); + } + + var proto$2 = Duration.prototype; + + proto$2.isValid = isValid$1; + proto$2.abs = abs; + proto$2.add = add$1; + proto$2.subtract = subtract$1; + proto$2.as = as; + proto$2.asMilliseconds = asMilliseconds; + proto$2.asSeconds = asSeconds; + proto$2.asMinutes = asMinutes; + proto$2.asHours = asHours; + proto$2.asDays = asDays; + proto$2.asWeeks = asWeeks; + proto$2.asMonths = asMonths; + proto$2.asYears = asYears; + proto$2.valueOf = valueOf$1; + proto$2._bubble = bubble; + proto$2.clone = clone$1; + proto$2.get = get$2; + proto$2.milliseconds = milliseconds; + proto$2.seconds = seconds; + proto$2.minutes = minutes; + proto$2.hours = hours; + proto$2.days = days; + proto$2.weeks = weeks; + proto$2.months = months; + proto$2.years = years; + proto$2.humanize = humanize; + proto$2.toISOString = toISOString$1; + proto$2.toString = toISOString$1; + proto$2.toJSON = toISOString$1; + proto$2.locale = locale; + proto$2.localeData = localeData; + + // Deprecations + proto$2.toIsoString = deprecate('toIsoString() is deprecated. Please use toISOString() instead (notice the capitals)', toISOString$1); + proto$2.lang = lang; + + // Side effect imports + + // FORMATTING + + addFormatToken('X', 0, 0, 'unix'); + addFormatToken('x', 0, 0, 'valueOf'); + + // PARSING + + addRegexToken('x', matchSigned); + addRegexToken('X', matchTimestamp); + addParseToken('X', function (input, array, config) { + config._d = new Date(parseFloat(input, 10) * 1000); + }); + addParseToken('x', function (input, array, config) { + config._d = new Date(toInt(input)); + }); + + // Side effect imports + + + hooks.version = '2.20.1'; + + setHookCallback(createLocal); + + hooks.fn = proto; + hooks.min = min; + hooks.max = max; + hooks.now = now; + hooks.utc = createUTC; + hooks.unix = createUnix; + hooks.months = listMonths; + hooks.isDate = isDate; + hooks.locale = getSetGlobalLocale; + hooks.invalid = createInvalid; + hooks.duration = createDuration; + hooks.isMoment = isMoment; + hooks.weekdays = listWeekdays; + hooks.parseZone = createInZone; + hooks.localeData = getLocale; + hooks.isDuration = isDuration; + hooks.monthsShort = listMonthsShort; + hooks.weekdaysMin = listWeekdaysMin; + hooks.defineLocale = defineLocale; + hooks.updateLocale = updateLocale; + hooks.locales = listLocales; + hooks.weekdaysShort = listWeekdaysShort; + hooks.normalizeUnits = normalizeUnits; + hooks.relativeTimeRounding = getSetRelativeTimeRounding; + hooks.relativeTimeThreshold = getSetRelativeTimeThreshold; + hooks.calendarFormat = getCalendarFormat; + hooks.prototype = proto; + + // currently HTML5 input type only supports 24-hour formats + hooks.HTML5_FMT = { + DATETIME_LOCAL: 'YYYY-MM-DDTHH:mm', // + DATETIME_LOCAL_SECONDS: 'YYYY-MM-DDTHH:mm:ss', // + DATETIME_LOCAL_MS: 'YYYY-MM-DDTHH:mm:ss.SSS', // + DATE: 'YYYY-MM-DD', // + TIME: 'HH:mm', // + TIME_SECONDS: 'HH:mm:ss', // + TIME_MS: 'HH:mm:ss.SSS', // + WEEK: 'YYYY-[W]WW', // + MONTH: 'YYYY-MM' // + }; + + return hooks; + + }))); + + }, {}], 7: [function (require, module, exports) { + /** + * @namespace Chart + */ + var Chart = require(29)(); + + Chart.helpers = require(45); + + // @todo dispatch these helpers into appropriated helpers/helpers.* file and write unit tests! + require(27)(Chart); + + Chart.defaults = require(25); + Chart.Element = require(26); + Chart.elements = require(40); + Chart.Interaction = require(28); + Chart.layouts = require(30); + Chart.platform = require(48); + Chart.plugins = require(31); + Chart.Ticks = require(34); + + require(22)(Chart); + require(23)(Chart); + require(24)(Chart); + require(33)(Chart); + require(32)(Chart); + require(35)(Chart); + + require(55)(Chart); + require(53)(Chart); + require(54)(Chart); + require(56)(Chart); + require(57)(Chart); + require(58)(Chart); + + // Controllers must be loaded after elements + // See Chart.core.datasetController.dataElementType + require(15)(Chart); + require(16)(Chart); + require(17)(Chart); + require(18)(Chart); + require(19)(Chart); + require(20)(Chart); + require(21)(Chart); + + require(8)(Chart); + require(9)(Chart); + require(10)(Chart); + require(11)(Chart); + require(12)(Chart); + require(13)(Chart); + require(14)(Chart); + + // Loading built-it plugins + var plugins = require(49); + for (var k in plugins) { + if (plugins.hasOwnProperty(k)) { + Chart.plugins.register(plugins[k]); + } + } + + Chart.platform.initialize(); + + module.exports = Chart; + if (typeof window !== 'undefined') { + window.Chart = Chart; + } + + // DEPRECATIONS + + /** + * Provided for backward compatibility, not available anymore + * @namespace Chart.Legend + * @deprecated since version 2.1.5 + * @todo remove at version 3 + * @private + */ + Chart.Legend = plugins.legend._element; + + /** + * Provided for backward compatibility, not available anymore + * @namespace Chart.Title + * @deprecated since version 2.1.5 + * @todo remove at version 3 + * @private + */ + Chart.Title = plugins.title._element; + + /** + * Provided for backward compatibility, use Chart.plugins instead + * @namespace Chart.pluginService + * @deprecated since version 2.1.5 + * @todo remove at version 3 + * @private + */ + Chart.pluginService = Chart.plugins; + + /** + * Provided for backward compatibility, inheriting from Chart.PlugingBase has no + * effect, instead simply create/register plugins via plain JavaScript objects. + * @interface Chart.PluginBase + * @deprecated since version 2.5.0 + * @todo remove at version 3 + * @private + */ + Chart.PluginBase = Chart.Element.extend({}); + + /** + * Provided for backward compatibility, use Chart.helpers.canvas instead. + * @namespace Chart.canvasHelpers + * @deprecated since version 2.6.0 + * @todo remove at version 3 + * @private + */ + Chart.canvasHelpers = Chart.helpers.canvas; + + /** + * Provided for backward compatibility, use Chart.layouts instead. + * @namespace Chart.layoutService + * @deprecated since version 2.8.0 + * @todo remove at version 3 + * @private + */ + Chart.layoutService = Chart.layouts; + + }, { "10": 10, "11": 11, "12": 12, "13": 13, "14": 14, "15": 15, "16": 16, "17": 17, "18": 18, "19": 19, "20": 20, "21": 21, "22": 22, "23": 23, "24": 24, "25": 25, "26": 26, "27": 27, "28": 28, "29": 29, "30": 30, "31": 31, "32": 32, "33": 33, "34": 34, "35": 35, "40": 40, "45": 45, "48": 48, "49": 49, "53": 53, "54": 54, "55": 55, "56": 56, "57": 57, "58": 58, "8": 8, "9": 9 }], 8: [function (require, module, exports) { + 'use strict'; + + module.exports = function (Chart) { + + Chart.Bar = function (context, config) { + config.type = 'bar'; + + return new Chart(context, config); + }; + + }; + + }, {}], 9: [function (require, module, exports) { + 'use strict'; + + module.exports = function (Chart) { + + Chart.Bubble = function (context, config) { + config.type = 'bubble'; + return new Chart(context, config); + }; + + }; + + }, {}], 10: [function (require, module, exports) { + 'use strict'; + + module.exports = function (Chart) { + + Chart.Doughnut = function (context, config) { + config.type = 'doughnut'; + + return new Chart(context, config); + }; + + }; + + }, {}], 11: [function (require, module, exports) { + 'use strict'; + + module.exports = function (Chart) { + + Chart.Line = function (context, config) { + config.type = 'line'; + + return new Chart(context, config); + }; + + }; + + }, {}], 12: [function (require, module, exports) { + 'use strict'; + + module.exports = function (Chart) { + + Chart.PolarArea = function (context, config) { + config.type = 'polarArea'; + + return new Chart(context, config); + }; + + }; + + }, {}], 13: [function (require, module, exports) { + 'use strict'; + + module.exports = function (Chart) { + + Chart.Radar = function (context, config) { + config.type = 'radar'; + + return new Chart(context, config); + }; + + }; + + }, {}], 14: [function (require, module, exports) { + 'use strict'; + + module.exports = function (Chart) { + Chart.Scatter = function (context, config) { + config.type = 'scatter'; + return new Chart(context, config); + }; + }; + + }, {}], 15: [function (require, module, exports) { + 'use strict'; + + var defaults = require(25); + var elements = require(40); + var helpers = require(45); + + defaults._set('bar', { + hover: { + mode: 'label' + }, + + scales: { + xAxes: [{ + type: 'category', + + // Specific to Bar Controller + categoryPercentage: 0.8, + barPercentage: 0.9, + + // offset settings + offset: true, + + // grid line settings + gridLines: { + offsetGridLines: true + } + }], + + yAxes: [{ + type: 'linear' + }] + } + }); + + defaults._set('horizontalBar', { + hover: { + mode: 'index', + axis: 'y' + }, + + scales: { + xAxes: [{ + type: 'linear', + position: 'bottom' + }], + + yAxes: [{ + position: 'left', + type: 'category', + + // Specific to Horizontal Bar Controller + categoryPercentage: 0.8, + barPercentage: 0.9, + + // offset settings + offset: true, + + // grid line settings + gridLines: { + offsetGridLines: true + } + }] + }, + + elements: { + rectangle: { + borderSkipped: 'left' + } + }, + + tooltips: { + callbacks: { + title: function (item, data) { + // Pick first xLabel for now + var title = ''; + + if (item.length > 0) { + if (item[0].yLabel) { + title = item[0].yLabel; + } else if (data.labels.length > 0 && item[0].index < data.labels.length) { + title = data.labels[item[0].index]; + } + } + + return title; + }, + + label: function (item, data) { + var datasetLabel = data.datasets[item.datasetIndex].label || ''; + return datasetLabel + ': ' + item.xLabel; + } + }, + mode: 'index', + axis: 'y' + } + }); + + /** + * Computes the "optimal" sample size to maintain bars equally sized while preventing overlap. + * @private + */ + function computeMinSampleSize(scale, pixels) { + var min = scale.isHorizontal() ? scale.width : scale.height; + var ticks = scale.getTicks(); + var prev, curr, i, ilen; + + for (i = 1, ilen = pixels.length; i < ilen; ++i) { + min = Math.min(min, pixels[i] - pixels[i - 1]); + } + + for (i = 0, ilen = ticks.length; i < ilen; ++i) { + curr = scale.getPixelForTick(i); + min = i > 0 ? Math.min(min, 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 size, ratio; + + if (helpers.isNullOrUndef(thickness)) { + size = ruler.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 end extremity. + prev = curr - (next === null ? ruler.end - curr : next - curr); + } + + if (next === null) { + // last data: its size is also double based on the previous point. + next = curr + curr - prev; + } + + start = curr - ((curr - prev) / 2) * percent; + size = ((next - prev) / 2) * percent; + + return { + chunk: size / ruler.stackCount, + ratio: options.barPercentage, + start: start + }; + } + + module.exports = function (Chart) { + + Chart.controllers.bar = Chart.DatasetController.extend({ + + dataElementType: elements.Rectangle, + + initialize: function () { + var me = this; + var meta; + + Chart.DatasetController.prototype.initialize.apply(me, arguments); + + meta = me.getMeta(); + meta.stack = me.getDataset().stack; + meta.bar = true; + }, + + 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 chart = me.chart; + var meta = me.getMeta(); + var dataset = me.getDataset(); + var custom = rectangle.custom || {}; + var rectangleOptions = chart.options.elements.rectangle; + + rectangle._xScale = me.getScaleForId(meta.xAxisID); + rectangle._yScale = me.getScaleForId(meta.yAxisID); + rectangle._datasetIndex = me.index; + rectangle._index = index; + + rectangle._model = { + datasetLabel: dataset.label, + label: chart.data.labels[index], + borderSkipped: custom.borderSkipped ? custom.borderSkipped : rectangleOptions.borderSkipped, + backgroundColor: custom.backgroundColor ? custom.backgroundColor : helpers.valueAtIndexOrDefault(dataset.backgroundColor, index, rectangleOptions.backgroundColor), + borderColor: custom.borderColor ? custom.borderColor : helpers.valueAtIndexOrDefault(dataset.borderColor, index, rectangleOptions.borderColor), + borderWidth: custom.borderWidth ? custom.borderWidth : helpers.valueAtIndexOrDefault(dataset.borderWidth, index, rectangleOptions.borderWidth) + }; + + me.updateElementGeometry(rectangle, index, reset); + + rectangle.pivot(); + }, + + /** + * @private + */ + updateElementGeometry: function (rectangle, index, reset) { + var me = this; + var model = rectangle._model; + var vscale = me.getValueScale(); + var base = vscale.getBasePixel(); + var horizontal = vscale.isHorizontal(); + var ruler = me._ruler || me.getRuler(); + var vpixels = me.calculateBarValuePixels(me.index, index); + var ipixels = me.calculateBarIndexPixels(me.index, index, ruler); + + 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; + }, + + /** + * @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()); + }, + + /** + * Returns the stacks based on groups and bar visibility. + * @param {Number} [last] - The dataset index + * @returns {Array} The stack list + * @private + */ + _getStacks: function (last) { + var me = this; + var chart = me.chart; + var scale = me.getIndexScale(); + var stacked = scale.options.stacked; + var ilen = last === undefined ? chart.data.datasets.length : last + 1; + var stacks = []; + var i, meta; + + for (i = 0; i < ilen; ++i) { + meta = chart.getDatasetMeta(i); + if (meta.bar && chart.isDatasetVisible(i) && + (stacked === false || + (stacked === true && stacks.indexOf(meta.stack) === -1) || + (stacked === undefined && (meta.stack === undefined || stacks.indexOf(meta.stack) === -1)))) { + stacks.push(meta.stack); + } + } + + 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 stackCount = me.getStackCount(); + var datasetIndex = me.index; + var isHorizontal = scale.isHorizontal(); + var start = isHorizontal ? scale.left : scale.top; + var end = start + (isHorizontal ? scale.width : scale.height); + var pixels = []; + var i, ilen, min; + + for (i = 0, ilen = me.getMeta().data.length; i < ilen; ++i) { + pixels.push(scale.getPixelForValue(null, i, datasetIndex)); + } + + min = helpers.isNullOrUndef(scale.options.barThickness) + ? computeMinSampleSize(scale, pixels) + : -1; + + return { + min: min, + pixels: pixels, + start: start, + end: end, + stackCount: stackCount, + scale: scale + }; + }, + + /** + * Note: pixel values are not clamped to the scale area. + * @private + */ + calculateBarValuePixels: function (datasetIndex, index) { + var me = this; + var chart = me.chart; + var meta = me.getMeta(); + var scale = me.getValueScale(); + var datasets = chart.data.datasets; + var value = scale.getRightValue(datasets[datasetIndex].data[index]); + var stacked = scale.options.stacked; + var stack = meta.stack; + var start = 0; + var i, imeta, ivalue, base, head, size; + + if (stacked || (stacked === undefined && stack !== undefined)) { + for (i = 0; i < datasetIndex; ++i) { + imeta = chart.getDatasetMeta(i); + + if (imeta.bar && + imeta.stack === stack && + imeta.controller.getValueScaleId() === scale.id && + chart.isDatasetVisible(i)) { + + ivalue = scale.getRightValue(datasets[i].data[index]); + if ((value < 0 && ivalue < 0) || (value >= 0 && ivalue > 0)) { + start += ivalue; + } + } + } + } + + base = scale.getPixelForValue(start); + head = scale.getPixelForValue(start + value); + size = (head - base) / 2; + + return { + size: size, + base: base, + head: head, + center: head + size / 2 + }; + }, + + /** + * @private + */ + calculateBarIndexPixels: function (datasetIndex, index, ruler) { + var me = this; + var options = ruler.scale.options; + var range = options.barThickness === 'flex' + ? computeFlexCategoryTraits(index, ruler, options) + : computeFitCategoryTraits(index, ruler, options); + + var stackIndex = me.getStackIndex(datasetIndex, me.getMeta().stack); + var center = range.start + (range.chunk * stackIndex) + (range.chunk / 2); + var size = Math.min( + helpers.valueOrDefault(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.canvas.clipArea(chart.ctx, chart.chartArea); + + for (; i < ilen; ++i) { + if (!isNaN(scale.getRightValue(dataset.data[i]))) { + rects[i].draw(); + } + } + + helpers.canvas.unclipArea(chart.ctx); + }, + + setHoverStyle: function (rectangle) { + var dataset = this.chart.data.datasets[rectangle._datasetIndex]; + var index = rectangle._index; + var custom = rectangle.custom || {}; + var model = rectangle._model; + + model.backgroundColor = custom.hoverBackgroundColor ? custom.hoverBackgroundColor : helpers.valueAtIndexOrDefault(dataset.hoverBackgroundColor, index, helpers.getHoverColor(model.backgroundColor)); + model.borderColor = custom.hoverBorderColor ? custom.hoverBorderColor : helpers.valueAtIndexOrDefault(dataset.hoverBorderColor, index, helpers.getHoverColor(model.borderColor)); + model.borderWidth = custom.hoverBorderWidth ? custom.hoverBorderWidth : helpers.valueAtIndexOrDefault(dataset.hoverBorderWidth, index, model.borderWidth); + }, + + removeHoverStyle: function (rectangle) { + var dataset = this.chart.data.datasets[rectangle._datasetIndex]; + var index = rectangle._index; + var custom = rectangle.custom || {}; + var model = rectangle._model; + var rectangleElementOptions = this.chart.options.elements.rectangle; + + model.backgroundColor = custom.backgroundColor ? custom.backgroundColor : helpers.valueAtIndexOrDefault(dataset.backgroundColor, index, rectangleElementOptions.backgroundColor); + model.borderColor = custom.borderColor ? custom.borderColor : helpers.valueAtIndexOrDefault(dataset.borderColor, index, rectangleElementOptions.borderColor); + model.borderWidth = custom.borderWidth ? custom.borderWidth : helpers.valueAtIndexOrDefault(dataset.borderWidth, index, rectangleElementOptions.borderWidth); + } + }); + + Chart.controllers.horizontalBar = Chart.controllers.bar.extend({ + /** + * @private + */ + getValueScaleId: function () { + return this.getMeta().xAxisID; + }, + + /** + * @private + */ + getIndexScaleId: function () { + return this.getMeta().yAxisID; + } + }); + }; + + }, { "25": 25, "40": 40, "45": 45 }], 16: [function (require, module, exports) { + 'use strict'; + + var defaults = require(25); + var elements = require(40); + var helpers = require(45); + + 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 + ')'; + } + } + } + }); + + + module.exports = function (Chart) { + + Chart.controllers.bubble = Chart.DatasetController.extend({ + /** + * @protected + */ + dataElementType: elements.Point, + + /** + * @protected + */ + update: function (reset) { + var me = this; + var meta = me.getMeta(); + var points = meta.data; + + // Update Points + helpers.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._resolveElementOptions(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, + 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; + + model.backgroundColor = helpers.valueOrDefault(options.hoverBackgroundColor, helpers.getHoverColor(options.backgroundColor)); + model.borderColor = helpers.valueOrDefault(options.hoverBorderColor, helpers.getHoverColor(options.borderColor)); + model.borderWidth = helpers.valueOrDefault(options.hoverBorderWidth, options.borderWidth); + model.radius = options.radius + options.hoverRadius; + }, + + /** + * @protected + */ + removeHoverStyle: function (point) { + var model = point._model; + var options = point._options; + + model.backgroundColor = options.backgroundColor; + model.borderColor = options.borderColor; + model.borderWidth = options.borderWidth; + model.radius = options.radius; + }, + + /** + * @private + */ + _resolveElementOptions: function (point, index) { + var me = this; + var chart = me.chart; + var datasets = chart.data.datasets; + var dataset = datasets[me.index]; + var custom = point.custom || {}; + var options = chart.options.elements.point; + var resolve = helpers.options.resolve; + var data = dataset.data[index]; + var values = {}; + var i, ilen, key; + + // Scriptable options + var context = { + chart: chart, + dataIndex: index, + dataset: dataset, + datasetIndex: me.index + }; + + var keys = [ + 'backgroundColor', + 'borderColor', + 'borderWidth', + 'hoverBackgroundColor', + 'hoverBorderColor', + 'hoverBorderWidth', + 'hoverRadius', + 'hitRadius', + 'pointStyle' + ]; + + for (i = 0, ilen = keys.length; i < ilen; ++i) { + key = keys[i]; + values[key] = resolve([ + custom[key], + dataset[key], + options[key] + ], context, index); + } + + // Custom radius resolution + values.radius = resolve([ + custom.radius, + data ? data.r : undefined, + dataset.radius, + options.radius + ], context, index); + + return values; + } + }); + }; + + }, { "25": 25, "40": 40, "45": 45 }], 17: [function (require, module, exports) { + 'use strict'; + + var defaults = require(25); + var elements = require(40); + var helpers = require(45); + + 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 text = []; + text.push('
      '); + + var data = chart.data; + var datasets = data.datasets; + var labels = data.labels; + + if (datasets.length) { + for (var i = 0; i < datasets[0].data.length; ++i) { + text.push('
    • '); + if (labels[i]) { + text.push(labels[i]); + } + text.push('
    • '); + } + } + + text.push('
    '); + return text.join(''); + }, + legend: { + labels: { + generateLabels: function (chart) { + var data = chart.data; + if (data.labels.length && data.datasets.length) { + return data.labels.map(function (label, i) { + var meta = chart.getDatasetMeta(0); + var ds = data.datasets[0]; + var arc = meta.data[i]; + var custom = arc && arc.custom || {}; + var valueAtIndexOrDefault = helpers.valueAtIndexOrDefault; + var arcOpts = chart.options.elements.arc; + var fill = custom.backgroundColor ? custom.backgroundColor : valueAtIndexOrDefault(ds.backgroundColor, i, arcOpts.backgroundColor); + var stroke = custom.borderColor ? custom.borderColor : valueAtIndexOrDefault(ds.borderColor, i, arcOpts.borderColor); + var bw = custom.borderWidth ? custom.borderWidth : valueAtIndexOrDefault(ds.borderWidth, i, arcOpts.borderWidth); + + return { + text: label, + fillStyle: fill, + strokeStyle: stroke, + lineWidth: bw, + hidden: isNaN(ds.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: Math.PI * -0.5, + + // The total circumference of the chart. + circumference: Math.PI * 2.0, + + // 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.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; + } + } + } + }); + + defaults._set('pie', helpers.clone(defaults.doughnut)); + defaults._set('pie', { + cutoutPercentage: 0 + }); + + module.exports = function (Chart) { + + Chart.controllers.doughnut = Chart.controllers.pie = Chart.DatasetController.extend({ + + dataElementType: elements.Arc, + + linkScales: helpers.noop, + + // 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 arcOpts = opts.elements.arc; + var availableWidth = chartArea.right - chartArea.left - arcOpts.borderWidth; + var availableHeight = chartArea.bottom - chartArea.top - arcOpts.borderWidth; + var minSize = Math.min(availableWidth, availableHeight); + var offset = { x: 0, y: 0 }; + var meta = me.getMeta(); + var cutoutPercentage = opts.cutoutPercentage; + var circumference = opts.circumference; + + // If the chart's circumference isn't a full circle, calculate minSize as a ratio of the width/height of the arc + if (circumference < Math.PI * 2.0) { + var startAngle = opts.rotation % (Math.PI * 2.0); + startAngle += Math.PI * 2.0 * (startAngle >= Math.PI ? -1 : startAngle < -Math.PI ? 1 : 0); + var endAngle = startAngle + circumference; + var start = { x: Math.cos(startAngle), y: Math.sin(startAngle) }; + var end = { x: Math.cos(endAngle), y: Math.sin(endAngle) }; + var contains0 = (startAngle <= 0 && endAngle >= 0) || (startAngle <= Math.PI * 2.0 && Math.PI * 2.0 <= endAngle); + var contains90 = (startAngle <= Math.PI * 0.5 && Math.PI * 0.5 <= endAngle) || (startAngle <= Math.PI * 2.5 && Math.PI * 2.5 <= endAngle); + var contains180 = (startAngle <= -Math.PI && -Math.PI <= endAngle) || (startAngle <= Math.PI && Math.PI <= endAngle); + var contains270 = (startAngle <= -Math.PI * 0.5 && -Math.PI * 0.5 <= endAngle) || (startAngle <= Math.PI * 1.5 && Math.PI * 1.5 <= endAngle); + var cutout = cutoutPercentage / 100.0; + var min = { x: contains180 ? -1 : Math.min(start.x * (start.x < 0 ? 1 : cutout), end.x * (end.x < 0 ? 1 : cutout)), y: contains270 ? -1 : Math.min(start.y * (start.y < 0 ? 1 : cutout), end.y * (end.y < 0 ? 1 : cutout)) }; + var max = { x: contains0 ? 1 : Math.max(start.x * (start.x > 0 ? 1 : cutout), end.x * (end.x > 0 ? 1 : cutout)), y: contains90 ? 1 : Math.max(start.y * (start.y > 0 ? 1 : cutout), end.y * (end.y > 0 ? 1 : cutout)) }; + var size = { width: (max.x - min.x) * 0.5, height: (max.y - min.y) * 0.5 }; + minSize = Math.min(availableWidth / size.width, availableHeight / size.height); + offset = { x: (max.x + min.x) * -0.5, y: (max.y + min.y) * -0.5 }; + } + + chart.borderWidth = me.getMaxBorderWidth(meta.data); + chart.outerRadius = Math.max((minSize - chart.borderWidth) / 2, 0); + chart.innerRadius = Math.max(cutoutPercentage ? (chart.outerRadius / 100) * (cutoutPercentage) : 0, 0); + chart.radiusLength = (chart.outerRadius - chart.innerRadius) / chart.getVisibleDatasetCount(); + chart.offsetX = offset.x * chart.outerRadius; + chart.offsetY = offset.y * chart.outerRadius; + + meta.total = me.calculateTotal(); + + me.outerRadius = chart.outerRadius - (chart.radiusLength * me.getRingIndex(me.index)); + me.innerRadius = Math.max(me.outerRadius - chart.radiusLength, 0); + + helpers.each(meta.data, function (arc, index) { + me.updateElement(arc, index, 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 / (2.0 * Math.PI)); + var innerRadius = reset && animationOpts.animateScale ? 0 : me.innerRadius; + var outerRadius = reset && animationOpts.animateScale ? 0 : me.outerRadius; + var valueAtIndexOrDefault = helpers.valueAtIndexOrDefault; + + helpers.extend(arc, { + // Utility + _datasetIndex: me.index, + _index: index, + + // Desired view properties + _model: { + x: centerX + chart.offsetX, + y: centerY + chart.offsetY, + startAngle: startAngle, + endAngle: endAngle, + circumference: circumference, + outerRadius: outerRadius, + innerRadius: innerRadius, + label: valueAtIndexOrDefault(dataset.label, index, chart.data.labels[index]) + } + }); + + var model = arc._model; + // Resets the visual styles + this.removeHoverStyle(arc); + + // 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(); + }, + + removeHoverStyle: function (arc) { + Chart.DatasetController.prototype.removeHoverStyle.call(this, arc, this.chart.options.elements.arc); + }, + + calculateTotal: function () { + var dataset = this.getDataset(); + var meta = this.getMeta(); + var total = 0; + var value; + + helpers.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 (Math.PI * 2.0) * (Math.abs(value) / total); + } + return 0; + }, + + // gets the max border or hover width to properly scale pie charts + getMaxBorderWidth: function (arcs) { + var max = 0; + var index = this.index; + var length = arcs.length; + var borderWidth; + var hoverWidth; + + for (var i = 0; i < length; i++) { + borderWidth = arcs[i]._model ? arcs[i]._model.borderWidth : 0; + hoverWidth = arcs[i]._chart ? arcs[i]._chart.config.data.datasets[index].hoverBorderWidth : 0; + + max = borderWidth > max ? borderWidth : max; + max = hoverWidth > max ? hoverWidth : max; + } + return max; + } + }); + }; + + }, { "25": 25, "40": 40, "45": 45 }], 18: [function (require, module, exports) { + 'use strict'; + + var defaults = require(25); + var elements = require(40); + var helpers = require(45); + + 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' + }] + } + }); + + module.exports = function (Chart) { + + function lineEnabled(dataset, options) { + return helpers.valueOrDefault(dataset.showLine, options.showLines); + } + + Chart.controllers.line = Chart.DatasetController.extend({ + + datasetElementType: elements.Line, + + dataElementType: elements.Point, + + update: function (reset) { + var me = this; + var meta = me.getMeta(); + var line = meta.dataset; + var points = meta.data || []; + var options = me.chart.options; + var lineElementOptions = options.elements.line; + var scale = me.getScaleForId(meta.yAxisID); + var i, ilen, custom; + var dataset = me.getDataset(); + var showLine = lineEnabled(dataset, options); + + // Update Line + if (showLine) { + custom = line.custom || {}; + + // Compatibility: If the properties are defined with only the old name, use those values + if ((dataset.tension !== undefined) && (dataset.lineTension === undefined)) { + dataset.lineTension = dataset.tension; + } + + // Utility + line._scale = scale; + line._datasetIndex = me.index; + // Data + line._children = points; + // Model + line._model = { + // Appearance + // 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 + spanGaps: dataset.spanGaps ? dataset.spanGaps : options.spanGaps, + tension: custom.tension ? custom.tension : helpers.valueOrDefault(dataset.lineTension, lineElementOptions.tension), + backgroundColor: custom.backgroundColor ? custom.backgroundColor : (dataset.backgroundColor || lineElementOptions.backgroundColor), + borderWidth: custom.borderWidth ? custom.borderWidth : (dataset.borderWidth || lineElementOptions.borderWidth), + borderColor: custom.borderColor ? custom.borderColor : (dataset.borderColor || lineElementOptions.borderColor), + borderCapStyle: custom.borderCapStyle ? custom.borderCapStyle : (dataset.borderCapStyle || lineElementOptions.borderCapStyle), + borderDash: custom.borderDash ? custom.borderDash : (dataset.borderDash || lineElementOptions.borderDash), + borderDashOffset: custom.borderDashOffset ? custom.borderDashOffset : (dataset.borderDashOffset || lineElementOptions.borderDashOffset), + borderJoinStyle: custom.borderJoinStyle ? custom.borderJoinStyle : (dataset.borderJoinStyle || lineElementOptions.borderJoinStyle), + fill: custom.fill ? custom.fill : (dataset.fill !== undefined ? dataset.fill : lineElementOptions.fill), + steppedLine: custom.steppedLine ? custom.steppedLine : helpers.valueOrDefault(dataset.steppedLine, lineElementOptions.stepped), + cubicInterpolationMode: custom.cubicInterpolationMode ? custom.cubicInterpolationMode : helpers.valueOrDefault(dataset.cubicInterpolationMode, lineElementOptions.cubicInterpolationMode), + }; + + 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(); + } + }, + + getPointBackgroundColor: function (point, index) { + var backgroundColor = this.chart.options.elements.point.backgroundColor; + var dataset = this.getDataset(); + var custom = point.custom || {}; + + if (custom.backgroundColor) { + backgroundColor = custom.backgroundColor; + } else if (dataset.pointBackgroundColor) { + backgroundColor = helpers.valueAtIndexOrDefault(dataset.pointBackgroundColor, index, backgroundColor); + } else if (dataset.backgroundColor) { + backgroundColor = dataset.backgroundColor; + } + + return backgroundColor; + }, + + getPointBorderColor: function (point, index) { + var borderColor = this.chart.options.elements.point.borderColor; + var dataset = this.getDataset(); + var custom = point.custom || {}; + + if (custom.borderColor) { + borderColor = custom.borderColor; + } else if (dataset.pointBorderColor) { + borderColor = helpers.valueAtIndexOrDefault(dataset.pointBorderColor, index, borderColor); + } else if (dataset.borderColor) { + borderColor = dataset.borderColor; + } + + return borderColor; + }, + + getPointBorderWidth: function (point, index) { + var borderWidth = this.chart.options.elements.point.borderWidth; + var dataset = this.getDataset(); + var custom = point.custom || {}; + + if (!isNaN(custom.borderWidth)) { + borderWidth = custom.borderWidth; + } else if (!isNaN(dataset.pointBorderWidth) || helpers.isArray(dataset.pointBorderWidth)) { + borderWidth = helpers.valueAtIndexOrDefault(dataset.pointBorderWidth, index, borderWidth); + } else if (!isNaN(dataset.borderWidth)) { + borderWidth = dataset.borderWidth; + } + + return borderWidth; + }, + + 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 yScale = me.getScaleForId(meta.yAxisID); + var xScale = me.getScaleForId(meta.xAxisID); + var pointOptions = me.chart.options.elements.point; + var x, y; + + // Compatibility: If the properties are defined with only the old name, use those values + if ((dataset.radius !== undefined) && (dataset.pointRadius === undefined)) { + dataset.pointRadius = dataset.radius; + } + if ((dataset.hitRadius !== undefined) && (dataset.pointHitRadius === undefined)) { + dataset.pointHitRadius = dataset.hitRadius; + } + + 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._datasetIndex = datasetIndex; + point._index = index; + + // Desired view properties + point._model = { + x: x, + y: y, + skip: custom.skip || isNaN(x) || isNaN(y), + // Appearance + radius: custom.radius || helpers.valueAtIndexOrDefault(dataset.pointRadius, index, pointOptions.radius), + pointStyle: custom.pointStyle || helpers.valueAtIndexOrDefault(dataset.pointStyle, index, pointOptions.pointStyle), + backgroundColor: me.getPointBackgroundColor(point, index), + borderColor: me.getPointBorderColor(point, index), + borderWidth: me.getPointBorderWidth(point, index), + tension: meta.dataset._model ? meta.dataset._model.tension : 0, + steppedLine: meta.dataset._model ? meta.dataset._model.steppedLine : false, + // Tooltip + hitRadius: custom.hitRadius || helpers.valueAtIndexOrDefault(dataset.pointHitRadius, index, pointOptions.hitRadius) + }; + }, + + calculatePointY: function (value, index, datasetIndex) { + var me = this; + var chart = me.chart; + var meta = me.getMeta(); + var yScale = me.getScaleForId(meta.yAxisID); + var sumPos = 0; + var sumNeg = 0; + var i, ds, dsMeta; + + if (yScale.options.stacked) { + for (i = 0; i < datasetIndex; i++) { + ds = chart.data.datasets[i]; + dsMeta = chart.getDatasetMeta(i); + if (dsMeta.type === 'line' && dsMeta.yAxisID === yScale.id && chart.isDatasetVisible(i)) { + var stackedRightValue = Number(yScale.getRightValue(ds.data[index])); + if (stackedRightValue < 0) { + sumNeg += stackedRightValue || 0; + } else { + sumPos += stackedRightValue || 0; + } + } + } + + var rightValue = Number(yScale.getRightValue(value)); + if (rightValue < 0) { + return yScale.getPixelForValue(sumNeg + rightValue); + } + return yScale.getPixelForValue(sumPos + rightValue); + } + + return yScale.getPixelForValue(value); + }, + + updateBezierControlPoints: function () { + var me = this; + var meta = me.getMeta(); + var area = me.chart.chartArea; + var points = (meta.data || []); + var i, ilen, point, 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); + } + + if (meta.dataset._model.cubicInterpolationMode === 'monotone') { + helpers.splineCurveMonotone(points); + } else { + for (i = 0, ilen = points.length; i < ilen; ++i) { + point = points[i]; + model = point._model; + controlPoints = helpers.splineCurve( + helpers.previousItem(points, i)._model, + model, + helpers.nextItem(points, i)._model, + meta.dataset._model.tension + ); + model.controlPointPreviousX = controlPoints.previous.x; + model.controlPointPreviousY = controlPoints.previous.y; + model.controlPointNextX = controlPoints.next.x; + model.controlPointNextY = controlPoints.next.y; + } + } + + if (me.chart.options.elements.line.capBezierPoints) { + for (i = 0, ilen = points.length; i < ilen; ++i) { + model = points[i]._model; + model.controlPointPreviousX = capControlPoint(model.controlPointPreviousX, area.left, area.right); + model.controlPointPreviousY = capControlPoint(model.controlPointPreviousY, area.top, area.bottom); + 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 ilen = points.length; + var i = 0; + + helpers.canvas.clipArea(chart.ctx, area); + + if (lineEnabled(me.getDataset(), chart.options)) { + meta.dataset.draw(); + } + + helpers.canvas.unclipArea(chart.ctx); + + // Draw the points + for (; i < ilen; ++i) { + points[i].draw(area); + } + }, + + setHoverStyle: function (point) { + // Point + var dataset = this.chart.data.datasets[point._datasetIndex]; + var index = point._index; + var custom = point.custom || {}; + var model = point._model; + + model.radius = custom.hoverRadius || helpers.valueAtIndexOrDefault(dataset.pointHoverRadius, index, this.chart.options.elements.point.hoverRadius); + model.backgroundColor = custom.hoverBackgroundColor || helpers.valueAtIndexOrDefault(dataset.pointHoverBackgroundColor, index, helpers.getHoverColor(model.backgroundColor)); + model.borderColor = custom.hoverBorderColor || helpers.valueAtIndexOrDefault(dataset.pointHoverBorderColor, index, helpers.getHoverColor(model.borderColor)); + model.borderWidth = custom.hoverBorderWidth || helpers.valueAtIndexOrDefault(dataset.pointHoverBorderWidth, index, model.borderWidth); + }, + + removeHoverStyle: function (point) { + var me = this; + var dataset = me.chart.data.datasets[point._datasetIndex]; + var index = point._index; + var custom = point.custom || {}; + var model = point._model; + + // Compatibility: If the properties are defined with only the old name, use those values + if ((dataset.radius !== undefined) && (dataset.pointRadius === undefined)) { + dataset.pointRadius = dataset.radius; + } + + model.radius = custom.radius || helpers.valueAtIndexOrDefault(dataset.pointRadius, index, me.chart.options.elements.point.radius); + model.backgroundColor = me.getPointBackgroundColor(point, index); + model.borderColor = me.getPointBorderColor(point, index); + model.borderWidth = me.getPointBorderWidth(point, index); + } + }); + }; + + }, { "25": 25, "40": 40, "45": 45 }], 19: [function (require, module, exports) { + 'use strict'; + + var defaults = require(25); + var elements = require(40); + var helpers = require(45); + + 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 text = []; + text.push('
      '); + + var data = chart.data; + var datasets = data.datasets; + var labels = data.labels; + + if (datasets.length) { + for (var i = 0; i < datasets[0].data.length; ++i) { + text.push('
    • '); + if (labels[i]) { + text.push(labels[i]); + } + text.push('
    • '); + } + } + + text.push('
    '); + return text.join(''); + }, + legend: { + labels: { + generateLabels: function (chart) { + var data = chart.data; + if (data.labels.length && data.datasets.length) { + return data.labels.map(function (label, i) { + var meta = chart.getDatasetMeta(0); + var ds = data.datasets[0]; + var arc = meta.data[i]; + var custom = arc.custom || {}; + var valueAtIndexOrDefault = helpers.valueAtIndexOrDefault; + var arcOpts = chart.options.elements.arc; + var fill = custom.backgroundColor ? custom.backgroundColor : valueAtIndexOrDefault(ds.backgroundColor, i, arcOpts.backgroundColor); + var stroke = custom.borderColor ? custom.borderColor : valueAtIndexOrDefault(ds.borderColor, i, arcOpts.borderColor); + var bw = custom.borderWidth ? custom.borderWidth : valueAtIndexOrDefault(ds.borderWidth, i, arcOpts.borderWidth); + + return { + text: label, + fillStyle: fill, + strokeStyle: stroke, + lineWidth: bw, + hidden: isNaN(ds.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; + } + } + } + }); + + module.exports = function (Chart) { + + Chart.controllers.polarArea = Chart.DatasetController.extend({ + + dataElementType: elements.Arc, + + linkScales: helpers.noop, + + update: function (reset) { + var me = this; + var chart = me.chart; + var chartArea = chart.chartArea; + var meta = me.getMeta(); + var opts = chart.options; + var arcOpts = opts.elements.arc; + var minSize = Math.min(chartArea.right - chartArea.left, chartArea.bottom - chartArea.top); + chart.outerRadius = Math.max((minSize - arcOpts.borderWidth / 2) / 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; + + meta.count = me.countVisibleElements(); + + helpers.each(meta.data, function (arc, index) { + me.updateElement(arc, index, reset); + }); + }, + + 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 circumference = me.calculateCircumference(dataset.data[index]); + var centerX = scale.xCenter; + var centerY = scale.yCenter; + + // If there is NaN data before us, we need to calculate the starting angle correctly. + // We could be way more efficient here, but its unlikely that the polar area chart will have a lot of data + var visibleCount = 0; + var meta = me.getMeta(); + for (var i = 0; i < index; ++i) { + if (!isNaN(dataset.data[i]) && !meta.data[i].hidden) { + ++visibleCount; + } + } + + // var negHalfPI = -0.5 * Math.PI; + var datasetStartAngle = opts.startAngle; + var distance = arc.hidden ? 0 : scale.getDistanceFromCenterForValue(dataset.data[index]); + var startAngle = datasetStartAngle + (circumference * visibleCount); + var endAngle = startAngle + (arc.hidden ? 0 : circumference); + + var resetRadius = animationOpts.animateScale ? 0 : scale.getDistanceFromCenterForValue(dataset.data[index]); + + helpers.extend(arc, { + // Utility + _datasetIndex: me.index, + _index: index, + _scale: scale, + + // Desired view properties + _model: { + x: centerX, + y: centerY, + innerRadius: 0, + outerRadius: reset ? resetRadius : distance, + startAngle: reset && animationOpts.animateRotate ? datasetStartAngle : startAngle, + endAngle: reset && animationOpts.animateRotate ? datasetStartAngle : endAngle, + label: helpers.valueAtIndexOrDefault(labels, index, labels[index]) + } + }); + + // Apply border and fill style + me.removeHoverStyle(arc); + + arc.pivot(); + }, + + removeHoverStyle: function (arc) { + Chart.DatasetController.prototype.removeHoverStyle.call(this, arc, this.chart.options.elements.arc); + }, + + countVisibleElements: function () { + var dataset = this.getDataset(); + var meta = this.getMeta(); + var count = 0; + + helpers.each(meta.data, function (element, index) { + if (!isNaN(dataset.data[index]) && !element.hidden) { + count++; + } + }); + + return count; + }, + + calculateCircumference: function (value) { + var count = this.getMeta().count; + if (count > 0 && !isNaN(value)) { + return (2 * Math.PI) / count; + } + return 0; + } + }); + }; + + }, { "25": 25, "40": 40, "45": 45 }], 20: [function (require, module, exports) { + 'use strict'; + + var defaults = require(25); + var elements = require(40); + var helpers = require(45); + + defaults._set('radar', { + scale: { + type: 'radialLinear' + }, + elements: { + line: { + tension: 0 // no bezier in radar + } + } + }); + + module.exports = function (Chart) { + + Chart.controllers.radar = Chart.DatasetController.extend({ + + datasetElementType: elements.Line, + + dataElementType: elements.Point, + + linkScales: helpers.noop, + + update: function (reset) { + var me = this; + var meta = me.getMeta(); + var line = meta.dataset; + var points = meta.data; + var custom = line.custom || {}; + var dataset = me.getDataset(); + var lineElementOptions = me.chart.options.elements.line; + var scale = me.chart.scale; + + // Compatibility: If the properties are defined with only the old name, use those values + if ((dataset.tension !== undefined) && (dataset.lineTension === undefined)) { + dataset.lineTension = dataset.tension; + } + + helpers.extend(meta.dataset, { + // Utility + _datasetIndex: me.index, + _scale: scale, + // Data + _children: points, + _loop: true, + // Model + _model: { + // Appearance + tension: custom.tension ? custom.tension : helpers.valueOrDefault(dataset.lineTension, lineElementOptions.tension), + backgroundColor: custom.backgroundColor ? custom.backgroundColor : (dataset.backgroundColor || lineElementOptions.backgroundColor), + borderWidth: custom.borderWidth ? custom.borderWidth : (dataset.borderWidth || lineElementOptions.borderWidth), + borderColor: custom.borderColor ? custom.borderColor : (dataset.borderColor || lineElementOptions.borderColor), + fill: custom.fill ? custom.fill : (dataset.fill !== undefined ? dataset.fill : lineElementOptions.fill), + borderCapStyle: custom.borderCapStyle ? custom.borderCapStyle : (dataset.borderCapStyle || lineElementOptions.borderCapStyle), + borderDash: custom.borderDash ? custom.borderDash : (dataset.borderDash || lineElementOptions.borderDash), + borderDashOffset: custom.borderDashOffset ? custom.borderDashOffset : (dataset.borderDashOffset || lineElementOptions.borderDashOffset), + borderJoinStyle: custom.borderJoinStyle ? custom.borderJoinStyle : (dataset.borderJoinStyle || lineElementOptions.borderJoinStyle), + } + }); + + meta.dataset.pivot(); + + // Update Points + helpers.each(points, function (point, index) { + me.updateElement(point, index, reset); + }, me); + + // Update bezier control points + me.updateBezierControlPoints(); + }, + updateElement: function (point, index, reset) { + var me = this; + var custom = point.custom || {}; + var dataset = me.getDataset(); + var scale = me.chart.scale; + var pointElementOptions = me.chart.options.elements.point; + var pointPosition = scale.getPointPositionForValue(index, dataset.data[index]); + + // Compatibility: If the properties are defined with only the old name, use those values + if ((dataset.radius !== undefined) && (dataset.pointRadius === undefined)) { + dataset.pointRadius = dataset.radius; + } + if ((dataset.hitRadius !== undefined) && (dataset.pointHitRadius === undefined)) { + dataset.pointHitRadius = dataset.hitRadius; + } + + helpers.extend(point, { + // Utility + _datasetIndex: me.index, + _index: index, + _scale: scale, + + // Desired view properties + _model: { + x: reset ? scale.xCenter : pointPosition.x, // value not used in dataset scale, but we want a consistent API between scales + y: reset ? scale.yCenter : pointPosition.y, + + // Appearance + tension: custom.tension ? custom.tension : helpers.valueOrDefault(dataset.lineTension, me.chart.options.elements.line.tension), + radius: custom.radius ? custom.radius : helpers.valueAtIndexOrDefault(dataset.pointRadius, index, pointElementOptions.radius), + backgroundColor: custom.backgroundColor ? custom.backgroundColor : helpers.valueAtIndexOrDefault(dataset.pointBackgroundColor, index, pointElementOptions.backgroundColor), + borderColor: custom.borderColor ? custom.borderColor : helpers.valueAtIndexOrDefault(dataset.pointBorderColor, index, pointElementOptions.borderColor), + borderWidth: custom.borderWidth ? custom.borderWidth : helpers.valueAtIndexOrDefault(dataset.pointBorderWidth, index, pointElementOptions.borderWidth), + pointStyle: custom.pointStyle ? custom.pointStyle : helpers.valueAtIndexOrDefault(dataset.pointStyle, index, pointElementOptions.pointStyle), + + // Tooltip + hitRadius: custom.hitRadius ? custom.hitRadius : helpers.valueAtIndexOrDefault(dataset.pointHitRadius, index, pointElementOptions.hitRadius) + } + }); + + point._model.skip = custom.skip ? custom.skip : (isNaN(point._model.x) || isNaN(point._model.y)); + }, + updateBezierControlPoints: function () { + var chartArea = this.chart.chartArea; + var meta = this.getMeta(); + + helpers.each(meta.data, function (point, index) { + var model = point._model; + var controlPoints = helpers.splineCurve( + helpers.previousItem(meta.data, index, true)._model, + model, + helpers.nextItem(meta.data, index, true)._model, + model.tension + ); + + // Prevent the bezier going outside of the bounds of the graph + model.controlPointPreviousX = Math.max(Math.min(controlPoints.previous.x, chartArea.right), chartArea.left); + model.controlPointPreviousY = Math.max(Math.min(controlPoints.previous.y, chartArea.bottom), chartArea.top); + + model.controlPointNextX = Math.max(Math.min(controlPoints.next.x, chartArea.right), chartArea.left); + model.controlPointNextY = Math.max(Math.min(controlPoints.next.y, chartArea.bottom), chartArea.top); + + // Now pivot the point for animation + point.pivot(); + }); + }, + + setHoverStyle: function (point) { + // Point + var dataset = this.chart.data.datasets[point._datasetIndex]; + var custom = point.custom || {}; + var index = point._index; + var model = point._model; + + model.radius = custom.hoverRadius ? custom.hoverRadius : helpers.valueAtIndexOrDefault(dataset.pointHoverRadius, index, this.chart.options.elements.point.hoverRadius); + model.backgroundColor = custom.hoverBackgroundColor ? custom.hoverBackgroundColor : helpers.valueAtIndexOrDefault(dataset.pointHoverBackgroundColor, index, helpers.getHoverColor(model.backgroundColor)); + model.borderColor = custom.hoverBorderColor ? custom.hoverBorderColor : helpers.valueAtIndexOrDefault(dataset.pointHoverBorderColor, index, helpers.getHoverColor(model.borderColor)); + model.borderWidth = custom.hoverBorderWidth ? custom.hoverBorderWidth : helpers.valueAtIndexOrDefault(dataset.pointHoverBorderWidth, index, model.borderWidth); + }, + + removeHoverStyle: function (point) { + var dataset = this.chart.data.datasets[point._datasetIndex]; + var custom = point.custom || {}; + var index = point._index; + var model = point._model; + var pointElementOptions = this.chart.options.elements.point; + + model.radius = custom.radius ? custom.radius : helpers.valueAtIndexOrDefault(dataset.pointRadius, index, pointElementOptions.radius); + model.backgroundColor = custom.backgroundColor ? custom.backgroundColor : helpers.valueAtIndexOrDefault(dataset.pointBackgroundColor, index, pointElementOptions.backgroundColor); + model.borderColor = custom.borderColor ? custom.borderColor : helpers.valueAtIndexOrDefault(dataset.pointBorderColor, index, pointElementOptions.borderColor); + model.borderWidth = custom.borderWidth ? custom.borderWidth : helpers.valueAtIndexOrDefault(dataset.pointBorderWidth, index, pointElementOptions.borderWidth); + } + }); + }; + + }, { "25": 25, "40": 40, "45": 45 }], 21: [function (require, module, exports) { + 'use strict'; + + var defaults = require(25); + + 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' + }] + }, + + showLines: false, + + 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 + ')'; + } + } + } + }); + + module.exports = function (Chart) { + + // Scatter charts use line controllers + Chart.controllers.scatter = Chart.controllers.line; + + }; + + }, { "25": 25 }], 22: [function (require, module, exports) { + /* global window: false */ + 'use strict'; + + var defaults = require(25); + var Element = require(26); + var helpers = require(45); + + defaults._set('global', { + animation: { + duration: 1000, + easing: 'easeOutQuart', + onProgress: helpers.noop, + onComplete: helpers.noop + } + }); + + module.exports = function (Chart) { + + Chart.Animation = 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 + }); + + Chart.animationService = { + frameDuration: 17, + animations: [], + dropFrames: 0, + 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; + + 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.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.requestAnimFrame.call(window, function () { + me.request = null; + me.startDigest(); + }); + } + }, + + /** + * @private + */ + startDigest: function () { + var me = this; + var startTime = Date.now(); + var framesToDrop = 0; + + if (me.dropFrames > 1) { + framesToDrop = Math.floor(me.dropFrames); + me.dropFrames = me.dropFrames % 1; + } + + me.advance(1 + framesToDrop); + + var endTime = Date.now(); + + me.dropFrames += (endTime - startTime) / me.frameDuration; + + // Do we have more stuff to animate? + if (me.animations.length > 0) { + me.requestAnimationFrame(); + } + }, + + /** + * @private + */ + advance: function (count) { + var animations = this.animations; + var animation, chart; + var i = 0; + + while (i < animations.length) { + animation = animations[i]; + chart = animation.chart; + + animation.currentStep = (animation.currentStep || 0) + count; + animation.currentStep = Math.min(animation.currentStep, animation.numSteps); + + helpers.callback(animation.render, [chart, animation], chart); + helpers.callback(animation.onAnimationProgress, [animation], chart); + + if (animation.currentStep >= animation.numSteps) { + helpers.callback(animation.onAnimationComplete, [animation], chart); + chart.animating = false; + animations.splice(i, 1); + } else { + ++i; + } + } + } + }; + + /** + * 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(Chart.Animation.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(Chart.Animation.prototype, 'chartInstance', { + get: function () { + return this.chart; + }, + set: function (value) { + this.chart = value; + } + }); + + }; + + }, { "25": 25, "26": 26, "45": 45 }], 23: [function (require, module, exports) { + 'use strict'; + + var defaults = require(25); + var helpers = require(45); + var Interaction = require(28); + var layouts = require(30); + var platform = require(48); + var plugins = require(31); + + module.exports = function (Chart) { + + // Create a dictionary of chart types, to allow for extension of existing types + Chart.types = {}; + + // Store a reference to each instance - allowing us to globally resize chart instances on window resize. + // Destroy method on the chart will remove the instance of the chart from this reference. + Chart.instances = {}; + + // Controllers available for dataset visualization eg. bar, line, slice, etc. + Chart.controllers = {}; + + /** + * Initializes the given config with global and chart default values. + */ + function initConfig(config) { + config = config || {}; + + // Do NOT use configMerge() for the data object because this method merges arrays + // and so would change references to labels and datasets, preventing data updates. + var data = config.data = config.data || {}; + data.datasets = data.datasets || []; + data.labels = data.labels || []; + + config.options = helpers.configMerge( + defaults.global, + defaults[config.type], + config.options || {}); + + return config; + } + + /** + * Updates the config of the chart + * @param chart {Chart} chart to update the options for + */ + function updateConfig(chart) { + var newOptions = chart.options; + + helpers.each(chart.scales, function (scale) { + layouts.removeBox(chart, scale); + }); + + newOptions = helpers.configMerge( + Chart.defaults.global, + Chart.defaults[chart.config.type], + newOptions); + + chart.options = chart.config.options = newOptions; + chart.ensureScalesHaveIDs(); + chart.buildOrUpdateScales(); + // Tooltip + chart.tooltip._options = newOptions.tooltips; + chart.tooltip.initialize(); + } + + function positionIsHorizontal(position) { + return position === 'top' || position === 'bottom'; + } + + helpers.extend(Chart.prototype, /** @lends Chart */ { + /** + * @private + */ + construct: function (item, config) { + var me = this; + + config = initConfig(config); + + var context = platform.acquireContext(item, config); + var canvas = context && context.canvas; + var height = canvas && canvas.height; + var width = canvas && canvas.width; + + me.id = helpers.uid(); + me.ctx = context; + me.canvas = canvas; + me.config = config; + me.width = width; + me.height = height; + me.aspectRatio = height ? width / height : null; + me.options = config.options; + me._bufferedRender = false; + + /** + * Provided for backward compatibility, Chart and Chart.Controller have been merged, + * the "instance" still need to be defined since it might be called from plugins. + * @prop Chart#chart + * @deprecated since version 2.6.0 + * @todo remove at version 3 + * @private + */ + me.chart = me; + me.controller = me; // chart.chart.controller #inception + + // Add the chart instance to the global namespace + Chart.instances[me.id] = me; + + // Define alias to the config data: `chart.data === chart.config.data` + Object.defineProperty(me, 'data', { + get: function () { + return me.config.data; + }, + set: function (value) { + me.config.data = value; + } + }); + + if (!context || !canvas) { + // The given item is not a compatible context2d element, let's return before finalizing + // the chart initialization but after setting basic chart / controller properties that + // can help to figure out that the chart is not valid (e.g chart.canvas !== null); + // https://github.com/chartjs/Chart.js/issues/2807 + console.error("Failed to create chart: can't acquire context from the given item"); + return; + } + + me.initialize(); + me.update(); + }, + + /** + * @private + */ + initialize: function () { + var me = this; + + // Before init plugin notification + plugins.notify(me, 'beforeInit'); + + helpers.retinaScale(me, me.options.devicePixelRatio); + + me.bindEvents(); + + if (me.options.responsive) { + // Initial resize before chart draws (must be silent to preserve initial animations). + me.resize(true); + } + + // Make sure scales have IDs and are built before we build any controllers. + me.ensureScalesHaveIDs(); + me.buildOrUpdateScales(); + me.initToolTip(); + + // After init plugin notification + plugins.notify(me, 'afterInit'); + + return me; + }, + + clear: function () { + helpers.canvas.clear(this); + return this; + }, + + stop: function () { + // Stops any current animation loop occurring + Chart.animationService.cancelAnimation(this); + return this; + }, + + resize: function (silent) { + var me = this; + var options = me.options; + var canvas = me.canvas; + var aspectRatio = (options.maintainAspectRatio && me.aspectRatio) || null; + + // the canvas render width and height will be casted to integers so make sure that + // the canvas display style uses the same integer values to avoid blurring effect. + + // Set to 0 instead of canvas.size because the size defaults to 300x150 if the element is collased + var newWidth = Math.max(0, Math.floor(helpers.getMaximumWidth(canvas))); + var newHeight = Math.max(0, Math.floor(aspectRatio ? newWidth / aspectRatio : helpers.getMaximumHeight(canvas))); + + if (me.width === newWidth && me.height === newHeight) { + return; + } + + canvas.width = me.width = newWidth; + canvas.height = me.height = newHeight; + canvas.style.width = newWidth + 'px'; + canvas.style.height = newHeight + 'px'; + + helpers.retinaScale(me, options.devicePixelRatio); + + if (!silent) { + // Notify any plugins about the resize + var newSize = { width: newWidth, height: newHeight }; + plugins.notify(me, 'resize', [newSize]); + + // Notify of resize + if (me.options.onResize) { + me.options.onResize(me, newSize); + } + + me.stop(); + me.update(me.options.responsiveAnimationDuration); + } + }, + + ensureScalesHaveIDs: function () { + var options = this.options; + var scalesOptions = options.scales || {}; + var scaleOptions = options.scale; + + helpers.each(scalesOptions.xAxes, function (xAxisOptions, index) { + xAxisOptions.id = xAxisOptions.id || ('x-axis-' + index); + }); + + helpers.each(scalesOptions.yAxes, function (yAxisOptions, index) { + yAxisOptions.id = yAxisOptions.id || ('y-axis-' + index); + }); + + if (scaleOptions) { + scaleOptions.id = scaleOptions.id || 'scale'; + } + }, + + /** + * Builds a map of scale ID to scale object for future lookup. + */ + buildOrUpdateScales: function () { + var me = this; + var options = me.options; + var scales = me.scales || {}; + var items = []; + var updated = Object.keys(scales).reduce(function (obj, id) { + obj[id] = false; + return obj; + }, {}); + + if (options.scales) { + items = items.concat( + (options.scales.xAxes || []).map(function (xAxisOptions) { + return { options: xAxisOptions, dtype: 'category', dposition: 'bottom' }; + }), + (options.scales.yAxes || []).map(function (yAxisOptions) { + return { options: yAxisOptions, dtype: 'linear', dposition: 'left' }; + }) + ); + } + + if (options.scale) { + items.push({ + options: options.scale, + dtype: 'radialLinear', + isDefault: true, + dposition: 'chartArea' + }); + } + + helpers.each(items, function (item) { + var scaleOptions = item.options; + var id = scaleOptions.id; + var scaleType = helpers.valueOrDefault(scaleOptions.type, item.dtype); + + if (positionIsHorizontal(scaleOptions.position) !== positionIsHorizontal(item.dposition)) { + scaleOptions.position = item.dposition; + } + + updated[id] = true; + var scale = null; + if (id in scales && scales[id].type === scaleType) { + scale = scales[id]; + scale.options = scaleOptions; + scale.ctx = me.ctx; + scale.chart = me; + } else { + var scaleClass = Chart.scaleService.getScaleConstructor(scaleType); + if (!scaleClass) { + return; + } + scale = new scaleClass({ + id: id, + type: scaleType, + options: scaleOptions, + ctx: me.ctx, + chart: me + }); + scales[scale.id] = scale; + } + + scale.mergeTicksOptions(); + + // TODO(SB): I think we should be able to remove this custom case (options.scale) + // and consider it as a regular scale part of the "scales"" map only! This would + // make the logic easier and remove some useless? custom code. + if (item.isDefault) { + me.scale = scale; + } + }); + // clear up discarded scales + helpers.each(updated, function (hasUpdated, id) { + if (!hasUpdated) { + delete scales[id]; + } + }); + + me.scales = scales; + + Chart.scaleService.addScalesToLayout(this); + }, + + buildOrUpdateControllers: function () { + var me = this; + var types = []; + var newControllers = []; + + helpers.each(me.data.datasets, function (dataset, datasetIndex) { + var meta = me.getDatasetMeta(datasetIndex); + var type = dataset.type || me.config.type; + + if (meta.type && meta.type !== type) { + me.destroyDatasetMeta(datasetIndex); + meta = me.getDatasetMeta(datasetIndex); + } + meta.type = type; + + types.push(meta.type); + + if (meta.controller) { + meta.controller.updateIndex(datasetIndex); + meta.controller.linkScales(); + } else { + var ControllerClass = Chart.controllers[meta.type]; + if (ControllerClass === undefined) { + throw new Error('"' + meta.type + '" is not a chart type.'); + } + + meta.controller = new ControllerClass(me, datasetIndex); + newControllers.push(meta.controller); + } + }, me); + + return newControllers; + }, + + /** + * Reset the elements of all datasets + * @private + */ + resetElements: function () { + var me = this; + helpers.each(me.data.datasets, function (dataset, datasetIndex) { + me.getDatasetMeta(datasetIndex).controller.reset(); + }, me); + }, + + /** + * Resets the chart back to it's state before the initial animation + */ + reset: function () { + this.resetElements(); + this.tooltip.initialize(); + }, + + update: function (config) { + var me = this; + + if (!config || typeof config !== 'object') { + // backwards compatibility + config = { + duration: config, + lazy: arguments[1] + }; + } + + updateConfig(me); + + // plugins options references might have change, let's invalidate the cache + // https://github.com/chartjs/Chart.js/issues/5111#issuecomment-355934167 + plugins._invalidate(me); + + if (plugins.notify(me, 'beforeUpdate') === false) { + return; + } + + // In case the entire data object changed + me.tooltip._data = me.data; + + // Make sure dataset controllers are updated and new controllers are reset + var newControllers = me.buildOrUpdateControllers(); + + // Make sure all dataset controllers have correct meta data counts + helpers.each(me.data.datasets, function (dataset, datasetIndex) { + me.getDatasetMeta(datasetIndex).controller.buildOrUpdateElements(); + }, me); + + me.updateLayout(); + + // Can only reset the new controllers after the scales have been updated + if (me.options.animation && me.options.animation.duration) { + helpers.each(newControllers, function (controller) { + controller.reset(); + }); + } + + me.updateDatasets(); + + // Need to reset tooltip in case it is displayed with elements that are removed + // after update. + me.tooltip.initialize(); + + // Last active contains items that were previously in the tooltip. + // When we reset the tooltip, we need to clear it + me.lastActive = []; + + // Do this before render so that any plugins that need final scale updates can use it + plugins.notify(me, 'afterUpdate'); + + if (me._bufferedRender) { + me._bufferedRequest = { + duration: config.duration, + easing: config.easing, + lazy: config.lazy + }; + } else { + me.render(config); + } + }, + + /** + * Updates the chart layout unless a plugin returns `false` to the `beforeLayout` + * hook, in which case, plugins will not be called on `afterLayout`. + * @private + */ + updateLayout: function () { + var me = this; + + if (plugins.notify(me, 'beforeLayout') === false) { + return; + } + + layouts.update(this, this.width, this.height); + + /** + * Provided for backward compatibility, use `afterLayout` instead. + * @method IPlugin#afterScaleUpdate + * @deprecated since version 2.5.0 + * @todo remove at version 3 + * @private + */ + plugins.notify(me, 'afterScaleUpdate'); + plugins.notify(me, 'afterLayout'); + }, + + /** + * Updates all datasets unless a plugin returns `false` to the `beforeDatasetsUpdate` + * hook, in which case, plugins will not be called on `afterDatasetsUpdate`. + * @private + */ + updateDatasets: function () { + var me = this; + + if (plugins.notify(me, 'beforeDatasetsUpdate') === false) { + return; + } + + for (var i = 0, ilen = me.data.datasets.length; i < ilen; ++i) { + me.updateDataset(i); + } + + plugins.notify(me, 'afterDatasetsUpdate'); + }, + + /** + * Updates dataset at index unless a plugin returns `false` to the `beforeDatasetUpdate` + * hook, in which case, plugins will not be called on `afterDatasetUpdate`. + * @private + */ + updateDataset: function (index) { + var me = this; + var meta = me.getDatasetMeta(index); + var args = { + meta: meta, + index: index + }; + + if (plugins.notify(me, 'beforeDatasetUpdate', [args]) === false) { + return; + } + + meta.controller.update(); + + plugins.notify(me, 'afterDatasetUpdate', [args]); + }, + + render: function (config) { + var me = this; + + if (!config || typeof config !== 'object') { + // backwards compatibility + config = { + duration: config, + lazy: arguments[1] + }; + } + + var duration = config.duration; + var lazy = config.lazy; + + if (plugins.notify(me, 'beforeRender') === false) { + return; + } + + var animationOptions = me.options.animation; + var onComplete = function (animation) { + plugins.notify(me, 'afterRender'); + helpers.callback(animationOptions && animationOptions.onComplete, [animation], me); + }; + + if (animationOptions && ((typeof duration !== 'undefined' && duration !== 0) || (typeof duration === 'undefined' && animationOptions.duration !== 0))) { + var animation = new Chart.Animation({ + numSteps: (duration || animationOptions.duration) / 16.66, // 60 fps + easing: config.easing || animationOptions.easing, + + render: function (chart, animationObject) { + var easingFunction = helpers.easing.effects[animationObject.easing]; + var currentStep = animationObject.currentStep; + var stepDecimal = currentStep / animationObject.numSteps; + + chart.draw(easingFunction(stepDecimal), stepDecimal, currentStep); + }, + + onAnimationProgress: animationOptions.onProgress, + onAnimationComplete: onComplete + }); + + Chart.animationService.addAnimation(me, animation, duration, lazy); + } else { + me.draw(); + + // See https://github.com/chartjs/Chart.js/issues/3781 + onComplete(new Chart.Animation({ numSteps: 0, chart: me })); + } + + return me; + }, + + draw: function (easingValue) { + var me = this; + + me.clear(); + + if (helpers.isNullOrUndef(easingValue)) { + easingValue = 1; + } + + me.transition(easingValue); + + if (plugins.notify(me, 'beforeDraw', [easingValue]) === false) { + return; + } + + // Draw all the scales + helpers.each(me.boxes, function (box) { + box.draw(me.chartArea); + }, me); + + if (me.scale) { + me.scale.draw(); + } + + me.drawDatasets(easingValue); + me._drawTooltip(easingValue); + + plugins.notify(me, 'afterDraw', [easingValue]); + }, + + /** + * @private + */ + transition: function (easingValue) { + var me = this; + + for (var i = 0, ilen = (me.data.datasets || []).length; i < ilen; ++i) { + if (me.isDatasetVisible(i)) { + me.getDatasetMeta(i).controller.transition(easingValue); + } + } + + me.tooltip.transition(easingValue); + }, + + /** + * Draws all datasets unless a plugin returns `false` to the `beforeDatasetsDraw` + * hook, in which case, plugins will not be called on `afterDatasetsDraw`. + * @private + */ + drawDatasets: function (easingValue) { + var me = this; + + if (plugins.notify(me, 'beforeDatasetsDraw', [easingValue]) === false) { + return; + } + + // Draw datasets reversed to support proper line stacking + for (var i = (me.data.datasets || []).length - 1; i >= 0; --i) { + if (me.isDatasetVisible(i)) { + me.drawDataset(i, easingValue); + } + } + + plugins.notify(me, 'afterDatasetsDraw', [easingValue]); + }, + + /** + * Draws dataset at index unless a plugin returns `false` to the `beforeDatasetDraw` + * hook, in which case, plugins will not be called on `afterDatasetDraw`. + * @private + */ + drawDataset: function (index, easingValue) { + var me = this; + var meta = me.getDatasetMeta(index); + var args = { + meta: meta, + index: index, + easingValue: easingValue + }; + + if (plugins.notify(me, 'beforeDatasetDraw', [args]) === false) { + return; + } + + meta.controller.draw(easingValue); + + plugins.notify(me, 'afterDatasetDraw', [args]); + }, + + /** + * Draws tooltip unless a plugin returns `false` to the `beforeTooltipDraw` + * hook, in which case, plugins will not be called on `afterTooltipDraw`. + * @private + */ + _drawTooltip: function (easingValue) { + var me = this; + var tooltip = me.tooltip; + var args = { + tooltip: tooltip, + easingValue: easingValue + }; + + if (plugins.notify(me, 'beforeTooltipDraw', [args]) === false) { + return; + } + + tooltip.draw(); + + plugins.notify(me, 'afterTooltipDraw', [args]); + }, + + // Get the single element that was clicked on + // @return : An object containing the dataset index and element index of the matching element. Also contains the rectangle that was draw + getElementAtEvent: function (e) { + return Interaction.modes.single(this, e); + }, + + getElementsAtEvent: function (e) { + return Interaction.modes.label(this, e, { intersect: true }); + }, + + getElementsAtXAxis: function (e) { + return Interaction.modes['x-axis'](this, e, { intersect: true }); + }, + + getElementsAtEventForMode: function (e, mode, options) { + var method = Interaction.modes[mode]; + if (typeof method === 'function') { + return method(this, e, options); + } + + return []; + }, + + getDatasetAtEvent: function (e) { + return Interaction.modes.dataset(this, e, { intersect: true }); + }, + + getDatasetMeta: function (datasetIndex) { + var me = this; + var dataset = me.data.datasets[datasetIndex]; + if (!dataset._meta) { + dataset._meta = {}; + } + + var meta = dataset._meta[me.id]; + if (!meta) { + meta = dataset._meta[me.id] = { + type: null, + data: [], + dataset: null, + controller: null, + hidden: null, // See isDatasetVisible() comment + xAxisID: null, + yAxisID: null + }; + } + + return meta; + }, + + getVisibleDatasetCount: function () { + var count = 0; + for (var i = 0, ilen = this.data.datasets.length; i < ilen; ++i) { + if (this.isDatasetVisible(i)) { + count++; + } + } + return count; + }, + + isDatasetVisible: function (datasetIndex) { + var meta = this.getDatasetMeta(datasetIndex); + + // meta.hidden is a per chart dataset hidden flag override with 3 states: if true or false, + // the dataset.hidden value is ignored, else if null, the dataset hidden state is returned. + return typeof meta.hidden === 'boolean' ? !meta.hidden : !this.data.datasets[datasetIndex].hidden; + }, + + generateLegend: function () { + return this.options.legendCallback(this); + }, + + /** + * @private + */ + destroyDatasetMeta: function (datasetIndex) { + var id = this.id; + var dataset = this.data.datasets[datasetIndex]; + var meta = dataset._meta && dataset._meta[id]; + + if (meta) { + meta.controller.destroy(); + delete dataset._meta[id]; + } + }, + + destroy: function () { + var me = this; + var canvas = me.canvas; + var i, ilen; + + me.stop(); + + // dataset controllers need to cleanup associated data + for (i = 0, ilen = me.data.datasets.length; i < ilen; ++i) { + me.destroyDatasetMeta(i); + } + + if (canvas) { + me.unbindEvents(); + helpers.canvas.clear(me); + platform.releaseContext(me.ctx); + me.canvas = null; + me.ctx = null; + } + + plugins.notify(me, 'destroy'); + + delete Chart.instances[me.id]; + }, + + toBase64Image: function () { + return this.canvas.toDataURL.apply(this.canvas, arguments); + }, + + initToolTip: function () { + var me = this; + me.tooltip = new Chart.Tooltip({ + _chart: me, + _chartInstance: me, // deprecated, backward compatibility + _data: me.data, + _options: me.options.tooltips + }, me); + }, + + /** + * @private + */ + bindEvents: function () { + var me = this; + var listeners = me._listeners = {}; + var listener = function () { + me.eventHandler.apply(me, arguments); + }; + + helpers.each(me.options.events, function (type) { + platform.addEventListener(me, type, listener); + listeners[type] = listener; + }); + + // Elements used to detect size change should not be injected for non responsive charts. + // See https://github.com/chartjs/Chart.js/issues/2210 + if (me.options.responsive) { + listener = function () { + me.resize(); + }; + + platform.addEventListener(me, 'resize', listener); + listeners.resize = listener; + } + }, + + /** + * @private + */ + unbindEvents: function () { + var me = this; + var listeners = me._listeners; + if (!listeners) { + return; + } + + delete me._listeners; + helpers.each(listeners, function (listener, type) { + platform.removeEventListener(me, type, listener); + }); + }, + + updateHoverStyle: function (elements, mode, enabled) { + var method = enabled ? 'setHoverStyle' : 'removeHoverStyle'; + var element, i, ilen; + + for (i = 0, ilen = elements.length; i < ilen; ++i) { + element = elements[i]; + if (element) { + this.getDatasetMeta(element._datasetIndex).controller[method](element); + } + } + }, + + /** + * @private + */ + eventHandler: function (e) { + var me = this; + var tooltip = me.tooltip; + + if (plugins.notify(me, 'beforeEvent', [e]) === false) { + return; + } + + // Buffer any update calls so that renders do not occur + me._bufferedRender = true; + me._bufferedRequest = null; + + var changed = me.handleEvent(e); + // for smooth tooltip animations issue #4989 + // the tooltip should be the source of change + // Animation check workaround: + // tooltip._start will be null when tooltip isn't animating + if (tooltip) { + changed = tooltip._start + ? tooltip.handleEvent(e) + : changed | tooltip.handleEvent(e); + } + + plugins.notify(me, 'afterEvent', [e]); + + var bufferedRequest = me._bufferedRequest; + if (bufferedRequest) { + // If we have an update that was triggered, we need to do a normal render + me.render(bufferedRequest); + } else if (changed && !me.animating) { + // If entering, leaving, or changing elements, animate the change via pivot + me.stop(); + + // We only need to render at this point. Updating will cause scales to be + // recomputed generating flicker & using more memory than necessary. + me.render(me.options.hover.animationDuration, true); + } + + me._bufferedRender = false; + me._bufferedRequest = null; + + return me; + }, + + /** + * Handle an event + * @private + * @param {IEvent} event the event to handle + * @return {Boolean} true if the chart needs to re-render + */ + handleEvent: function (e) { + var me = this; + var options = me.options || {}; + var hoverOptions = options.hover; + var changed = false; + + me.lastActive = me.lastActive || []; + + // Find Active Elements for hover and tooltips + if (e.type === 'mouseout') { + me.active = []; + } else { + me.active = me.getElementsAtEventForMode(e, hoverOptions.mode, hoverOptions); + } + + // Invoke onHover hook + // Need to call with native event here to not break backwards compatibility + helpers.callback(options.onHover || options.hover.onHover, [e.native, me.active], me); + + if (e.type === 'mouseup' || e.type === 'click') { + if (options.onClick) { + // Use e.native here for backwards compatibility + options.onClick.call(me, e.native, me.active); + } + } + + // Remove styling for last active (even if it may still be active) + if (me.lastActive.length) { + me.updateHoverStyle(me.lastActive, hoverOptions.mode, false); + } + + // Built in hover styling + if (me.active.length && hoverOptions.mode) { + me.updateHoverStyle(me.active, hoverOptions.mode, true); + } + + changed = !helpers.arrayEquals(me.active, me.lastActive); + + // Remember Last Actives + me.lastActive = me.active; + + return changed; + } + }); + + /** + * Provided for backward compatibility, use Chart instead. + * @class Chart.Controller + * @deprecated since version 2.6.0 + * @todo remove at version 3 + * @private + */ + Chart.Controller = Chart; + }; + + }, { "25": 25, "28": 28, "30": 30, "31": 31, "45": 45, "48": 48 }], 24: [function (require, module, exports) { + 'use strict'; + + var helpers = require(45); + + module.exports = function (Chart) { + + 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.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) + Chart.DatasetController = function (chart, datasetIndex) { + this.initialize(chart, datasetIndex); + }; + + helpers.extend(Chart.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, + + initialize: function (chart, datasetIndex) { + var me = this; + me.chart = chart; + me.index = datasetIndex; + me.linkScales(); + me.addElements(); + }, + + updateIndex: function (datasetIndex) { + this.index = datasetIndex; + }, + + linkScales: function () { + var me = this; + var meta = me.getMeta(); + var dataset = me.getDataset(); + + if (meta.xAxisID === null || !(meta.xAxisID in me.chart.scales)) { + meta.xAxisID = dataset.xAxisID || me.chart.options.scales.xAxes[0].id; + } + if (meta.yAxisID === null || !(meta.yAxisID in me.chart.scales)) { + meta.yAxisID = dataset.yAxisID || me.chart.options.scales.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]; + }, + + 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); + } + + 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(); + }, + + update: helpers.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(); + } + }, + + removeHoverStyle: function (element, elementOpts) { + var dataset = this.chart.data.datasets[element._datasetIndex]; + var index = element._index; + var custom = element.custom || {}; + var valueOrDefault = helpers.valueAtIndexOrDefault; + var model = element._model; + + model.backgroundColor = custom.backgroundColor ? custom.backgroundColor : valueOrDefault(dataset.backgroundColor, index, elementOpts.backgroundColor); + model.borderColor = custom.borderColor ? custom.borderColor : valueOrDefault(dataset.borderColor, index, elementOpts.borderColor); + model.borderWidth = custom.borderWidth ? custom.borderWidth : valueOrDefault(dataset.borderWidth, index, elementOpts.borderWidth); + }, + + setHoverStyle: function (element) { + var dataset = this.chart.data.datasets[element._datasetIndex]; + var index = element._index; + var custom = element.custom || {}; + var valueOrDefault = helpers.valueAtIndexOrDefault; + var getHoverColor = helpers.getHoverColor; + var model = element._model; + + model.backgroundColor = custom.hoverBackgroundColor ? custom.hoverBackgroundColor : valueOrDefault(dataset.hoverBackgroundColor, index, getHoverColor(model.backgroundColor)); + model.borderColor = custom.hoverBorderColor ? custom.hoverBorderColor : valueOrDefault(dataset.hoverBorderColor, index, getHoverColor(model.borderColor)); + model.borderWidth = custom.hoverBorderWidth ? custom.hoverBorderWidth : valueOrDefault(dataset.hoverBorderWidth, index, model.borderWidth); + }, + + /** + * @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 () { + this.insertElements(this.getDataset().data.length - 1, arguments.length); + }, + + /** + * @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); + } + }); + + Chart.DatasetController.extend = helpers.inherits; + }; + + }, { "45": 45 }], 25: [function (require, module, exports) { + 'use strict'; + + var helpers = require(45); + + module.exports = { + /** + * @private + */ + _set: function (scope, values) { + return helpers.merge(this[scope] || (this[scope] = {}), values); + } + }; + + }, { "45": 45 }], 26: [function (require, module, exports) { + 'use strict'; + + var color = require(2); + var helpers = require(45); + + 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 = color(origin); + if (c0.valid) { + c1 = color(target); + if (c1.valid) { + view[key] = c1.mix(c0, ease).rgbString(); + continue; + } + } + } else if (type === 'number' && isFinite(origin) && isFinite(target)) { + view[key] = origin + (target - origin) * ease; + continue; + } + } + + view[key] = target; + } + } + + var Element = function (configuration) { + helpers.extend(this, configuration); + this.initialize.apply(this, arguments); + }; + + helpers.extend(Element.prototype, { + + initialize: function () { + this.hidden = false; + }, + + pivot: function () { + var me = this; + if (!me._view) { + me._view = helpers.clone(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 = 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.isNumber(this._model.x) && helpers.isNumber(this._model.y); + } + }); + + Element.extend = helpers.inherits; + + module.exports = Element; + + }, { "2": 2, "45": 45 }], 27: [function (require, module, exports) { + /* global window: false */ + /* global document: false */ + 'use strict'; + + var color = require(2); + var defaults = require(25); + var helpers = require(45); + + module.exports = function (Chart) { + + // -- Basic js utility methods + + helpers.configMerge = function (/* objects ... */) { + return helpers.merge(helpers.clone(arguments[0]), [].slice.call(arguments, 1), { + merger: function (key, target, source, options) { + var tval = target[key] || {}; + var sval = source[key]; + + if (key === 'scales') { + // scale config merging is complex. Add our own function here for that + target[key] = helpers.scaleMerge(tval, sval); + } else if (key === 'scale') { + // used in polar area & radar charts since there is only one scale + target[key] = helpers.merge(tval, [Chart.scaleService.getScaleDefaults(sval.type), sval]); + } else { + helpers._merger(key, target, source, options); + } + } + }); + }; + + helpers.scaleMerge = function (/* objects ... */) { + return helpers.merge(helpers.clone(arguments[0]), [].slice.call(arguments, 1), { + merger: function (key, target, source, options) { + if (key === 'xAxes' || key === 'yAxes') { + var slen = source[key].length; + var i, type, scale; + + if (!target[key]) { + target[key] = []; + } + + for (i = 0; i < slen; ++i) { + scale = source[key][i]; + type = helpers.valueOrDefault(scale.type, key === 'xAxes' ? 'category' : 'linear'); + + if (i >= target[key].length) { + target[key].push({}); + } + + if (!target[key][i].type || (scale.type && scale.type !== target[key][i].type)) { + // new/untyped scale or type changed: let's apply the new defaults + // then merge source scale to correctly overwrite the defaults. + helpers.merge(target[key][i], [Chart.scaleService.getScaleDefaults(type), scale]); + } else { + // scales type are the same + helpers.merge(target[key][i], scale); + } + } + } else { + helpers._merger(key, target, source, options); + } + } + }); + }; + + helpers.where = function (collection, filterCallback) { + if (helpers.isArray(collection) && Array.prototype.filter) { + return collection.filter(filterCallback); + } + var filtered = []; + + helpers.each(collection, function (item) { + if (filterCallback(item)) { + filtered.push(item); + } + }); + + return filtered; + }; + helpers.findIndex = Array.prototype.findIndex ? + function (array, callback, scope) { + return array.findIndex(callback, scope); + } : + function (array, callback, scope) { + scope = scope === undefined ? array : scope; + for (var i = 0, ilen = array.length; i < ilen; ++i) { + if (callback.call(scope, array[i], i, array)) { + return i; + } + } + return -1; + }; + helpers.findNextWhere = function (arrayToSearch, filterCallback, startIndex) { + // Default to start of the array + if (helpers.isNullOrUndef(startIndex)) { + startIndex = -1; + } + for (var i = startIndex + 1; i < arrayToSearch.length; i++) { + var currentItem = arrayToSearch[i]; + if (filterCallback(currentItem)) { + return currentItem; + } + } + }; + helpers.findPreviousWhere = function (arrayToSearch, filterCallback, startIndex) { + // Default to end of the array + if (helpers.isNullOrUndef(startIndex)) { + startIndex = arrayToSearch.length; + } + for (var i = startIndex - 1; i >= 0; i--) { + var currentItem = arrayToSearch[i]; + if (filterCallback(currentItem)) { + return currentItem; + } + } + }; + + // -- Math methods + helpers.isNumber = function (n) { + return !isNaN(parseFloat(n)) && isFinite(n); + }; + helpers.almostEquals = function (x, y, epsilon) { + return Math.abs(x - y) < epsilon; + }; + helpers.almostWhole = function (x, epsilon) { + var rounded = Math.round(x); + return (((rounded - epsilon) < x) && ((rounded + epsilon) > x)); + }; + helpers.max = function (array) { + return array.reduce(function (max, value) { + if (!isNaN(value)) { + return Math.max(max, value); + } + return max; + }, Number.NEGATIVE_INFINITY); + }; + helpers.min = function (array) { + return array.reduce(function (min, value) { + if (!isNaN(value)) { + return Math.min(min, value); + } + return min; + }, Number.POSITIVE_INFINITY); + }; + helpers.sign = Math.sign ? + function (x) { + return Math.sign(x); + } : + function (x) { + x = +x; // convert to a number + if (x === 0 || isNaN(x)) { + return x; + } + return x > 0 ? 1 : -1; + }; + helpers.log10 = Math.log10 ? + function (x) { + return Math.log10(x); + } : + function (x) { + var exponent = Math.log(x) * Math.LOG10E; // Math.LOG10E = 1 / Math.LN10. + // Check for whole powers of 10, + // which due to floating point rounding error should be corrected. + var powerOf10 = Math.round(exponent); + var isPowerOf10 = x === Math.pow(10, powerOf10); + + return isPowerOf10 ? powerOf10 : exponent; + }; + helpers.toRadians = function (degrees) { + return degrees * (Math.PI / 180); + }; + helpers.toDegrees = function (radians) { + return radians * (180 / Math.PI); + }; + // Gets the angle from vertical upright to the point about a centre. + helpers.getAngleFromPoint = function (centrePoint, anglePoint) { + var distanceFromXCenter = anglePoint.x - centrePoint.x; + var distanceFromYCenter = anglePoint.y - centrePoint.y; + var radialDistanceFromCenter = Math.sqrt(distanceFromXCenter * distanceFromXCenter + distanceFromYCenter * distanceFromYCenter); + + var angle = Math.atan2(distanceFromYCenter, distanceFromXCenter); + + if (angle < (-0.5 * Math.PI)) { + angle += 2.0 * Math.PI; // make sure the returned angle is in the range of (-PI/2, 3PI/2] + } + + return { + angle: angle, + distance: radialDistanceFromCenter + }; + }; + helpers.distanceBetweenPoints = function (pt1, pt2) { + return Math.sqrt(Math.pow(pt2.x - pt1.x, 2) + Math.pow(pt2.y - pt1.y, 2)); + }; + helpers.aliasPixel = function (pixelWidth) { + return (pixelWidth % 2 === 0) ? 0 : 0.5; + }; + helpers.splineCurve = function (firstPoint, middlePoint, afterPoint, t) { + // Props to Rob Spencer at scaled innovation for his post on splining between points + // http://scaledinnovation.com/analytics/splines/aboutSplines.html + + // This function must also respect "skipped" points + + var previous = firstPoint.skip ? middlePoint : firstPoint; + var current = middlePoint; + var next = afterPoint.skip ? middlePoint : afterPoint; + + var d01 = Math.sqrt(Math.pow(current.x - previous.x, 2) + Math.pow(current.y - previous.y, 2)); + var d12 = Math.sqrt(Math.pow(next.x - current.x, 2) + Math.pow(next.y - current.y, 2)); + + var s01 = d01 / (d01 + d12); + var s12 = d12 / (d01 + d12); + + // If all points are the same, s01 & s02 will be inf + s01 = isNaN(s01) ? 0 : s01; + s12 = isNaN(s12) ? 0 : s12; + + var fa = t * s01; // scaling factor for triangle Ta + var fb = t * s12; + + return { + previous: { + x: current.x - fa * (next.x - previous.x), + y: current.y - fa * (next.y - previous.y) + }, + next: { + x: current.x + fb * (next.x - previous.x), + y: current.y + fb * (next.y - previous.y) + } + }; + }; + helpers.EPSILON = Number.EPSILON || 1e-14; + helpers.splineCurveMonotone = function (points) { + // This function calculates Bézier control points in a similar way than |splineCurve|, + // but preserves monotonicity of the provided data and ensures no local extremums are added + // between the dataset discrete points due to the interpolation. + // See : https://en.wikipedia.org/wiki/Monotone_cubic_interpolation + + var pointsWithTangents = (points || []).map(function (point) { + return { + model: point._model, + deltaK: 0, + mK: 0 + }; + }); + + // Calculate slopes (deltaK) and initialize tangents (mK) + var pointsLen = pointsWithTangents.length; + var i, pointBefore, pointCurrent, pointAfter; + for (i = 0; i < pointsLen; ++i) { + pointCurrent = pointsWithTangents[i]; + if (pointCurrent.model.skip) { + continue; + } + + pointBefore = i > 0 ? pointsWithTangents[i - 1] : null; + pointAfter = i < pointsLen - 1 ? pointsWithTangents[i + 1] : null; + if (pointAfter && !pointAfter.model.skip) { + var slopeDeltaX = (pointAfter.model.x - pointCurrent.model.x); + + // In the case of two points that appear at the same x pixel, slopeDeltaX is 0 + pointCurrent.deltaK = slopeDeltaX !== 0 ? (pointAfter.model.y - pointCurrent.model.y) / slopeDeltaX : 0; + } + + if (!pointBefore || pointBefore.model.skip) { + pointCurrent.mK = pointCurrent.deltaK; + } else if (!pointAfter || pointAfter.model.skip) { + pointCurrent.mK = pointBefore.deltaK; + } else if (this.sign(pointBefore.deltaK) !== this.sign(pointCurrent.deltaK)) { + pointCurrent.mK = 0; + } else { + pointCurrent.mK = (pointBefore.deltaK + pointCurrent.deltaK) / 2; + } + } + + // Adjust tangents to ensure monotonic properties + var alphaK, betaK, tauK, squaredMagnitude; + for (i = 0; i < pointsLen - 1; ++i) { + pointCurrent = pointsWithTangents[i]; + pointAfter = pointsWithTangents[i + 1]; + if (pointCurrent.model.skip || pointAfter.model.skip) { + continue; + } + + if (helpers.almostEquals(pointCurrent.deltaK, 0, this.EPSILON)) { + pointCurrent.mK = pointAfter.mK = 0; + continue; + } + + alphaK = pointCurrent.mK / pointCurrent.deltaK; + betaK = pointAfter.mK / pointCurrent.deltaK; + squaredMagnitude = Math.pow(alphaK, 2) + Math.pow(betaK, 2); + if (squaredMagnitude <= 9) { + continue; + } + + tauK = 3 / Math.sqrt(squaredMagnitude); + pointCurrent.mK = alphaK * tauK * pointCurrent.deltaK; + pointAfter.mK = betaK * tauK * pointCurrent.deltaK; + } + + // Compute control points + var deltaX; + for (i = 0; i < pointsLen; ++i) { + pointCurrent = pointsWithTangents[i]; + if (pointCurrent.model.skip) { + continue; + } + + pointBefore = i > 0 ? pointsWithTangents[i - 1] : null; + pointAfter = i < pointsLen - 1 ? pointsWithTangents[i + 1] : null; + if (pointBefore && !pointBefore.model.skip) { + deltaX = (pointCurrent.model.x - pointBefore.model.x) / 3; + pointCurrent.model.controlPointPreviousX = pointCurrent.model.x - deltaX; + pointCurrent.model.controlPointPreviousY = pointCurrent.model.y - deltaX * pointCurrent.mK; + } + if (pointAfter && !pointAfter.model.skip) { + deltaX = (pointAfter.model.x - pointCurrent.model.x) / 3; + pointCurrent.model.controlPointNextX = pointCurrent.model.x + deltaX; + pointCurrent.model.controlPointNextY = pointCurrent.model.y + deltaX * pointCurrent.mK; + } + } + }; + helpers.nextItem = function (collection, index, loop) { + if (loop) { + return index >= collection.length - 1 ? collection[0] : collection[index + 1]; + } + return index >= collection.length - 1 ? collection[collection.length - 1] : collection[index + 1]; + }; + helpers.previousItem = function (collection, index, loop) { + if (loop) { + return index <= 0 ? collection[collection.length - 1] : collection[index - 1]; + } + return index <= 0 ? collection[0] : collection[index - 1]; + }; + // Implementation of the nice number algorithm used in determining where axis labels will go + helpers.niceNum = function (range, round) { + var exponent = Math.floor(helpers.log10(range)); + var fraction = range / Math.pow(10, exponent); + var niceFraction; + + if (round) { + if (fraction < 1.5) { + niceFraction = 1; + } else if (fraction < 3) { + niceFraction = 2; + } else if (fraction < 7) { + niceFraction = 5; + } else { + niceFraction = 10; + } + } else if (fraction <= 1.0) { + niceFraction = 1; + } else if (fraction <= 2) { + niceFraction = 2; + } else if (fraction <= 5) { + niceFraction = 5; + } else { + niceFraction = 10; + } + + return niceFraction * Math.pow(10, exponent); + }; + // Request animation polyfill - http://www.paulirish.com/2011/requestanimationframe-for-smart-animating/ + helpers.requestAnimFrame = (function () { + if (typeof window === 'undefined') { + return function (callback) { + callback(); + }; + } + return window.requestAnimationFrame || + window.webkitRequestAnimationFrame || + window.mozRequestAnimationFrame || + window.oRequestAnimationFrame || + window.msRequestAnimationFrame || + function (callback) { + return window.setTimeout(callback, 1000 / 60); + }; + } ()); + // -- DOM methods + helpers.getRelativePosition = function (evt, chart) { + var mouseX, mouseY; + var e = evt.originalEvent || evt; + var canvas = evt.currentTarget || evt.srcElement; + var boundingRect = canvas.getBoundingClientRect(); + + var touches = e.touches; + if (touches && touches.length > 0) { + mouseX = touches[0].clientX; + mouseY = touches[0].clientY; + + } else { + mouseX = e.clientX; + mouseY = e.clientY; + } + + // Scale mouse coordinates into canvas coordinates + // by following the pattern laid out by 'jerryj' in the comments of + // http://www.html5canvastutorials.com/advanced/html5-canvas-mouse-coordinates/ + var paddingLeft = parseFloat(helpers.getStyle(canvas, 'padding-left')); + var paddingTop = parseFloat(helpers.getStyle(canvas, 'padding-top')); + var paddingRight = parseFloat(helpers.getStyle(canvas, 'padding-right')); + var paddingBottom = parseFloat(helpers.getStyle(canvas, 'padding-bottom')); + var width = boundingRect.right - boundingRect.left - paddingLeft - paddingRight; + var height = boundingRect.bottom - boundingRect.top - paddingTop - paddingBottom; + + // We divide by the current device pixel ratio, because the canvas is scaled up by that amount in each direction. However + // the backend model is in unscaled coordinates. Since we are going to deal with our model coordinates, we go back here + mouseX = Math.round((mouseX - boundingRect.left - paddingLeft) / (width) * canvas.width / chart.currentDevicePixelRatio); + mouseY = Math.round((mouseY - boundingRect.top - paddingTop) / (height) * canvas.height / chart.currentDevicePixelRatio); + + return { + x: mouseX, + y: mouseY + }; + + }; + + // Private helper function to convert max-width/max-height values that may be percentages into a number + function parseMaxStyle(styleValue, node, parentProperty) { + var valueInPixels; + if (typeof styleValue === 'string') { + valueInPixels = parseInt(styleValue, 10); + + if (styleValue.indexOf('%') !== -1) { + // percentage * size in dimension + valueInPixels = valueInPixels / 100 * node.parentNode[parentProperty]; + } + } else { + valueInPixels = styleValue; + } + + return valueInPixels; + } + + /** + * Returns if the given value contains an effective constraint. + * @private + */ + function isConstrainedValue(value) { + return value !== undefined && value !== null && value !== 'none'; + } + + // Private helper to get a constraint dimension + // @param domNode : the node to check the constraint on + // @param maxStyle : the style that defines the maximum for the direction we are using (maxWidth / maxHeight) + // @param percentageProperty : property of parent to use when calculating width as a percentage + // @see http://www.nathanaeljones.com/blog/2013/reading-max-width-cross-browser + function getConstraintDimension(domNode, maxStyle, percentageProperty) { + var view = document.defaultView; + var parentNode = domNode.parentNode; + var constrainedNode = view.getComputedStyle(domNode)[maxStyle]; + var constrainedContainer = view.getComputedStyle(parentNode)[maxStyle]; + var hasCNode = isConstrainedValue(constrainedNode); + var hasCContainer = isConstrainedValue(constrainedContainer); + var infinity = Number.POSITIVE_INFINITY; + + if (hasCNode || hasCContainer) { + return Math.min( + hasCNode ? parseMaxStyle(constrainedNode, domNode, percentageProperty) : infinity, + hasCContainer ? parseMaxStyle(constrainedContainer, parentNode, percentageProperty) : infinity); + } + + return 'none'; + } + // returns Number or undefined if no constraint + helpers.getConstraintWidth = function (domNode) { + return getConstraintDimension(domNode, 'max-width', 'clientWidth'); + }; + // returns Number or undefined if no constraint + helpers.getConstraintHeight = function (domNode) { + return getConstraintDimension(domNode, 'max-height', 'clientHeight'); + }; + helpers.getMaximumWidth = function (domNode) { + var container = domNode.parentNode; + if (!container) { + return domNode.clientWidth; + } + + var paddingLeft = parseInt(helpers.getStyle(container, 'padding-left'), 10); + var paddingRight = parseInt(helpers.getStyle(container, 'padding-right'), 10); + var w = container.clientWidth - paddingLeft - paddingRight; + var cw = helpers.getConstraintWidth(domNode); + return isNaN(cw) ? w : Math.min(w, cw); + }; + helpers.getMaximumHeight = function (domNode) { + var container = domNode.parentNode; + if (!container) { + return domNode.clientHeight; + } + + var paddingTop = parseInt(helpers.getStyle(container, 'padding-top'), 10); + var paddingBottom = parseInt(helpers.getStyle(container, 'padding-bottom'), 10); + var h = container.clientHeight - paddingTop - paddingBottom; + var ch = helpers.getConstraintHeight(domNode); + return isNaN(ch) ? h : Math.min(h, ch); + }; + helpers.getStyle = function (el, property) { + return el.currentStyle ? + el.currentStyle[property] : + document.defaultView.getComputedStyle(el, null).getPropertyValue(property); + }; + helpers.retinaScale = function (chart, forceRatio) { + var pixelRatio = chart.currentDevicePixelRatio = forceRatio || window.devicePixelRatio || 1; + if (pixelRatio === 1) { + return; + } + + var canvas = chart.canvas; + var height = chart.height; + var width = chart.width; + + canvas.height = height * pixelRatio; + canvas.width = width * pixelRatio; + chart.ctx.scale(pixelRatio, pixelRatio); + + // If no style has been set on the canvas, the render size is used as display size, + // making the chart visually bigger, so let's enforce it to the "correct" values. + // See https://github.com/chartjs/Chart.js/issues/3575 + if (!canvas.style.height && !canvas.style.width) { + canvas.style.height = height + 'px'; + canvas.style.width = width + 'px'; + } + }; + // -- Canvas methods + helpers.fontString = function (pixelSize, fontStyle, fontFamily) { + return fontStyle + ' ' + pixelSize + 'px ' + fontFamily; + }; + helpers.longestText = function (ctx, font, arrayOfThings, cache) { + cache = cache || {}; + var data = cache.data = cache.data || {}; + var gc = cache.garbageCollect = cache.garbageCollect || []; + + if (cache.font !== font) { + data = cache.data = {}; + gc = cache.garbageCollect = []; + cache.font = font; + } + + ctx.font = font; + var longest = 0; + helpers.each(arrayOfThings, function (thing) { + // Undefined strings and arrays should not be measured + if (thing !== undefined && thing !== null && helpers.isArray(thing) !== true) { + longest = helpers.measureText(ctx, data, gc, longest, thing); + } else if (helpers.isArray(thing)) { + // if it is an array lets measure each element + // to do maybe simplify this function a bit so we can do this more recursively? + helpers.each(thing, function (nestedThing) { + // Undefined strings and arrays should not be measured + if (nestedThing !== undefined && nestedThing !== null && !helpers.isArray(nestedThing)) { + longest = helpers.measureText(ctx, data, gc, longest, nestedThing); + } + }); + } + }); + + var gcLen = gc.length / 2; + if (gcLen > arrayOfThings.length) { + for (var i = 0; i < gcLen; i++) { + delete data[gc[i]]; + } + gc.splice(0, gcLen); + } + return longest; + }; + helpers.measureText = function (ctx, data, gc, longest, string) { + var textWidth = data[string]; + if (!textWidth) { + textWidth = data[string] = ctx.measureText(string).width; + gc.push(string); + } + if (textWidth > longest) { + longest = textWidth; + } + return longest; + }; + helpers.numberOfLabelLines = function (arrayOfThings) { + var numberOfLines = 1; + helpers.each(arrayOfThings, function (thing) { + if (helpers.isArray(thing)) { + if (thing.length > numberOfLines) { + numberOfLines = thing.length; + } + } + }); + return numberOfLines; + }; + + helpers.color = !color ? + function (value) { + console.error('Color.js not found!'); + return value; + } : + function (value) { + /* global CanvasGradient */ + if (value instanceof CanvasGradient) { + value = defaults.global.defaultColor; + } + + return color(value); + }; + + helpers.getHoverColor = function (colorValue) { + /* global CanvasPattern */ + return (colorValue instanceof CanvasPattern) ? + colorValue : + helpers.color(colorValue).saturate(0.5).darken(0.1).rgbString(); + }; + }; + + }, { "2": 2, "25": 25, "45": 45 }], 28: [function (require, module, exports) { + 'use strict'; + + var helpers = require(45); + + /** + * 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 {Point} the event position + */ + function getRelativePosition(e, chart) { + if (e.native) { + return { + x: e.x, + y: e.y + }; + } + + return helpers.getRelativePosition(e, chart); + } + + /** + * Helper function to traverse all of the visible elements in the chart + * @param chart {chart} the chart + * @param handler {Function} the callback to execute for each visible item + */ + function parseVisibleItems(chart, handler) { + var datasets = chart.data.datasets; + var meta, i, j, ilen, jlen; + + for (i = 0, ilen = datasets.length; i < ilen; ++i) { + if (!chart.isDatasetVisible(i)) { + continue; + } + + meta = chart.getDatasetMeta(i); + for (j = 0, jlen = meta.data.length; j < jlen; ++j) { + var element = meta.data[j]; + if (!element._view.skip) { + handler(element); + } + } + } + } + + /** + * Helper function to get the items that intersect the event position + * @param items {ChartElement[]} elements to filter + * @param position {Point} 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 position {Point} the point to be nearest to + * @param intersect {Boolean} if true, only consider items that intersect the position + * @param distanceMetric {Function} 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.data.datasets.forEach(function (dataset, datasetIndex) { + if (chart.isDatasetVisible(datasetIndex)) { + var meta = chart.getDatasetMeta(datasetIndex); + 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 + */ + module.exports = { + // 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 e {Event} the event we are find things at + * @param options {IInteractionOptions} 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 e {Event} the event we are find things at + * @param options {IInteractionOptions} 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 e {Event} 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 e {Event} the event we are find things at + * @param options {IInteractionOptions} 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); + var nearestItems = getNearestItems(chart, position, options.intersect, distanceMetric); + + // We have multiple items at the same distance from the event. Now sort by smallest + if (nearestItems.length > 1) { + nearestItems.sort(function (a, b) { + var sizeA = a.getArea(); + var sizeB = b.getArea(); + var ret = sizeA - sizeB; + + if (ret === 0) { + // if equal sort by dataset index + ret = a._datasetIndex - b._datasetIndex; + } + + return ret; + }); + } + + // Return only 1 item + return nearestItems.slice(0, 1); + }, + + /** + * 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 e {Event} the event we are find things at + * @param options {IInteractionOptions} 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 e {Event} the event we are find things at + * @param options {IInteractionOptions} 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; + } + } + }; + + }, { "45": 45 }], 29: [function (require, module, exports) { + 'use strict'; + + var defaults = require(25); + + defaults._set('global', { + responsive: true, + responsiveAnimationDuration: 0, + maintainAspectRatio: true, + events: ['mousemove', 'mouseout', 'click', 'touchstart', 'touchmove'], + hover: { + onHover: null, + mode: 'nearest', + intersect: true, + animationDuration: 400 + }, + onClick: null, + defaultColor: 'rgba(0,0,0,0.1)', + defaultFontColor: '#666', + defaultFontFamily: "'Helvetica Neue', 'Helvetica', 'Arial', sans-serif", + defaultFontSize: 12, + defaultFontStyle: 'normal', + showLines: true, + + // Element defaults defined in element extensions + elements: {}, + + // Layout options such as padding + layout: { + padding: { + top: 0, + right: 0, + bottom: 0, + left: 0 + } + } + }); + + module.exports = function () { + + // Occupy the global variable of Chart, and create a simple base class + var Chart = function (item, config) { + this.construct(item, config); + return this; + }; + + Chart.Chart = Chart; + + return Chart; + }; + + }, { "25": 25 }], 30: [function (require, module, exports) { + 'use strict'; + + var helpers = require(45); + + function filterByPosition(array, position) { + return helpers.where(array, function (v) { + return v.position === position; + }); + } + + function sortByWeight(array, reverse) { + array.forEach(function (v, i) { + v._tmpIndex_ = i; + return v; + }); + array.sort(function (a, b) { + var v0 = reverse ? b : a; + var v1 = reverse ? a : b; + return v0.weight === v1.weight ? + v0._tmpIndex_ - v1._tmpIndex_ : + v0.weight - v1.weight; + }); + array.forEach(function (v) { + delete v._tmpIndex_; + }); + } + + /** + * @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. + module.exports = { + 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; + + chart.boxes.push(item); + }, + + /** + * Remove a layoutItem from a chart + * @param {Chart} chart - the chart to remove the box from + * @param {Object} 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 {Object} 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.options.toPadding(layoutOptions.padding); + var leftPadding = padding.left; + var rightPadding = padding.right; + var topPadding = padding.top; + var bottomPadding = padding.bottom; + + var leftBoxes = filterByPosition(chart.boxes, 'left'); + var rightBoxes = filterByPosition(chart.boxes, 'right'); + var topBoxes = filterByPosition(chart.boxes, 'top'); + var bottomBoxes = filterByPosition(chart.boxes, 'bottom'); + var chartAreaBoxes = filterByPosition(chart.boxes, 'chartArea'); + + // Sort boxes by weight. A higher weight is further away from the chart area + sortByWeight(leftBoxes, true); + sortByWeight(rightBoxes, false); + sortByWeight(topBoxes, true); + sortByWeight(bottomBoxes, false); + + // 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) | + // |----------------------------------------------------| + // + // What we do to find the best sizing, we do the following + // 1. Determine the minimum size of the chart area. + // 2. Split the remaining width equally between each vertical axis + // 3. Split the remaining height equally between each horizontal axis + // 4. Give each layout the maximum size it can be. The layout will return it's minimum size + // 5. Adjust the sizes of each axis based on it's minimum reported size. + // 6. Refit each axis + // 7. Position each axis in the final location + // 8. Tell the chart the final location of the chart area + // 9. Tell any axes that overlay the chart area the positions of the chart area + + // Step 1 + var chartWidth = width - leftPadding - rightPadding; + var chartHeight = height - topPadding - bottomPadding; + var chartAreaWidth = chartWidth / 2; // min 50% + var chartAreaHeight = chartHeight / 2; // min 50% + + // Step 2 + var verticalBoxWidth = (width - chartAreaWidth) / (leftBoxes.length + rightBoxes.length); + + // Step 3 + var horizontalBoxHeight = (height - chartAreaHeight) / (topBoxes.length + bottomBoxes.length); + + // Step 4 + var maxChartAreaWidth = chartWidth; + var maxChartAreaHeight = chartHeight; + var minBoxSizes = []; + + function getMinimumBoxSize(box) { + var minSize; + var isHorizontal = box.isHorizontal(); + + if (isHorizontal) { + minSize = box.update(box.fullWidth ? chartWidth : maxChartAreaWidth, horizontalBoxHeight); + maxChartAreaHeight -= minSize.height; + } else { + minSize = box.update(verticalBoxWidth, maxChartAreaHeight); + maxChartAreaWidth -= minSize.width; + } + + minBoxSizes.push({ + horizontal: isHorizontal, + minSize: minSize, + box: box, + }); + } + + helpers.each(leftBoxes.concat(rightBoxes, topBoxes, bottomBoxes), getMinimumBoxSize); + + // If a horizontal box has padding, we move the left boxes over to avoid ugly charts (see issue #2478) + var maxHorizontalLeftPadding = 0; + var maxHorizontalRightPadding = 0; + var maxVerticalTopPadding = 0; + var maxVerticalBottomPadding = 0; + + helpers.each(topBoxes.concat(bottomBoxes), function (horizontalBox) { + if (horizontalBox.getPadding) { + var boxPadding = horizontalBox.getPadding(); + maxHorizontalLeftPadding = Math.max(maxHorizontalLeftPadding, boxPadding.left); + maxHorizontalRightPadding = Math.max(maxHorizontalRightPadding, boxPadding.right); + } + }); + + helpers.each(leftBoxes.concat(rightBoxes), function (verticalBox) { + if (verticalBox.getPadding) { + var boxPadding = verticalBox.getPadding(); + maxVerticalTopPadding = Math.max(maxVerticalTopPadding, boxPadding.top); + maxVerticalBottomPadding = Math.max(maxVerticalBottomPadding, boxPadding.bottom); + } + }); + + // At this point, maxChartAreaHeight and maxChartAreaWidth are the size the chart area could + // be if the axes are drawn at their minimum sizes. + // Steps 5 & 6 + var totalLeftBoxesWidth = leftPadding; + var totalRightBoxesWidth = rightPadding; + var totalTopBoxesHeight = topPadding; + var totalBottomBoxesHeight = bottomPadding; + + // Function to fit a box + function fitBox(box) { + var minBoxSize = helpers.findNextWhere(minBoxSizes, function (minBox) { + return minBox.box === box; + }); + + if (minBoxSize) { + if (box.isHorizontal()) { + var scaleMargin = { + left: Math.max(totalLeftBoxesWidth, maxHorizontalLeftPadding), + right: Math.max(totalRightBoxesWidth, maxHorizontalRightPadding), + top: 0, + bottom: 0 + }; + + // Don't use min size here because of label rotation. When the labels are rotated, their rotation highly depends + // on the margin. Sometimes they need to increase in size slightly + box.update(box.fullWidth ? chartWidth : maxChartAreaWidth, chartHeight / 2, scaleMargin); + } else { + box.update(minBoxSize.minSize.width, maxChartAreaHeight); + } + } + } + + // Update, and calculate the left and right margins for the horizontal boxes + helpers.each(leftBoxes.concat(rightBoxes), fitBox); + + helpers.each(leftBoxes, function (box) { + totalLeftBoxesWidth += box.width; + }); + + helpers.each(rightBoxes, function (box) { + totalRightBoxesWidth += box.width; + }); + + // Set the Left and Right margins for the horizontal boxes + helpers.each(topBoxes.concat(bottomBoxes), fitBox); + + // Figure out how much margin is on the top and bottom of the vertical boxes + helpers.each(topBoxes, function (box) { + totalTopBoxesHeight += box.height; + }); + + helpers.each(bottomBoxes, function (box) { + totalBottomBoxesHeight += box.height; + }); + + function finalFitVerticalBox(box) { + var minBoxSize = helpers.findNextWhere(minBoxSizes, function (minSize) { + return minSize.box === box; + }); + + var scaleMargin = { + left: 0, + right: 0, + top: totalTopBoxesHeight, + bottom: totalBottomBoxesHeight + }; + + if (minBoxSize) { + box.update(minBoxSize.minSize.width, maxChartAreaHeight, scaleMargin); + } + } + + // Let the left layout know the final margin + helpers.each(leftBoxes.concat(rightBoxes), finalFitVerticalBox); + + // Recalculate because the size of each layout might have changed slightly due to the margins (label rotation for instance) + totalLeftBoxesWidth = leftPadding; + totalRightBoxesWidth = rightPadding; + totalTopBoxesHeight = topPadding; + totalBottomBoxesHeight = bottomPadding; + + helpers.each(leftBoxes, function (box) { + totalLeftBoxesWidth += box.width; + }); + + helpers.each(rightBoxes, function (box) { + totalRightBoxesWidth += box.width; + }); + + helpers.each(topBoxes, function (box) { + totalTopBoxesHeight += box.height; + }); + helpers.each(bottomBoxes, function (box) { + totalBottomBoxesHeight += box.height; + }); + + // We may be adding some padding to account for rotated x axis labels + var leftPaddingAddition = Math.max(maxHorizontalLeftPadding - totalLeftBoxesWidth, 0); + totalLeftBoxesWidth += leftPaddingAddition; + totalRightBoxesWidth += Math.max(maxHorizontalRightPadding - totalRightBoxesWidth, 0); + + var topPaddingAddition = Math.max(maxVerticalTopPadding - totalTopBoxesHeight, 0); + totalTopBoxesHeight += topPaddingAddition; + totalBottomBoxesHeight += Math.max(maxVerticalBottomPadding - totalBottomBoxesHeight, 0); + + // Figure out if our chart area changed. This would occur if the dataset layout label rotation + // changed due to the application of the margins in step 6. Since we can only get bigger, this is safe to do + // without calling `fit` again + var newMaxChartAreaHeight = height - totalTopBoxesHeight - totalBottomBoxesHeight; + var newMaxChartAreaWidth = width - totalLeftBoxesWidth - totalRightBoxesWidth; + + if (newMaxChartAreaWidth !== maxChartAreaWidth || newMaxChartAreaHeight !== maxChartAreaHeight) { + helpers.each(leftBoxes, function (box) { + box.height = newMaxChartAreaHeight; + }); + + helpers.each(rightBoxes, function (box) { + box.height = newMaxChartAreaHeight; + }); + + helpers.each(topBoxes, function (box) { + if (!box.fullWidth) { + box.width = newMaxChartAreaWidth; + } + }); + + helpers.each(bottomBoxes, function (box) { + if (!box.fullWidth) { + box.width = newMaxChartAreaWidth; + } + }); + + maxChartAreaHeight = newMaxChartAreaHeight; + maxChartAreaWidth = newMaxChartAreaWidth; + } + + // Step 7 - Position the boxes + var left = leftPadding + leftPaddingAddition; + var top = topPadding + topPaddingAddition; + + function placeBox(box) { + if (box.isHorizontal()) { + box.left = box.fullWidth ? leftPadding : totalLeftBoxesWidth; + box.right = box.fullWidth ? width - rightPadding : totalLeftBoxesWidth + maxChartAreaWidth; + box.top = top; + box.bottom = top + box.height; + + // Move to next point + top = box.bottom; + + } else { + + box.left = left; + box.right = left + box.width; + box.top = totalTopBoxesHeight; + box.bottom = totalTopBoxesHeight + maxChartAreaHeight; + + // Move to next point + left = box.right; + } + } + + helpers.each(leftBoxes.concat(topBoxes), placeBox); + + // Account for chart width and height + left += maxChartAreaWidth; + top += maxChartAreaHeight; + + helpers.each(rightBoxes, placeBox); + helpers.each(bottomBoxes, placeBox); + + // Step 8 + chart.chartArea = { + left: totalLeftBoxesWidth, + top: totalTopBoxesHeight, + right: totalLeftBoxesWidth + maxChartAreaWidth, + bottom: totalTopBoxesHeight + maxChartAreaHeight + }; + + // Step 9 + helpers.each(chartAreaBoxes, function (box) { + box.left = chart.chartArea.left; + box.top = chart.chartArea.top; + box.right = chart.chartArea.right; + box.bottom = chart.chartArea.bottom; + + box.update(maxChartAreaWidth, maxChartAreaHeight); + }); + } + }; + + }, { "45": 45 }], 31: [function (require, module, exports) { + 'use strict'; + + var defaults = require(25); + var helpers = require(45); + + defaults._set('global', { + plugins: {} + }); + + /** + * The plugin service singleton + * @namespace Chart.plugins + * @since 2.1.0 + */ + module.exports = { + /** + * Globally registered plugins. + * @private + */ + _plugins: [], + + /** + * This identifier is used to invalidate the descriptors cache attached to each chart + * when a global plugin is registered or unregistered. In this case, the cache ID is + * incremented and descriptors are regenerated during following API calls. + * @private + */ + _cacheId: 0, + + /** + * Registers the given plugin(s) if not already registered. + * @param {Array|Object} plugins plugin instance(s). + */ + register: function (plugins) { + var p = this._plugins; + ([]).concat(plugins).forEach(function (plugin) { + if (p.indexOf(plugin) === -1) { + p.push(plugin); + } + }); + + this._cacheId++; + }, + + /** + * Unregisters the given plugin(s) only if registered. + * @param {Array|Object} plugins plugin instance(s). + */ + unregister: function (plugins) { + var p = this._plugins; + ([]).concat(plugins).forEach(function (plugin) { + var idx = p.indexOf(plugin); + if (idx !== -1) { + p.splice(idx, 1); + } + }); + + this._cacheId++; + }, + + /** + * Remove all registered plugins. + * @since 2.1.5 + */ + clear: function () { + this._plugins = []; + this._cacheId++; + }, + + /** + * Returns the number of registered plugins? + * @returns {Number} + * @since 2.1.5 + */ + count: function () { + return this._plugins.length; + }, + + /** + * Returns all registered plugin instances. + * @returns {Array} array of plugin objects. + * @since 2.1.5 + */ + getAll: function () { + return this._plugins; + }, + + /** + * Calls enabled plugins for `chart` on the specified hook and with the given args. + * This method immediately returns as soon as a plugin explicitly returns false. The + * returned value can be used, for instance, to interrupt the current action. + * @param {Object} chart - The chart instance for which plugins should be called. + * @param {String} hook - The name of the plugin method to call (e.g. 'beforeUpdate'). + * @param {Array} [args] - Extra arguments to apply to the hook call. + * @returns {Boolean} false if any of the plugins return false, else returns true. + */ + notify: function (chart, hook, args) { + var descriptors = this.descriptors(chart); + var ilen = descriptors.length; + var i, descriptor, plugin, params, method; + + for (i = 0; i < ilen; ++i) { + descriptor = descriptors[i]; + plugin = descriptor.plugin; + method = plugin[hook]; + if (typeof method === 'function') { + params = [chart].concat(args || []); + params.push(descriptor.options); + if (method.apply(plugin, params) === false) { + return false; + } + } + } + + return true; + }, + + /** + * Returns descriptors of enabled plugins for the given chart. + * @returns {Array} [{ plugin, options }] + * @private + */ + descriptors: function (chart) { + var cache = chart.$plugins || (chart.$plugins = {}); + if (cache.id === this._cacheId) { + return cache.descriptors; + } + + var plugins = []; + var descriptors = []; + var config = (chart && chart.config) || {}; + var options = (config.options && config.options.plugins) || {}; + + this._plugins.concat(config.plugins || []).forEach(function (plugin) { + var idx = plugins.indexOf(plugin); + if (idx !== -1) { + return; + } + + var id = plugin.id; + var opts = options[id]; + if (opts === false) { + return; + } + + if (opts === true) { + opts = helpers.clone(defaults.global.plugins[id]); + } + + plugins.push(plugin); + descriptors.push({ + plugin: plugin, + options: opts || {} + }); + }); + + cache.descriptors = descriptors; + cache.id = this._cacheId; + return descriptors; + }, + + /** + * Invalidates cache for the given chart: descriptors hold a reference on plugin option, + * but in some cases, this reference can be changed by the user when updating options. + * https://github.com/chartjs/Chart.js/issues/5111#issuecomment-355934167 + * @private + */ + _invalidate: function (chart) { + delete chart.$plugins; + } + }; + + /** + * Plugin extension hooks. + * @interface IPlugin + * @since 2.1.0 + */ + /** + * @method IPlugin#beforeInit + * @desc Called before initializing `chart`. + * @param {Chart.Controller} chart - The chart instance. + * @param {Object} options - The plugin options. + */ + /** + * @method IPlugin#afterInit + * @desc Called after `chart` has been initialized and before the first update. + * @param {Chart.Controller} chart - The chart instance. + * @param {Object} options - The plugin options. + */ + /** + * @method IPlugin#beforeUpdate + * @desc Called before updating `chart`. If any plugin returns `false`, the update + * is cancelled (and thus subsequent render(s)) until another `update` is triggered. + * @param {Chart.Controller} chart - The chart instance. + * @param {Object} options - The plugin options. + * @returns {Boolean} `false` to cancel the chart update. + */ + /** + * @method IPlugin#afterUpdate + * @desc Called after `chart` has been updated and before rendering. Note that this + * hook will not be called if the chart update has been previously cancelled. + * @param {Chart.Controller} chart - The chart instance. + * @param {Object} options - The plugin options. + */ + /** + * @method IPlugin#beforeDatasetsUpdate + * @desc Called before updating the `chart` datasets. If any plugin returns `false`, + * the datasets update is cancelled until another `update` is triggered. + * @param {Chart.Controller} chart - The chart instance. + * @param {Object} options - The plugin options. + * @returns {Boolean} false to cancel the datasets update. + * @since version 2.1.5 + */ + /** + * @method IPlugin#afterDatasetsUpdate + * @desc Called after the `chart` datasets have been updated. Note that this hook + * will not be called if the datasets update has been previously cancelled. + * @param {Chart.Controller} chart - The chart instance. + * @param {Object} options - The plugin options. + * @since version 2.1.5 + */ + /** + * @method IPlugin#beforeDatasetUpdate + * @desc Called before updating the `chart` dataset at the given `args.index`. If any plugin + * returns `false`, the datasets update is cancelled until another `update` is triggered. + * @param {Chart} chart - The chart instance. + * @param {Object} args - The call arguments. + * @param {Number} args.index - The dataset index. + * @param {Object} args.meta - The dataset metadata. + * @param {Object} options - The plugin options. + * @returns {Boolean} `false` to cancel the chart datasets drawing. + */ + /** + * @method IPlugin#afterDatasetUpdate + * @desc Called after the `chart` datasets at the given `args.index` has been updated. Note + * that this hook will not be called if the datasets update has been previously cancelled. + * @param {Chart} chart - The chart instance. + * @param {Object} args - The call arguments. + * @param {Number} args.index - The dataset index. + * @param {Object} args.meta - The dataset metadata. + * @param {Object} options - The plugin options. + */ + /** + * @method IPlugin#beforeLayout + * @desc Called before laying out `chart`. If any plugin returns `false`, + * the layout update is cancelled until another `update` is triggered. + * @param {Chart.Controller} chart - The chart instance. + * @param {Object} options - The plugin options. + * @returns {Boolean} `false` to cancel the chart layout. + */ + /** + * @method IPlugin#afterLayout + * @desc Called after the `chart` has been layed out. Note that this hook will not + * be called if the layout update has been previously cancelled. + * @param {Chart.Controller} chart - The chart instance. + * @param {Object} options - The plugin options. + */ + /** + * @method IPlugin#beforeRender + * @desc Called before rendering `chart`. If any plugin returns `false`, + * the rendering is cancelled until another `render` is triggered. + * @param {Chart.Controller} chart - The chart instance. + * @param {Object} options - The plugin options. + * @returns {Boolean} `false` to cancel the chart rendering. + */ + /** + * @method IPlugin#afterRender + * @desc Called after the `chart` has been fully rendered (and animation completed). Note + * that this hook will not be called if the rendering has been previously cancelled. + * @param {Chart.Controller} chart - The chart instance. + * @param {Object} options - The plugin options. + */ + /** + * @method IPlugin#beforeDraw + * @desc Called before drawing `chart` at every animation frame specified by the given + * easing value. If any plugin returns `false`, the frame drawing is cancelled until + * another `render` is triggered. + * @param {Chart.Controller} chart - The chart instance. + * @param {Number} easingValue - The current animation value, between 0.0 and 1.0. + * @param {Object} options - The plugin options. + * @returns {Boolean} `false` to cancel the chart drawing. + */ + /** + * @method IPlugin#afterDraw + * @desc Called after the `chart` has been drawn for the specific easing value. Note + * that this hook will not be called if the drawing has been previously cancelled. + * @param {Chart.Controller} chart - The chart instance. + * @param {Number} easingValue - The current animation value, between 0.0 and 1.0. + * @param {Object} options - The plugin options. + */ + /** + * @method IPlugin#beforeDatasetsDraw + * @desc Called before drawing the `chart` datasets. If any plugin returns `false`, + * the datasets drawing is cancelled until another `render` is triggered. + * @param {Chart.Controller} chart - The chart instance. + * @param {Number} easingValue - The current animation value, between 0.0 and 1.0. + * @param {Object} options - The plugin options. + * @returns {Boolean} `false` to cancel the chart datasets drawing. + */ + /** + * @method IPlugin#afterDatasetsDraw + * @desc Called after the `chart` datasets have been drawn. Note that this hook + * will not be called if the datasets drawing has been previously cancelled. + * @param {Chart.Controller} chart - The chart instance. + * @param {Number} easingValue - The current animation value, between 0.0 and 1.0. + * @param {Object} options - The plugin options. + */ + /** + * @method IPlugin#beforeDatasetDraw + * @desc Called before drawing the `chart` dataset at the given `args.index` (datasets + * are drawn in the reverse order). If any plugin returns `false`, the datasets drawing + * is cancelled until another `render` is triggered. + * @param {Chart} chart - The chart instance. + * @param {Object} args - The call arguments. + * @param {Number} args.index - The dataset index. + * @param {Object} args.meta - The dataset metadata. + * @param {Number} args.easingValue - The current animation value, between 0.0 and 1.0. + * @param {Object} options - The plugin options. + * @returns {Boolean} `false` to cancel the chart datasets drawing. + */ + /** + * @method IPlugin#afterDatasetDraw + * @desc Called after the `chart` datasets at the given `args.index` have been drawn + * (datasets are drawn in the reverse order). Note that this hook will not be called + * if the datasets drawing has been previously cancelled. + * @param {Chart} chart - The chart instance. + * @param {Object} args - The call arguments. + * @param {Number} args.index - The dataset index. + * @param {Object} args.meta - The dataset metadata. + * @param {Number} args.easingValue - The current animation value, between 0.0 and 1.0. + * @param {Object} options - The plugin options. + */ + /** + * @method IPlugin#beforeTooltipDraw + * @desc Called before drawing the `tooltip`. If any plugin returns `false`, + * the tooltip drawing is cancelled until another `render` is triggered. + * @param {Chart} chart - The chart instance. + * @param {Object} args - The call arguments. + * @param {Object} args.tooltip - The tooltip. + * @param {Number} args.easingValue - The current animation value, between 0.0 and 1.0. + * @param {Object} options - The plugin options. + * @returns {Boolean} `false` to cancel the chart tooltip drawing. + */ + /** + * @method IPlugin#afterTooltipDraw + * @desc Called after drawing the `tooltip`. Note that this hook will not + * be called if the tooltip drawing has been previously cancelled. + * @param {Chart} chart - The chart instance. + * @param {Object} args - The call arguments. + * @param {Object} args.tooltip - The tooltip. + * @param {Number} args.easingValue - The current animation value, between 0.0 and 1.0. + * @param {Object} options - The plugin options. + */ + /** + * @method IPlugin#beforeEvent + * @desc Called before processing the specified `event`. If any plugin returns `false`, + * the event will be discarded. + * @param {Chart.Controller} chart - The chart instance. + * @param {IEvent} event - The event object. + * @param {Object} options - The plugin options. + */ + /** + * @method IPlugin#afterEvent + * @desc Called after the `event` has been consumed. Note that this hook + * will not be called if the `event` has been previously discarded. + * @param {Chart.Controller} chart - The chart instance. + * @param {IEvent} event - The event object. + * @param {Object} options - The plugin options. + */ + /** + * @method IPlugin#resize + * @desc Called after the chart as been resized. + * @param {Chart.Controller} chart - The chart instance. + * @param {Number} size - The new canvas display size (eq. canvas.style width & height). + * @param {Object} options - The plugin options. + */ + /** + * @method IPlugin#destroy + * @desc Called after the chart as been destroyed. + * @param {Chart.Controller} chart - The chart instance. + * @param {Object} options - The plugin options. + */ + + }, { "25": 25, "45": 45 }], 32: [function (require, module, exports) { + 'use strict'; + + var defaults = require(25); + var Element = require(26); + var helpers = require(45); + var Ticks = require(34); + + defaults._set('scale', { + display: true, + position: 'left', + offset: false, + + // grid line settings + gridLines: { + display: true, + color: 'rgba(0, 0, 0, 0.1)', + lineWidth: 1, + drawBorder: true, + drawOnChartArea: true, + drawTicks: true, + tickMarkLength: 10, + zeroLineWidth: 1, + zeroLineColor: 'rgba(0,0,0,0.25)', + zeroLineBorderDash: [], + zeroLineBorderDashOffset: 0.0, + offsetGridLines: false, + borderDash: [], + borderDashOffset: 0.0 + }, + + // scale label + scaleLabel: { + // display property + display: false, + + // actual label + labelString: '', + + // line height + lineHeight: 1.2, + + // top/bottom padding + padding: { + top: 4, + bottom: 4 + } + }, + + // label settings + ticks: { + beginAtZero: false, + minRotation: 0, + maxRotation: 50, + mirror: false, + padding: 0, + reverse: false, + display: true, + autoSkip: true, + autoSkipPadding: 0, + labelOffset: 0, + // We pass through arrays to be rendered as multiline labels, we convert Others to strings here. + callback: Ticks.formatters.values, + minor: {}, + major: {} + } + }); + + function labelsFromTicks(ticks) { + var labels = []; + var i, ilen; + + for (i = 0, ilen = ticks.length; i < ilen; ++i) { + labels.push(ticks[i].label); + } + + return labels; + } + + function getLineValue(scale, index, offsetGridLines) { + var lineValue = scale.getPixelForTick(index); + + if (offsetGridLines) { + if (index === 0) { + lineValue -= (scale.getPixelForTick(1) - lineValue) / 2; + } else { + lineValue -= (lineValue - scale.getPixelForTick(index - 1)) / 2; + } + } + return lineValue; + } + + module.exports = function (Chart) { + + function computeTextSize(context, tick, font) { + return helpers.isArray(tick) ? + helpers.longestText(context, font, tick) : + context.measureText(tick).width; + } + + function parseFontOptions(options) { + var valueOrDefault = helpers.valueOrDefault; + var globalDefaults = defaults.global; + var size = valueOrDefault(options.fontSize, globalDefaults.defaultFontSize); + var style = valueOrDefault(options.fontStyle, globalDefaults.defaultFontStyle); + var family = valueOrDefault(options.fontFamily, globalDefaults.defaultFontFamily); + + return { + size: size, + style: style, + family: family, + font: helpers.fontString(size, style, family) + }; + } + + function parseLineHeight(options) { + return helpers.options.toLineHeight( + helpers.valueOrDefault(options.lineHeight, 1.2), + helpers.valueOrDefault(options.fontSize, defaults.global.defaultFontSize)); + } + + Chart.Scale = Element.extend({ + /** + * Get the padding needed for the scale + * @method getPadding + * @private + * @returns {Padding} the necessary padding + */ + getPadding: function () { + var me = this; + return { + left: me.paddingLeft || 0, + top: me.paddingTop || 0, + right: me.paddingRight || 0, + bottom: me.paddingBottom || 0 + }; + }, + + /** + * Returns the scale tick objects ({label, major}) + * @since 2.7 + */ + getTicks: function () { + return this._ticks; + }, + + // These methods are ordered by lifecyle. Utilities then follow. + // Any function defined here is inherited by all scale types. + // Any function can be extended by the scale type + + mergeTicksOptions: function () { + var ticks = this.options.ticks; + if (ticks.minor === false) { + ticks.minor = { + display: false + }; + } + if (ticks.major === false) { + ticks.major = { + display: false + }; + } + for (var key in ticks) { + if (key !== 'major' && key !== 'minor') { + if (typeof ticks.minor[key] === 'undefined') { + ticks.minor[key] = ticks[key]; + } + if (typeof ticks.major[key] === 'undefined') { + ticks.major[key] = ticks[key]; + } + } + } + }, + beforeUpdate: function () { + helpers.callback(this.options.beforeUpdate, [this]); + }, + update: function (maxWidth, maxHeight, margins) { + var me = this; + var i, ilen, labels, label, ticks, tick; + + // Update Lifecycle - Probably don't want to ever extend or overwrite this function ;) + me.beforeUpdate(); + + // Absorb the master measurements + me.maxWidth = maxWidth; + me.maxHeight = maxHeight; + me.margins = helpers.extend({ + left: 0, + right: 0, + top: 0, + bottom: 0 + }, margins); + me.longestTextCache = me.longestTextCache || {}; + + // Dimensions + me.beforeSetDimensions(); + me.setDimensions(); + me.afterSetDimensions(); + + // Data min/max + me.beforeDataLimits(); + me.determineDataLimits(); + me.afterDataLimits(); + + // Ticks - `this.ticks` is now DEPRECATED! + // Internal ticks are now stored as objects in the PRIVATE `this._ticks` member + // and must not be accessed directly from outside this class. `this.ticks` being + // around for long time and not marked as private, we can't change its structure + // without unexpected breaking changes. If you need to access the scale ticks, + // use scale.getTicks() instead. + + me.beforeBuildTicks(); + + // New implementations should return an array of objects but for BACKWARD COMPAT, + // we still support no return (`this.ticks` internally set by calling this method). + ticks = me.buildTicks() || []; + + me.afterBuildTicks(); + + me.beforeTickToLabelConversion(); + + // New implementations should return the formatted tick labels but for BACKWARD + // COMPAT, we still support no return (`this.ticks` internally changed by calling + // this method and supposed to contain only string values). + labels = me.convertTicksToLabels(ticks) || me.ticks; + + me.afterTickToLabelConversion(); + + me.ticks = labels; // BACKWARD COMPATIBILITY + + // IMPORTANT: from this point, we consider that `this.ticks` will NEVER change! + + // BACKWARD COMPAT: synchronize `_ticks` with labels (so potentially `this.ticks`) + for (i = 0, ilen = labels.length; i < ilen; ++i) { + label = labels[i]; + tick = ticks[i]; + if (!tick) { + ticks.push(tick = { + label: label, + major: false + }); + } else { + tick.label = label; + } + } + + me._ticks = ticks; + + // Tick Rotation + me.beforeCalculateTickRotation(); + me.calculateTickRotation(); + me.afterCalculateTickRotation(); + // Fit + me.beforeFit(); + me.fit(); + me.afterFit(); + // + me.afterUpdate(); + + return me.minSize; + + }, + afterUpdate: function () { + helpers.callback(this.options.afterUpdate, [this]); + }, + + // + + beforeSetDimensions: function () { + helpers.callback(this.options.beforeSetDimensions, [this]); + }, + setDimensions: function () { + var me = this; + // Set the unconstrained dimension before label rotation + if (me.isHorizontal()) { + // Reset position before calculating rotation + me.width = me.maxWidth; + me.left = 0; + me.right = me.width; + } else { + me.height = me.maxHeight; + + // Reset position before calculating rotation + me.top = 0; + me.bottom = me.height; + } + + // Reset padding + me.paddingLeft = 0; + me.paddingTop = 0; + me.paddingRight = 0; + me.paddingBottom = 0; + }, + afterSetDimensions: function () { + helpers.callback(this.options.afterSetDimensions, [this]); + }, + + // Data limits + beforeDataLimits: function () { + helpers.callback(this.options.beforeDataLimits, [this]); + }, + determineDataLimits: helpers.noop, + afterDataLimits: function () { + helpers.callback(this.options.afterDataLimits, [this]); + }, + + // + beforeBuildTicks: function () { + helpers.callback(this.options.beforeBuildTicks, [this]); + }, + buildTicks: helpers.noop, + afterBuildTicks: function () { + helpers.callback(this.options.afterBuildTicks, [this]); + }, + + beforeTickToLabelConversion: function () { + helpers.callback(this.options.beforeTickToLabelConversion, [this]); + }, + convertTicksToLabels: function () { + var me = this; + // Convert ticks to strings + var tickOpts = me.options.ticks; + me.ticks = me.ticks.map(tickOpts.userCallback || tickOpts.callback, this); + }, + afterTickToLabelConversion: function () { + helpers.callback(this.options.afterTickToLabelConversion, [this]); + }, + + // + + beforeCalculateTickRotation: function () { + helpers.callback(this.options.beforeCalculateTickRotation, [this]); + }, + calculateTickRotation: function () { + var me = this; + var context = me.ctx; + var tickOpts = me.options.ticks; + var labels = labelsFromTicks(me._ticks); + + // Get the width of each grid by calculating the difference + // between x offsets between 0 and 1. + var tickFont = parseFontOptions(tickOpts); + context.font = tickFont.font; + + var labelRotation = tickOpts.minRotation || 0; + + if (labels.length && me.options.display && me.isHorizontal()) { + var originalLabelWidth = helpers.longestText(context, tickFont.font, labels, me.longestTextCache); + var labelWidth = originalLabelWidth; + var cosRotation, sinRotation; + + // Allow 3 pixels x2 padding either side for label readability + var tickWidth = me.getPixelForTick(1) - me.getPixelForTick(0) - 6; + + // Max label rotation can be set or default to 90 - also act as a loop counter + while (labelWidth > tickWidth && labelRotation < tickOpts.maxRotation) { + var angleRadians = helpers.toRadians(labelRotation); + cosRotation = Math.cos(angleRadians); + sinRotation = Math.sin(angleRadians); + + if (sinRotation * originalLabelWidth > me.maxHeight) { + // go back one step + labelRotation--; + break; + } + + labelRotation++; + labelWidth = cosRotation * originalLabelWidth; + } + } + + me.labelRotation = labelRotation; + }, + afterCalculateTickRotation: function () { + helpers.callback(this.options.afterCalculateTickRotation, [this]); + }, + + // + + beforeFit: function () { + helpers.callback(this.options.beforeFit, [this]); + }, + fit: function () { + var me = this; + // Reset + var minSize = me.minSize = { + width: 0, + height: 0 + }; + + var labels = labelsFromTicks(me._ticks); + + var opts = me.options; + var tickOpts = opts.ticks; + var scaleLabelOpts = opts.scaleLabel; + var gridLineOpts = opts.gridLines; + var display = opts.display; + var isHorizontal = me.isHorizontal(); + + var tickFont = parseFontOptions(tickOpts); + var tickMarkLength = opts.gridLines.tickMarkLength; + + // Width + if (isHorizontal) { + // subtract the margins to line up with the chartArea if we are a full width scale + minSize.width = me.isFullWidth() ? me.maxWidth - me.margins.left - me.margins.right : me.maxWidth; + } else { + minSize.width = display && gridLineOpts.drawTicks ? tickMarkLength : 0; + } + + // height + if (isHorizontal) { + minSize.height = display && gridLineOpts.drawTicks ? tickMarkLength : 0; + } else { + minSize.height = me.maxHeight; // fill all the height + } + + // Are we showing a title for the scale? + if (scaleLabelOpts.display && display) { + var scaleLabelLineHeight = parseLineHeight(scaleLabelOpts); + var scaleLabelPadding = helpers.options.toPadding(scaleLabelOpts.padding); + var deltaHeight = scaleLabelLineHeight + scaleLabelPadding.height; + + if (isHorizontal) { + minSize.height += deltaHeight; + } else { + minSize.width += deltaHeight; + } + } + + // Don't bother fitting the ticks if we are not showing them + if (tickOpts.display && display) { + var largestTextWidth = helpers.longestText(me.ctx, tickFont.font, labels, me.longestTextCache); + var tallestLabelHeightInLines = helpers.numberOfLabelLines(labels); + var lineSpace = tickFont.size * 0.5; + var tickPadding = me.options.ticks.padding; + + if (isHorizontal) { + // A horizontal axis is more constrained by the height. + me.longestLabelWidth = largestTextWidth; + + var angleRadians = helpers.toRadians(me.labelRotation); + var cosRotation = Math.cos(angleRadians); + var sinRotation = Math.sin(angleRadians); + + // TODO - improve this calculation + var labelHeight = (sinRotation * largestTextWidth) + + (tickFont.size * tallestLabelHeightInLines) + + (lineSpace * (tallestLabelHeightInLines - 1)) + + lineSpace; // padding + + minSize.height = Math.min(me.maxHeight, minSize.height + labelHeight + tickPadding); + + me.ctx.font = tickFont.font; + var firstLabelWidth = computeTextSize(me.ctx, labels[0], tickFont.font); + var lastLabelWidth = computeTextSize(me.ctx, labels[labels.length - 1], tickFont.font); + + // Ensure that our ticks are always inside the canvas. When rotated, ticks are right aligned + // which means that the right padding is dominated by the font height + if (me.labelRotation !== 0) { + me.paddingLeft = opts.position === 'bottom' ? (cosRotation * firstLabelWidth) + 3 : (cosRotation * lineSpace) + 3; // add 3 px to move away from canvas edges + me.paddingRight = opts.position === 'bottom' ? (cosRotation * lineSpace) + 3 : (cosRotation * lastLabelWidth) + 3; + } else { + me.paddingLeft = firstLabelWidth / 2 + 3; // add 3 px to move away from canvas edges + me.paddingRight = lastLabelWidth / 2 + 3; + } + } else { + // A vertical axis is more constrained by the width. Labels are the + // dominant factor here, so get that length first and account for padding + if (tickOpts.mirror) { + largestTextWidth = 0; + } else { + // use lineSpace for consistency with horizontal axis + // tickPadding is not implemented for horizontal + largestTextWidth += tickPadding + lineSpace; + } + + minSize.width = Math.min(me.maxWidth, minSize.width + largestTextWidth); + + me.paddingTop = tickFont.size / 2; + me.paddingBottom = tickFont.size / 2; + } + } + + me.handleMargins(); + + me.width = minSize.width; + me.height = minSize.height; + }, + + /** + * Handle margins and padding interactions + * @private + */ + handleMargins: function () { + var me = this; + if (me.margins) { + me.paddingLeft = Math.max(me.paddingLeft - me.margins.left, 0); + me.paddingTop = Math.max(me.paddingTop - me.margins.top, 0); + me.paddingRight = Math.max(me.paddingRight - me.margins.right, 0); + me.paddingBottom = Math.max(me.paddingBottom - me.margins.bottom, 0); + } + }, + + afterFit: function () { + helpers.callback(this.options.afterFit, [this]); + }, + + // Shared Methods + isHorizontal: function () { + return this.options.position === 'top' || this.options.position === 'bottom'; + }, + isFullWidth: function () { + return (this.options.fullWidth); + }, + + // Get the correct value. NaN bad inputs, If the value type is object get the x or y based on whether we are horizontal or not + getRightValue: function (rawValue) { + // Null and undefined values first + if (helpers.isNullOrUndef(rawValue)) { + return NaN; + } + // isNaN(object) returns true, so make sure NaN is checking for a number; Discard Infinite values + if (typeof rawValue === 'number' && !isFinite(rawValue)) { + return NaN; + } + // If it is in fact an object, dive in one more level + if (rawValue) { + if (this.isHorizontal()) { + if (rawValue.x !== undefined) { + return this.getRightValue(rawValue.x); + } + } else if (rawValue.y !== undefined) { + return this.getRightValue(rawValue.y); + } + } + + // Value is good, return it + return rawValue; + }, + + /** + * Used to get the value to display in the tooltip for the data at the given index + * @param index + * @param datasetIndex + */ + getLabelForIndex: helpers.noop, + + /** + * Returns the location of the given data point. Value can either be an index or a numerical value + * The coordinate (0, 0) is at the upper-left corner of the canvas + * @param value + * @param index + * @param datasetIndex + */ + getPixelForValue: helpers.noop, + + /** + * Used to get the data value from a given pixel. This is the inverse of getPixelForValue + * The coordinate (0, 0) is at the upper-left corner of the canvas + * @param pixel + */ + getValueForPixel: helpers.noop, + + /** + * Returns the location of the tick at the given index + * The coordinate (0, 0) is at the upper-left corner of the canvas + */ + getPixelForTick: function (index) { + var me = this; + var offset = me.options.offset; + if (me.isHorizontal()) { + var innerWidth = me.width - (me.paddingLeft + me.paddingRight); + var tickWidth = innerWidth / Math.max((me._ticks.length - (offset ? 0 : 1)), 1); + var pixel = (tickWidth * index) + me.paddingLeft; + + if (offset) { + pixel += tickWidth / 2; + } + + var finalVal = me.left + Math.round(pixel); + finalVal += me.isFullWidth() ? me.margins.left : 0; + return finalVal; + } + var innerHeight = me.height - (me.paddingTop + me.paddingBottom); + return me.top + (index * (innerHeight / (me._ticks.length - 1))); + }, + + /** + * Utility for getting the pixel location of a percentage of scale + * The coordinate (0, 0) is at the upper-left corner of the canvas + */ + getPixelForDecimal: function (decimal) { + var me = this; + if (me.isHorizontal()) { + var innerWidth = me.width - (me.paddingLeft + me.paddingRight); + var valueOffset = (innerWidth * decimal) + me.paddingLeft; + + var finalVal = me.left + Math.round(valueOffset); + finalVal += me.isFullWidth() ? me.margins.left : 0; + return finalVal; + } + return me.top + (decimal * me.height); + }, + + /** + * Returns the pixel for the minimum chart value + * The coordinate (0, 0) is at the upper-left corner of the canvas + */ + getBasePixel: function () { + return this.getPixelForValue(this.getBaseValue()); + }, + + getBaseValue: function () { + var me = this; + var min = me.min; + var max = me.max; + + return me.beginAtZero ? 0 : + min < 0 && max < 0 ? max : + min > 0 && max > 0 ? min : + 0; + }, + + /** + * Returns a subset of ticks to be plotted to avoid overlapping labels. + * @private + */ + _autoSkip: function (ticks) { + var skipRatio; + var me = this; + var isHorizontal = me.isHorizontal(); + var optionTicks = me.options.ticks.minor; + var tickCount = ticks.length; + var labelRotationRadians = helpers.toRadians(me.labelRotation); + var cosRotation = Math.cos(labelRotationRadians); + var longestRotatedLabel = me.longestLabelWidth * cosRotation; + var result = []; + var i, tick, shouldSkip; + + // figure out the maximum number of gridlines to show + var maxTicks; + if (optionTicks.maxTicksLimit) { + maxTicks = optionTicks.maxTicksLimit; + } + + if (isHorizontal) { + skipRatio = false; + + if ((longestRotatedLabel + optionTicks.autoSkipPadding) * tickCount > (me.width - (me.paddingLeft + me.paddingRight))) { + skipRatio = 1 + Math.floor(((longestRotatedLabel + optionTicks.autoSkipPadding) * tickCount) / (me.width - (me.paddingLeft + me.paddingRight))); + } + + // if they defined a max number of optionTicks, + // increase skipRatio until that number is met + if (maxTicks && tickCount > maxTicks) { + skipRatio = Math.max(skipRatio, Math.floor(tickCount / maxTicks)); + } + } + + for (i = 0; i < tickCount; i++) { + tick = ticks[i]; + + // Since we always show the last tick,we need may need to hide the last shown one before + shouldSkip = (skipRatio > 1 && i % skipRatio > 0) || (i % skipRatio === 0 && i + skipRatio >= tickCount); + if (shouldSkip && i !== tickCount - 1) { + // leave tick in place but make sure it's not displayed (#4635) + delete tick.label; + } + result.push(tick); + } + return result; + }, + + // Actually draw the scale on the canvas + // @param {rectangle} chartArea : the area of the chart to draw full grid lines on + draw: function (chartArea) { + var me = this; + var options = me.options; + if (!options.display) { + return; + } + + var context = me.ctx; + var globalDefaults = defaults.global; + var optionTicks = options.ticks.minor; + var optionMajorTicks = options.ticks.major || optionTicks; + var gridLines = options.gridLines; + var scaleLabel = options.scaleLabel; + + var isRotated = me.labelRotation !== 0; + var isHorizontal = me.isHorizontal(); + + var ticks = optionTicks.autoSkip ? me._autoSkip(me.getTicks()) : me.getTicks(); + var tickFontColor = helpers.valueOrDefault(optionTicks.fontColor, globalDefaults.defaultFontColor); + var tickFont = parseFontOptions(optionTicks); + var majorTickFontColor = helpers.valueOrDefault(optionMajorTicks.fontColor, globalDefaults.defaultFontColor); + var majorTickFont = parseFontOptions(optionMajorTicks); + + var tl = gridLines.drawTicks ? gridLines.tickMarkLength : 0; + + var scaleLabelFontColor = helpers.valueOrDefault(scaleLabel.fontColor, globalDefaults.defaultFontColor); + var scaleLabelFont = parseFontOptions(scaleLabel); + var scaleLabelPadding = helpers.options.toPadding(scaleLabel.padding); + var labelRotationRadians = helpers.toRadians(me.labelRotation); + + var itemsToDraw = []; + + var axisWidth = me.options.gridLines.lineWidth; + var xTickStart = options.position === 'right' ? me.right : me.right - axisWidth - tl; + var xTickEnd = options.position === 'right' ? me.right + tl : me.right; + var yTickStart = options.position === 'bottom' ? me.top + axisWidth : me.bottom - tl - axisWidth; + var yTickEnd = options.position === 'bottom' ? me.top + axisWidth + tl : me.bottom + axisWidth; + + helpers.each(ticks, function (tick, index) { + // autoskipper skipped this tick (#4635) + if (helpers.isNullOrUndef(tick.label)) { + return; + } + + var label = tick.label; + var lineWidth, lineColor, borderDash, borderDashOffset; + if (index === me.zeroLineIndex && options.offset === gridLines.offsetGridLines) { + // Draw the first index specially + lineWidth = gridLines.zeroLineWidth; + lineColor = gridLines.zeroLineColor; + borderDash = gridLines.zeroLineBorderDash; + borderDashOffset = gridLines.zeroLineBorderDashOffset; + } else { + lineWidth = helpers.valueAtIndexOrDefault(gridLines.lineWidth, index); + lineColor = helpers.valueAtIndexOrDefault(gridLines.color, index); + borderDash = helpers.valueOrDefault(gridLines.borderDash, globalDefaults.borderDash); + borderDashOffset = helpers.valueOrDefault(gridLines.borderDashOffset, globalDefaults.borderDashOffset); + } + + // Common properties + var tx1, ty1, tx2, ty2, x1, y1, x2, y2, labelX, labelY; + var textAlign = 'middle'; + var textBaseline = 'middle'; + var tickPadding = optionTicks.padding; + + if (isHorizontal) { + var labelYOffset = tl + tickPadding; + + if (options.position === 'bottom') { + // bottom + textBaseline = !isRotated ? 'top' : 'middle'; + textAlign = !isRotated ? 'center' : 'right'; + labelY = me.top + labelYOffset; + } else { + // top + textBaseline = !isRotated ? 'bottom' : 'middle'; + textAlign = !isRotated ? 'center' : 'left'; + labelY = me.bottom - labelYOffset; + } + + var xLineValue = getLineValue(me, index, gridLines.offsetGridLines && ticks.length > 1); + if (xLineValue < me.left) { + lineColor = 'rgba(0,0,0,0)'; + } + xLineValue += helpers.aliasPixel(lineWidth); + + labelX = me.getPixelForTick(index) + optionTicks.labelOffset; // x values for optionTicks (need to consider offsetLabel option) + + tx1 = tx2 = x1 = x2 = xLineValue; + ty1 = yTickStart; + ty2 = yTickEnd; + y1 = chartArea.top; + y2 = chartArea.bottom + axisWidth; + } else { + var isLeft = options.position === 'left'; + var labelXOffset; + + if (optionTicks.mirror) { + textAlign = isLeft ? 'left' : 'right'; + labelXOffset = tickPadding; + } else { + textAlign = isLeft ? 'right' : 'left'; + labelXOffset = tl + tickPadding; + } + + labelX = isLeft ? me.right - labelXOffset : me.left + labelXOffset; + + var yLineValue = getLineValue(me, index, gridLines.offsetGridLines && ticks.length > 1); + if (yLineValue < me.top) { + lineColor = 'rgba(0,0,0,0)'; + } + yLineValue += helpers.aliasPixel(lineWidth); + + labelY = me.getPixelForTick(index) + optionTicks.labelOffset; + + tx1 = xTickStart; + tx2 = xTickEnd; + x1 = chartArea.left; + x2 = chartArea.right + axisWidth; + ty1 = ty2 = y1 = y2 = yLineValue; + } + + itemsToDraw.push({ + tx1: tx1, + ty1: ty1, + tx2: tx2, + ty2: ty2, + x1: x1, + y1: y1, + x2: x2, + y2: y2, + labelX: labelX, + labelY: labelY, + glWidth: lineWidth, + glColor: lineColor, + glBorderDash: borderDash, + glBorderDashOffset: borderDashOffset, + rotation: -1 * labelRotationRadians, + label: label, + major: tick.major, + textBaseline: textBaseline, + textAlign: textAlign + }); + }); + + // Draw all of the tick labels, tick marks, and grid lines at the correct places + helpers.each(itemsToDraw, function (itemToDraw) { + if (gridLines.display) { + context.save(); + context.lineWidth = itemToDraw.glWidth; + context.strokeStyle = itemToDraw.glColor; + if (context.setLineDash) { + context.setLineDash(itemToDraw.glBorderDash); + context.lineDashOffset = itemToDraw.glBorderDashOffset; + } + + context.beginPath(); + + if (gridLines.drawTicks) { + context.moveTo(itemToDraw.tx1, itemToDraw.ty1); + context.lineTo(itemToDraw.tx2, itemToDraw.ty2); + } + + if (gridLines.drawOnChartArea) { + context.moveTo(itemToDraw.x1, itemToDraw.y1); + context.lineTo(itemToDraw.x2, itemToDraw.y2); + } + + context.stroke(); + context.restore(); + } + + if (optionTicks.display) { + // Make sure we draw text in the correct color and font + context.save(); + context.translate(itemToDraw.labelX, itemToDraw.labelY); + context.rotate(itemToDraw.rotation); + context.font = itemToDraw.major ? majorTickFont.font : tickFont.font; + context.fillStyle = itemToDraw.major ? majorTickFontColor : tickFontColor; + context.textBaseline = itemToDraw.textBaseline; + context.textAlign = itemToDraw.textAlign; + + var label = itemToDraw.label; + if (helpers.isArray(label)) { + var lineCount = label.length; + var lineHeight = tickFont.size * 1.5; + var y = me.isHorizontal() ? 0 : -lineHeight * (lineCount - 1) / 2; + + for (var i = 0; i < lineCount; ++i) { + // We just make sure the multiline element is a string here.. + context.fillText('' + label[i], 0, y); + // apply same lineSpacing as calculated @ L#320 + y += lineHeight; + } + } else { + context.fillText(label, 0, 0); + } + context.restore(); + } + }); + + if (scaleLabel.display) { + // Draw the scale label + var scaleLabelX; + var scaleLabelY; + var rotation = 0; + var halfLineHeight = parseLineHeight(scaleLabel) / 2; + + if (isHorizontal) { + scaleLabelX = me.left + ((me.right - me.left) / 2); // midpoint of the width + scaleLabelY = options.position === 'bottom' + ? me.bottom - halfLineHeight - scaleLabelPadding.bottom + : me.top + halfLineHeight + scaleLabelPadding.top; + } else { + var isLeft = options.position === 'left'; + scaleLabelX = isLeft + ? me.left + halfLineHeight + scaleLabelPadding.top + : me.right - halfLineHeight - scaleLabelPadding.top; + scaleLabelY = me.top + ((me.bottom - me.top) / 2); + rotation = isLeft ? -0.5 * Math.PI : 0.5 * Math.PI; + } + + context.save(); + context.translate(scaleLabelX, scaleLabelY); + context.rotate(rotation); + context.textAlign = 'center'; + context.textBaseline = 'middle'; + context.fillStyle = scaleLabelFontColor; // render in correct colour + context.font = scaleLabelFont.font; + context.fillText(scaleLabel.labelString, 0, 0); + context.restore(); + } + + if (gridLines.drawBorder) { + // Draw the line at the edge of the axis + context.lineWidth = helpers.valueAtIndexOrDefault(gridLines.lineWidth, 0); + context.strokeStyle = helpers.valueAtIndexOrDefault(gridLines.color, 0); + var x1 = me.left; + var x2 = me.right + axisWidth; + var y1 = me.top; + var y2 = me.bottom + axisWidth; + + var aliasPixel = helpers.aliasPixel(context.lineWidth); + if (isHorizontal) { + y1 = y2 = options.position === 'top' ? me.bottom : me.top; + y1 += aliasPixel; + y2 += aliasPixel; + } else { + x1 = x2 = options.position === 'left' ? me.right : me.left; + x1 += aliasPixel; + x2 += aliasPixel; + } + + context.beginPath(); + context.moveTo(x1, y1); + context.lineTo(x2, y2); + context.stroke(); + } + } + }); + }; + + }, { "25": 25, "26": 26, "34": 34, "45": 45 }], 33: [function (require, module, exports) { + 'use strict'; + + var defaults = require(25); + var helpers = require(45); + var layouts = require(30); + + module.exports = function (Chart) { + + Chart.scaleService = { + // Scale registration object. Extensions can register new scale types (such as log or DB scales) and then + // use the new chart options to grab the correct scale + constructors: {}, + // Use a registration function so that we can move to an ES6 map when we no longer need to support + // old browsers + + // Scale config defaults + defaults: {}, + registerScaleType: function (type, scaleConstructor, scaleDefaults) { + this.constructors[type] = scaleConstructor; + this.defaults[type] = helpers.clone(scaleDefaults); + }, + getScaleConstructor: function (type) { + return this.constructors.hasOwnProperty(type) ? this.constructors[type] : undefined; + }, + getScaleDefaults: function (type) { + // Return the scale defaults merged with the global settings so that we always use the latest ones + return this.defaults.hasOwnProperty(type) ? helpers.merge({}, [defaults.scale, this.defaults[type]]) : {}; + }, + updateScaleDefaults: function (type, additions) { + var me = this; + if (me.defaults.hasOwnProperty(type)) { + me.defaults[type] = helpers.extend(me.defaults[type], additions); + } + }, + addScalesToLayout: function (chart) { + // Adds each scale to the chart.boxes array to be sized accordingly + helpers.each(chart.scales, function (scale) { + // Set ILayoutItem parameters for backwards compatibility + scale.fullWidth = scale.options.fullWidth; + scale.position = scale.options.position; + scale.weight = scale.options.weight; + layouts.addBox(chart, scale); + }); + } + }; + }; + + }, { "25": 25, "30": 30, "45": 45 }], 34: [function (require, module, exports) { + 'use strict'; + + var helpers = require(45); + + /** + * Namespace to hold static tick generation functions + * @namespace Chart.Ticks + */ + module.exports = { + /** + * Namespace to hold formatters for different types of ticks + * @namespace Chart.Ticks.formatters + */ + formatters: { + /** + * Formatter for value labels + * @method Chart.Ticks.formatters.values + * @param value the value to display + * @return {String|Array} the label to display + */ + values: function (value) { + return helpers.isArray(value) ? value : '' + value; + }, + + /** + * Formatter for linear numeric ticks + * @method Chart.Ticks.formatters.linear + * @param tickValue {Number} the value to be formatted + * @param index {Number} the position of the tickValue parameter in the ticks array + * @param ticks {Array} the list of ticks being converted + * @return {String} string representation of the tickValue parameter + */ + linear: function (tickValue, index, ticks) { + // If we have lots of ticks, don't use the ones + var delta = ticks.length > 3 ? ticks[2] - ticks[1] : ticks[1] - ticks[0]; + + // If we have a number like 2.5 as the delta, figure out how many decimal places we need + if (Math.abs(delta) > 1) { + if (tickValue !== Math.floor(tickValue)) { + // not an integer + delta = tickValue - Math.floor(tickValue); + } + } + + var logDelta = helpers.log10(Math.abs(delta)); + var tickString = ''; + + if (tickValue !== 0) { + var numDecimal = -1 * Math.floor(logDelta); + numDecimal = Math.max(Math.min(numDecimal, 20), 0); // toFixed has a max of 20 decimal places + tickString = tickValue.toFixed(numDecimal); + } else { + tickString = '0'; // never show decimal places for 0 + } + + return tickString; + }, + + logarithmic: function (tickValue, index, ticks) { + var remain = tickValue / (Math.pow(10, Math.floor(helpers.log10(tickValue)))); + + if (tickValue === 0) { + return '0'; + } else if (remain === 1 || remain === 2 || remain === 5 || index === 0 || index === ticks.length - 1) { + return tickValue.toExponential(); + } + return ''; + } + } + }; + + }, { "45": 45 }], 35: [function (require, module, exports) { + 'use strict'; + + var defaults = require(25); + var Element = require(26); + var helpers = require(45); + + defaults._set('global', { + tooltips: { + enabled: true, + custom: null, + mode: 'nearest', + position: 'average', + intersect: true, + backgroundColor: 'rgba(0,0,0,0.8)', + titleFontStyle: 'bold', + titleSpacing: 2, + titleMarginBottom: 6, + titleFontColor: '#fff', + titleAlign: 'left', + bodySpacing: 2, + bodyFontColor: '#fff', + bodyAlign: 'left', + footerFontStyle: 'bold', + footerSpacing: 2, + footerMarginTop: 6, + footerFontColor: '#fff', + footerAlign: 'left', + yPadding: 6, + xPadding: 6, + caretPadding: 2, + caretSize: 5, + cornerRadius: 6, + multiKeyBackground: '#fff', + displayColors: true, + borderColor: 'rgba(0,0,0,0)', + borderWidth: 0, + callbacks: { + // Args are: (tooltipItems, data) + beforeTitle: helpers.noop, + title: function (tooltipItems, data) { + // Pick first xLabel for now + var title = ''; + var labels = data.labels; + var labelCount = labels ? labels.length : 0; + + if (tooltipItems.length > 0) { + var item = tooltipItems[0]; + + if (item.xLabel) { + title = item.xLabel; + } else if (labelCount > 0 && item.index < labelCount) { + title = labels[item.index]; + } + } + + return title; + }, + afterTitle: helpers.noop, + + // Args are: (tooltipItems, data) + beforeBody: helpers.noop, + + // Args are: (tooltipItem, data) + beforeLabel: helpers.noop, + label: function (tooltipItem, data) { + var label = data.datasets[tooltipItem.datasetIndex].label || ''; + + if (label) { + label += ': '; + } + label += tooltipItem.yLabel; + return label; + }, + labelColor: function (tooltipItem, chart) { + var meta = chart.getDatasetMeta(tooltipItem.datasetIndex); + var activeElement = meta.data[tooltipItem.index]; + var view = activeElement._view; + return { + borderColor: view.borderColor, + backgroundColor: view.backgroundColor + }; + }, + labelTextColor: function () { + return this._options.bodyFontColor; + }, + afterLabel: helpers.noop, + + // Args are: (tooltipItems, data) + afterBody: helpers.noop, + + // Args are: (tooltipItems, data) + beforeFooter: helpers.noop, + footer: helpers.noop, + afterFooter: helpers.noop + } + } + }); + + module.exports = function (Chart) { + + /** + * Helper method to merge the opacity into a color + */ + function mergeOpacity(colorString, opacity) { + var color = helpers.color(colorString); + return color.alpha(opacity * color.alpha()).rgbaString(); + } + + // Helper to push or concat based on if the 2nd parameter is an array or not + function pushOrConcat(base, toPush) { + if (toPush) { + if (helpers.isArray(toPush)) { + // base = base.concat(toPush); + Array.prototype.push.apply(base, toPush); + } else { + base.push(toPush); + } + } + + return base; + } + + // Private helper to create a tooltip item model + // @param element : the chart element (point, arc, bar) to create the tooltip item for + // @return : new tooltip item + function createTooltipItem(element) { + var xScale = element._xScale; + var yScale = element._yScale || element._scale; // handle radar || polarArea charts + var index = element._index; + var datasetIndex = element._datasetIndex; + + return { + xLabel: xScale ? xScale.getLabelForIndex(index, datasetIndex) : '', + yLabel: yScale ? yScale.getLabelForIndex(index, datasetIndex) : '', + index: index, + datasetIndex: datasetIndex, + x: element._model.x, + y: element._model.y + }; + } + + /** + * Helper to get the reset model for the tooltip + * @param tooltipOpts {Object} the tooltip options + */ + function getBaseModel(tooltipOpts) { + var globalDefaults = defaults.global; + var valueOrDefault = helpers.valueOrDefault; + + return { + // Positioning + xPadding: tooltipOpts.xPadding, + yPadding: tooltipOpts.yPadding, + xAlign: tooltipOpts.xAlign, + yAlign: tooltipOpts.yAlign, + + // Body + bodyFontColor: tooltipOpts.bodyFontColor, + _bodyFontFamily: valueOrDefault(tooltipOpts.bodyFontFamily, globalDefaults.defaultFontFamily), + _bodyFontStyle: valueOrDefault(tooltipOpts.bodyFontStyle, globalDefaults.defaultFontStyle), + _bodyAlign: tooltipOpts.bodyAlign, + bodyFontSize: valueOrDefault(tooltipOpts.bodyFontSize, globalDefaults.defaultFontSize), + bodySpacing: tooltipOpts.bodySpacing, + + // Title + titleFontColor: tooltipOpts.titleFontColor, + _titleFontFamily: valueOrDefault(tooltipOpts.titleFontFamily, globalDefaults.defaultFontFamily), + _titleFontStyle: valueOrDefault(tooltipOpts.titleFontStyle, globalDefaults.defaultFontStyle), + titleFontSize: valueOrDefault(tooltipOpts.titleFontSize, globalDefaults.defaultFontSize), + _titleAlign: tooltipOpts.titleAlign, + titleSpacing: tooltipOpts.titleSpacing, + titleMarginBottom: tooltipOpts.titleMarginBottom, + + // Footer + footerFontColor: tooltipOpts.footerFontColor, + _footerFontFamily: valueOrDefault(tooltipOpts.footerFontFamily, globalDefaults.defaultFontFamily), + _footerFontStyle: valueOrDefault(tooltipOpts.footerFontStyle, globalDefaults.defaultFontStyle), + footerFontSize: valueOrDefault(tooltipOpts.footerFontSize, globalDefaults.defaultFontSize), + _footerAlign: tooltipOpts.footerAlign, + footerSpacing: tooltipOpts.footerSpacing, + footerMarginTop: tooltipOpts.footerMarginTop, + + // Appearance + caretSize: tooltipOpts.caretSize, + cornerRadius: tooltipOpts.cornerRadius, + backgroundColor: tooltipOpts.backgroundColor, + opacity: 0, + legendColorBackground: tooltipOpts.multiKeyBackground, + displayColors: tooltipOpts.displayColors, + borderColor: tooltipOpts.borderColor, + borderWidth: tooltipOpts.borderWidth + }; + } + + /** + * Get the size of the tooltip + */ + function getTooltipSize(tooltip, model) { + var ctx = tooltip._chart.ctx; + + var height = model.yPadding * 2; // Tooltip Padding + var width = 0; + + // Count of all lines in the body + var body = model.body; + var combinedBodyLength = body.reduce(function (count, bodyItem) { + return count + bodyItem.before.length + bodyItem.lines.length + bodyItem.after.length; + }, 0); + combinedBodyLength += model.beforeBody.length + model.afterBody.length; + + var titleLineCount = model.title.length; + var footerLineCount = model.footer.length; + var titleFontSize = model.titleFontSize; + var bodyFontSize = model.bodyFontSize; + var footerFontSize = model.footerFontSize; + + height += titleLineCount * titleFontSize; // Title Lines + height += titleLineCount ? (titleLineCount - 1) * model.titleSpacing : 0; // Title Line Spacing + height += titleLineCount ? model.titleMarginBottom : 0; // Title's bottom Margin + height += combinedBodyLength * bodyFontSize; // Body Lines + height += combinedBodyLength ? (combinedBodyLength - 1) * model.bodySpacing : 0; // Body Line Spacing + height += footerLineCount ? model.footerMarginTop : 0; // Footer Margin + height += footerLineCount * (footerFontSize); // Footer Lines + height += footerLineCount ? (footerLineCount - 1) * model.footerSpacing : 0; // Footer Line Spacing + + // Title width + var widthPadding = 0; + var maxLineWidth = function (line) { + width = Math.max(width, ctx.measureText(line).width + widthPadding); + }; + + ctx.font = helpers.fontString(titleFontSize, model._titleFontStyle, model._titleFontFamily); + helpers.each(model.title, maxLineWidth); + + // Body width + ctx.font = helpers.fontString(bodyFontSize, model._bodyFontStyle, model._bodyFontFamily); + helpers.each(model.beforeBody.concat(model.afterBody), maxLineWidth); + + // Body lines may include some extra width due to the color box + widthPadding = model.displayColors ? (bodyFontSize + 2) : 0; + helpers.each(body, function (bodyItem) { + helpers.each(bodyItem.before, maxLineWidth); + helpers.each(bodyItem.lines, maxLineWidth); + helpers.each(bodyItem.after, maxLineWidth); + }); + + // Reset back to 0 + widthPadding = 0; + + // Footer width + ctx.font = helpers.fontString(footerFontSize, model._footerFontStyle, model._footerFontFamily); + helpers.each(model.footer, maxLineWidth); + + // Add padding + width += 2 * model.xPadding; + + return { + width: width, + height: height + }; + } + + /** + * Helper to get the alignment of a tooltip given the size + */ + function determineAlignment(tooltip, size) { + var model = tooltip._model; + var chart = tooltip._chart; + var chartArea = tooltip._chart.chartArea; + var xAlign = 'center'; + var yAlign = 'center'; + + if (model.y < size.height) { + yAlign = 'top'; + } else if (model.y > (chart.height - size.height)) { + yAlign = 'bottom'; + } + + var lf, rf; // functions to determine left, right alignment + var olf, orf; // functions to determine if left/right alignment causes tooltip to go outside chart + var yf; // function to get the y alignment if the tooltip goes outside of the left or right edges + var midX = (chartArea.left + chartArea.right) / 2; + var midY = (chartArea.top + chartArea.bottom) / 2; + + if (yAlign === 'center') { + lf = function (x) { + return x <= midX; + }; + rf = function (x) { + return x > midX; + }; + } else { + lf = function (x) { + return x <= (size.width / 2); + }; + rf = function (x) { + return x >= (chart.width - (size.width / 2)); + }; + } + + olf = function (x) { + return x + size.width + model.caretSize + model.caretPadding > chart.width; + }; + orf = function (x) { + return x - size.width - model.caretSize - model.caretPadding < 0; + }; + yf = function (y) { + return y <= midY ? 'top' : 'bottom'; + }; + + if (lf(model.x)) { + xAlign = 'left'; + + // Is tooltip too wide and goes over the right side of the chart.? + if (olf(model.x)) { + xAlign = 'center'; + yAlign = yf(model.y); + } + } else if (rf(model.x)) { + xAlign = 'right'; + + // Is tooltip too wide and goes outside left edge of canvas? + if (orf(model.x)) { + xAlign = 'center'; + yAlign = yf(model.y); + } + } + + var opts = tooltip._options; + return { + xAlign: opts.xAlign ? opts.xAlign : xAlign, + yAlign: opts.yAlign ? opts.yAlign : yAlign + }; + } + + /** + * @Helper to get the location a tooltip needs to be placed at given the initial position (via the vm) and the size and alignment + */ + function getBackgroundPoint(vm, size, alignment, chart) { + // Background Position + var x = vm.x; + var y = vm.y; + + var caretSize = vm.caretSize; + var caretPadding = vm.caretPadding; + var cornerRadius = vm.cornerRadius; + var xAlign = alignment.xAlign; + var yAlign = alignment.yAlign; + var paddingAndSize = caretSize + caretPadding; + var radiusAndPadding = cornerRadius + caretPadding; + + if (xAlign === 'right') { + x -= size.width; + } else if (xAlign === 'center') { + x -= (size.width / 2); + if (x + size.width > chart.width) { + x = chart.width - size.width; + } + if (x < 0) { + x = 0; + } + } + + if (yAlign === 'top') { + y += paddingAndSize; + } else if (yAlign === 'bottom') { + y -= size.height + paddingAndSize; + } else { + y -= (size.height / 2); + } + + if (yAlign === 'center') { + if (xAlign === 'left') { + x += paddingAndSize; + } else if (xAlign === 'right') { + x -= paddingAndSize; + } + } else if (xAlign === 'left') { + x -= radiusAndPadding; + } else if (xAlign === 'right') { + x += radiusAndPadding; + } + + return { + x: x, + y: y + }; + } + + Chart.Tooltip = Element.extend({ + initialize: function () { + this._model = getBaseModel(this._options); + this._lastActive = []; + }, + + // Get the title + // Args are: (tooltipItem, data) + getTitle: function () { + var me = this; + var opts = me._options; + var callbacks = opts.callbacks; + + var beforeTitle = callbacks.beforeTitle.apply(me, arguments); + var title = callbacks.title.apply(me, arguments); + var afterTitle = callbacks.afterTitle.apply(me, arguments); + + var lines = []; + lines = pushOrConcat(lines, beforeTitle); + lines = pushOrConcat(lines, title); + lines = pushOrConcat(lines, afterTitle); + + return lines; + }, + + // Args are: (tooltipItem, data) + getBeforeBody: function () { + var lines = this._options.callbacks.beforeBody.apply(this, arguments); + return helpers.isArray(lines) ? lines : lines !== undefined ? [lines] : []; + }, + + // Args are: (tooltipItem, data) + getBody: function (tooltipItems, data) { + var me = this; + var callbacks = me._options.callbacks; + var bodyItems = []; + + helpers.each(tooltipItems, function (tooltipItem) { + var bodyItem = { + before: [], + lines: [], + after: [] + }; + pushOrConcat(bodyItem.before, callbacks.beforeLabel.call(me, tooltipItem, data)); + pushOrConcat(bodyItem.lines, callbacks.label.call(me, tooltipItem, data)); + pushOrConcat(bodyItem.after, callbacks.afterLabel.call(me, tooltipItem, data)); + + bodyItems.push(bodyItem); + }); + + return bodyItems; + }, + + // Args are: (tooltipItem, data) + getAfterBody: function () { + var lines = this._options.callbacks.afterBody.apply(this, arguments); + return helpers.isArray(lines) ? lines : lines !== undefined ? [lines] : []; + }, + + // Get the footer and beforeFooter and afterFooter lines + // Args are: (tooltipItem, data) + getFooter: function () { + var me = this; + var callbacks = me._options.callbacks; + + var beforeFooter = callbacks.beforeFooter.apply(me, arguments); + var footer = callbacks.footer.apply(me, arguments); + var afterFooter = callbacks.afterFooter.apply(me, arguments); + + var lines = []; + lines = pushOrConcat(lines, beforeFooter); + lines = pushOrConcat(lines, footer); + lines = pushOrConcat(lines, afterFooter); + + return lines; + }, + + update: function (changed) { + var me = this; + var opts = me._options; + + // Need to regenerate the model because its faster than using extend and it is necessary due to the optimization in Chart.Element.transition + // that does _view = _model if ease === 1. This causes the 2nd tooltip update to set properties in both the view and model at the same time + // which breaks any animations. + var existingModel = me._model; + var model = me._model = getBaseModel(opts); + var active = me._active; + + var data = me._data; + + // In the case where active.length === 0 we need to keep these at existing values for good animations + var alignment = { + xAlign: existingModel.xAlign, + yAlign: existingModel.yAlign + }; + var backgroundPoint = { + x: existingModel.x, + y: existingModel.y + }; + var tooltipSize = { + width: existingModel.width, + height: existingModel.height + }; + var tooltipPosition = { + x: existingModel.caretX, + y: existingModel.caretY + }; + + var i, len; + + if (active.length) { + model.opacity = 1; + + var labelColors = []; + var labelTextColors = []; + tooltipPosition = Chart.Tooltip.positioners[opts.position].call(me, active, me._eventPosition); + + var tooltipItems = []; + for (i = 0, len = active.length; i < len; ++i) { + tooltipItems.push(createTooltipItem(active[i])); + } + + // If the user provided a filter function, use it to modify the tooltip items + if (opts.filter) { + tooltipItems = tooltipItems.filter(function (a) { + return opts.filter(a, data); + }); + } + + // If the user provided a sorting function, use it to modify the tooltip items + if (opts.itemSort) { + tooltipItems = tooltipItems.sort(function (a, b) { + return opts.itemSort(a, b, data); + }); + } + + // Determine colors for boxes + helpers.each(tooltipItems, function (tooltipItem) { + labelColors.push(opts.callbacks.labelColor.call(me, tooltipItem, me._chart)); + labelTextColors.push(opts.callbacks.labelTextColor.call(me, tooltipItem, me._chart)); + }); + + + // Build the Text Lines + model.title = me.getTitle(tooltipItems, data); + model.beforeBody = me.getBeforeBody(tooltipItems, data); + model.body = me.getBody(tooltipItems, data); + model.afterBody = me.getAfterBody(tooltipItems, data); + model.footer = me.getFooter(tooltipItems, data); + + // Initial positioning and colors + model.x = Math.round(tooltipPosition.x); + model.y = Math.round(tooltipPosition.y); + model.caretPadding = opts.caretPadding; + model.labelColors = labelColors; + model.labelTextColors = labelTextColors; + + // data points + model.dataPoints = tooltipItems; + + // We need to determine alignment of the tooltip + tooltipSize = getTooltipSize(this, model); + alignment = determineAlignment(this, tooltipSize); + // Final Size and Position + backgroundPoint = getBackgroundPoint(model, tooltipSize, alignment, me._chart); + } else { + model.opacity = 0; + } + + model.xAlign = alignment.xAlign; + model.yAlign = alignment.yAlign; + model.x = backgroundPoint.x; + model.y = backgroundPoint.y; + model.width = tooltipSize.width; + model.height = tooltipSize.height; + + // Point where the caret on the tooltip points to + model.caretX = tooltipPosition.x; + model.caretY = tooltipPosition.y; + + me._model = model; + + if (changed && opts.custom) { + opts.custom.call(me, model); + } + + return me; + }, + drawCaret: function (tooltipPoint, size) { + var ctx = this._chart.ctx; + var vm = this._view; + var caretPosition = this.getCaretPosition(tooltipPoint, size, vm); + + ctx.lineTo(caretPosition.x1, caretPosition.y1); + ctx.lineTo(caretPosition.x2, caretPosition.y2); + ctx.lineTo(caretPosition.x3, caretPosition.y3); + }, + getCaretPosition: function (tooltipPoint, size, vm) { + var x1, x2, x3, y1, y2, y3; + var caretSize = vm.caretSize; + var cornerRadius = vm.cornerRadius; + var xAlign = vm.xAlign; + var yAlign = vm.yAlign; + var ptX = tooltipPoint.x; + var ptY = tooltipPoint.y; + var width = size.width; + var height = size.height; + + if (yAlign === 'center') { + y2 = ptY + (height / 2); + + if (xAlign === 'left') { + x1 = ptX; + x2 = x1 - caretSize; + x3 = x1; + + y1 = y2 + caretSize; + y3 = y2 - caretSize; + } else { + x1 = ptX + width; + x2 = x1 + caretSize; + x3 = x1; + + y1 = y2 - caretSize; + y3 = y2 + caretSize; + } + } else { + if (xAlign === 'left') { + x2 = ptX + cornerRadius + (caretSize); + x1 = x2 - caretSize; + x3 = x2 + caretSize; + } else if (xAlign === 'right') { + x2 = ptX + width - cornerRadius - caretSize; + x1 = x2 - caretSize; + x3 = x2 + caretSize; + } else { + x2 = vm.caretX; + x1 = x2 - caretSize; + x3 = x2 + caretSize; + } + if (yAlign === 'top') { + y1 = ptY; + y2 = y1 - caretSize; + y3 = y1; + } else { + y1 = ptY + height; + y2 = y1 + caretSize; + y3 = y1; + // invert drawing order + var tmp = x3; + x3 = x1; + x1 = tmp; + } + } + return { x1: x1, x2: x2, x3: x3, y1: y1, y2: y2, y3: y3 }; + }, + drawTitle: function (pt, vm, ctx, opacity) { + var title = vm.title; + + if (title.length) { + ctx.textAlign = vm._titleAlign; + ctx.textBaseline = 'top'; + + var titleFontSize = vm.titleFontSize; + var titleSpacing = vm.titleSpacing; + + ctx.fillStyle = mergeOpacity(vm.titleFontColor, opacity); + ctx.font = helpers.fontString(titleFontSize, vm._titleFontStyle, vm._titleFontFamily); + + var i, len; + for (i = 0, len = title.length; i < len; ++i) { + ctx.fillText(title[i], pt.x, pt.y); + pt.y += titleFontSize + titleSpacing; // Line Height and spacing + + if (i + 1 === title.length) { + pt.y += vm.titleMarginBottom - titleSpacing; // If Last, add margin, remove spacing + } + } + } + }, + drawBody: function (pt, vm, ctx, opacity) { + var bodyFontSize = vm.bodyFontSize; + var bodySpacing = vm.bodySpacing; + var body = vm.body; + + ctx.textAlign = vm._bodyAlign; + ctx.textBaseline = 'top'; + ctx.font = helpers.fontString(bodyFontSize, vm._bodyFontStyle, vm._bodyFontFamily); + + // Before Body + var xLinePadding = 0; + var fillLineOfText = function (line) { + ctx.fillText(line, pt.x + xLinePadding, pt.y); + pt.y += bodyFontSize + bodySpacing; + }; + + // Before body lines + ctx.fillStyle = mergeOpacity(vm.bodyFontColor, opacity); + helpers.each(vm.beforeBody, fillLineOfText); + + var drawColorBoxes = vm.displayColors; + xLinePadding = drawColorBoxes ? (bodyFontSize + 2) : 0; + + // Draw body lines now + helpers.each(body, function (bodyItem, i) { + var textColor = mergeOpacity(vm.labelTextColors[i], opacity); + ctx.fillStyle = textColor; + helpers.each(bodyItem.before, fillLineOfText); + + helpers.each(bodyItem.lines, function (line) { + // Draw Legend-like boxes if needed + if (drawColorBoxes) { + // Fill a white rect so that colours merge nicely if the opacity is < 1 + ctx.fillStyle = mergeOpacity(vm.legendColorBackground, opacity); + ctx.fillRect(pt.x, pt.y, bodyFontSize, bodyFontSize); + + // Border + ctx.lineWidth = 1; + ctx.strokeStyle = mergeOpacity(vm.labelColors[i].borderColor, opacity); + ctx.strokeRect(pt.x, pt.y, bodyFontSize, bodyFontSize); + + // Inner square + ctx.fillStyle = mergeOpacity(vm.labelColors[i].backgroundColor, opacity); + ctx.fillRect(pt.x + 1, pt.y + 1, bodyFontSize - 2, bodyFontSize - 2); + ctx.fillStyle = textColor; + } + + fillLineOfText(line); + }); + + helpers.each(bodyItem.after, fillLineOfText); + }); + + // Reset back to 0 for after body + xLinePadding = 0; + + // After body lines + helpers.each(vm.afterBody, fillLineOfText); + pt.y -= bodySpacing; // Remove last body spacing + }, + drawFooter: function (pt, vm, ctx, opacity) { + var footer = vm.footer; + + if (footer.length) { + pt.y += vm.footerMarginTop; + + ctx.textAlign = vm._footerAlign; + ctx.textBaseline = 'top'; + + ctx.fillStyle = mergeOpacity(vm.footerFontColor, opacity); + ctx.font = helpers.fontString(vm.footerFontSize, vm._footerFontStyle, vm._footerFontFamily); + + helpers.each(footer, function (line) { + ctx.fillText(line, pt.x, pt.y); + pt.y += vm.footerFontSize + vm.footerSpacing; + }); + } + }, + drawBackground: function (pt, vm, ctx, tooltipSize, opacity) { + ctx.fillStyle = mergeOpacity(vm.backgroundColor, opacity); + ctx.strokeStyle = mergeOpacity(vm.borderColor, opacity); + ctx.lineWidth = vm.borderWidth; + var xAlign = vm.xAlign; + var yAlign = vm.yAlign; + var x = pt.x; + var y = pt.y; + var width = tooltipSize.width; + var height = tooltipSize.height; + var radius = vm.cornerRadius; + + ctx.beginPath(); + ctx.moveTo(x + radius, y); + if (yAlign === 'top') { + this.drawCaret(pt, tooltipSize); + } + ctx.lineTo(x + width - radius, y); + ctx.quadraticCurveTo(x + width, y, x + width, y + radius); + if (yAlign === 'center' && xAlign === 'right') { + this.drawCaret(pt, tooltipSize); + } + ctx.lineTo(x + width, y + height - radius); + ctx.quadraticCurveTo(x + width, y + height, x + width - radius, y + height); + if (yAlign === 'bottom') { + this.drawCaret(pt, tooltipSize); + } + ctx.lineTo(x + radius, y + height); + ctx.quadraticCurveTo(x, y + height, x, y + height - radius); + if (yAlign === 'center' && xAlign === 'left') { + this.drawCaret(pt, tooltipSize); + } + ctx.lineTo(x, y + radius); + ctx.quadraticCurveTo(x, y, x + radius, y); + ctx.closePath(); + + ctx.fill(); + + if (vm.borderWidth > 0) { + ctx.stroke(); + } + }, + draw: function () { + var ctx = this._chart.ctx; + var vm = this._view; + + if (vm.opacity === 0) { + return; + } + + var tooltipSize = { + width: vm.width, + height: vm.height + }; + var pt = { + x: vm.x, + y: vm.y + }; + + // IE11/Edge does not like very small opacities, so snap to 0 + var opacity = Math.abs(vm.opacity < 1e-3) ? 0 : vm.opacity; + + // Truthy/falsey value for empty tooltip + var hasTooltipContent = vm.title.length || vm.beforeBody.length || vm.body.length || vm.afterBody.length || vm.footer.length; + + if (this._options.enabled && hasTooltipContent) { + // Draw Background + this.drawBackground(pt, vm, ctx, tooltipSize, opacity); + + // Draw Title, Body, and Footer + pt.x += vm.xPadding; + pt.y += vm.yPadding; + + // Titles + this.drawTitle(pt, vm, ctx, opacity); + + // Body + this.drawBody(pt, vm, ctx, opacity); + + // Footer + this.drawFooter(pt, vm, ctx, opacity); + } + }, + + /** + * Handle an event + * @private + * @param {IEvent} event - The event to handle + * @returns {Boolean} true if the tooltip changed + */ + handleEvent: function (e) { + var me = this; + var options = me._options; + var changed = false; + + me._lastActive = me._lastActive || []; + + // Find Active Elements for tooltips + if (e.type === 'mouseout') { + me._active = []; + } else { + me._active = me._chart.getElementsAtEventForMode(e, options.mode, options); + } + + // Remember Last Actives + changed = !helpers.arrayEquals(me._active, me._lastActive); + + // Only handle target event on tooltip change + if (changed) { + me._lastActive = me._active; + + if (options.enabled || options.custom) { + me._eventPosition = { + x: e.x, + y: e.y + }; + + me.update(true); + me.pivot(); + } + } + + return changed; + } + }); + + /** + * @namespace Chart.Tooltip.positioners + */ + Chart.Tooltip.positioners = { + /** + * Average mode places the tooltip at the average position of the elements shown + * @function Chart.Tooltip.positioners.average + * @param elements {ChartElement[]} the elements being displayed in the tooltip + * @returns {Point} tooltip position + */ + average: function (elements) { + if (!elements.length) { + return false; + } + + var i, len; + var x = 0; + var y = 0; + var count = 0; + + for (i = 0, len = elements.length; i < len; ++i) { + var el = elements[i]; + if (el && el.hasValue()) { + var pos = el.tooltipPosition(); + x += pos.x; + y += pos.y; + ++count; + } + } + + return { + x: Math.round(x / count), + y: Math.round(y / count) + }; + }, + + /** + * Gets the tooltip position nearest of the item nearest to the event position + * @function Chart.Tooltip.positioners.nearest + * @param elements {Chart.Element[]} the tooltip elements + * @param eventPosition {Point} the position of the event in canvas coordinates + * @returns {Point} the tooltip position + */ + nearest: function (elements, eventPosition) { + var x = eventPosition.x; + var y = eventPosition.y; + var minDistance = Number.POSITIVE_INFINITY; + var i, len, nearestElement; + + for (i = 0, len = elements.length; i < len; ++i) { + var el = elements[i]; + if (el && el.hasValue()) { + var center = el.getCenterPoint(); + var d = helpers.distanceBetweenPoints(eventPosition, center); + + if (d < minDistance) { + minDistance = d; + nearestElement = el; + } + } + } + + if (nearestElement) { + var tp = nearestElement.tooltipPosition(); + x = tp.x; + y = tp.y; + } + + return { + x: x, + y: y + }; + } + }; + }; + + }, { "25": 25, "26": 26, "45": 45 }], 36: [function (require, module, exports) { + 'use strict'; + + var defaults = require(25); + var Element = require(26); + var helpers = require(45); + + defaults._set('global', { + elements: { + arc: { + backgroundColor: defaults.global.defaultColor, + borderColor: '#fff', + borderWidth: 2 + } + } + }); + + module.exports = Element.extend({ + 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.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 += 2.0 * Math.PI; + } + while (angle > endAngle) { + angle -= 2.0 * Math.PI; + } + while (angle < startAngle) { + angle += 2.0 * Math.PI; + } + + // 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 sA = vm.startAngle; + var eA = vm.endAngle; + + ctx.beginPath(); + + ctx.arc(vm.x, vm.y, vm.outerRadius, sA, eA); + ctx.arc(vm.x, vm.y, vm.innerRadius, eA, sA, true); + + ctx.closePath(); + ctx.strokeStyle = vm.borderColor; + ctx.lineWidth = vm.borderWidth; + + ctx.fillStyle = vm.backgroundColor; + + ctx.fill(); + ctx.lineJoin = 'bevel'; + + if (vm.borderWidth) { + ctx.stroke(); + } + } + }); + + }, { "25": 25, "26": 26, "45": 45 }], 37: [function (require, module, exports) { + 'use strict'; + + var defaults = require(25); + var Element = require(26); + var helpers = require(45); + + var globalDefaults = defaults.global; + + defaults._set('global', { + elements: { + line: { + tension: 0.4, + backgroundColor: globalDefaults.defaultColor, + borderWidth: 3, + borderColor: globalDefaults.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 + } + } + }); + + module.exports = Element.extend({ + 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 globalOptionLineElements = globalDefaults.elements.line; + var lastDrawnIndex = -1; + var index, current, previous, currentVM; + + // If we are looping, adding the first point again + if (me._loop && points.length) { + 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 = vm.borderDashOffset || globalOptionLineElements.borderDashOffset; + ctx.lineJoin = vm.borderJoinStyle || globalOptionLineElements.borderJoinStyle; + ctx.lineWidth = vm.borderWidth || globalOptionLineElements.borderWidth; + ctx.strokeStyle = vm.borderColor || globalDefaults.defaultColor; + + // Stroke Line + ctx.beginPath(); + lastDrawnIndex = -1; + + for (index = 0; index < points.length; ++index) { + current = points[index]; + previous = helpers.previousItem(points, index); + currentVM = current._view; + + // First point moves to it's starting position no matter what + if (index === 0) { + if (!currentVM.skip) { + ctx.moveTo(currentVM.x, currentVM.y); + lastDrawnIndex = index; + } + } else { + previous = lastDrawnIndex === -1 ? previous : points[lastDrawnIndex]; + + if (!currentVM.skip) { + if ((lastDrawnIndex !== (index - 1) && !spanGaps) || lastDrawnIndex === -1) { + // There was a gap and this is the first point after the gap + ctx.moveTo(currentVM.x, currentVM.y); + } else { + // Line to next point + helpers.canvas.lineTo(ctx, previous._view, current._view); + } + lastDrawnIndex = index; + } + } + } + + ctx.stroke(); + ctx.restore(); + } + }); + + }, { "25": 25, "26": 26, "45": 45 }], 38: [function (require, module, exports) { + 'use strict'; + + var defaults = require(25); + var Element = require(26); + var helpers = require(45); + + var defaultColor = defaults.global.defaultColor; + + defaults._set('global', { + elements: { + point: { + radius: 3, + pointStyle: 'circle', + backgroundColor: defaultColor, + borderColor: defaultColor, + 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; + } + + module.exports = Element.extend({ + 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 model = this._model; + var ctx = this._chart.ctx; + var pointStyle = vm.pointStyle; + var radius = vm.radius; + var x = vm.x; + var y = vm.y; + var color = helpers.color; + var errMargin = 1.01; // 1.01 is margin for Accumulated error. (Especially Edge, IE.) + var ratio = 0; + + if (vm.skip) { + return; + } + + ctx.strokeStyle = vm.borderColor || defaultColor; + ctx.lineWidth = helpers.valueOrDefault(vm.borderWidth, defaults.global.elements.point.borderWidth); + ctx.fillStyle = vm.backgroundColor || defaultColor; + + // Cliping for Points. + // going out from inner charArea? + if ((chartArea !== undefined) && ((model.x < chartArea.left) || (chartArea.right * errMargin < model.x) || (model.y < chartArea.top) || (chartArea.bottom * errMargin < model.y))) { + // Point fade out + if (model.x < chartArea.left) { + ratio = (x - model.x) / (chartArea.left - model.x); + } else if (chartArea.right * errMargin < model.x) { + ratio = (model.x - x) / (model.x - chartArea.right); + } else if (model.y < chartArea.top) { + ratio = (y - model.y) / (chartArea.top - model.y); + } else if (chartArea.bottom * errMargin < model.y) { + ratio = (model.y - y) / (model.y - chartArea.bottom); + } + ratio = Math.round(ratio * 100) / 100; + ctx.strokeStyle = color(ctx.strokeStyle).alpha(ratio).rgbString(); + ctx.fillStyle = color(ctx.fillStyle).alpha(ratio).rgbString(); + } + + helpers.canvas.drawPoint(ctx, pointStyle, radius, x, y); + } + }); + + }, { "25": 25, "26": 26, "45": 45 }], 39: [function (require, module, exports) { + 'use strict'; + + var defaults = require(25); + var Element = require(26); + + defaults._set('global', { + elements: { + rectangle: { + backgroundColor: defaults.global.defaultColor, + borderColor: defaults.global.defaultColor, + borderSkipped: 'bottom', + borderWidth: 0 + } + } + }); + + function isVertical(bar) { + return bar._view.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(bar) { + var vm = bar._view; + var x1, x2, y1, y2; + + if (isVertical(bar)) { + // vertical + var halfWidth = vm.width / 2; + x1 = vm.x - halfWidth; + x2 = vm.x + halfWidth; + y1 = Math.min(vm.y, vm.base); + y2 = Math.max(vm.y, vm.base); + } else { + // horizontal bar + var halfHeight = vm.height / 2; + x1 = Math.min(vm.x, vm.base); + x2 = Math.max(vm.x, vm.base); + y1 = vm.y - halfHeight; + y2 = vm.y + halfHeight; + } + + return { + left: x1, + top: y1, + right: x2, + bottom: y2 + }; + } + + module.exports = Element.extend({ + draw: function () { + var ctx = this._chart.ctx; + var vm = this._view; + var left, right, top, bottom, signX, signY, borderSkipped; + var borderWidth = vm.borderWidth; + + if (!vm.horizontal) { + // bar + left = vm.x - vm.width / 2; + right = vm.x + vm.width / 2; + top = vm.y; + bottom = vm.base; + signX = 1; + signY = bottom > top ? 1 : -1; + borderSkipped = vm.borderSkipped || 'bottom'; + } else { + // horizontal bar + left = vm.base; + right = vm.x; + top = vm.y - vm.height / 2; + bottom = vm.y + vm.height / 2; + signX = right > left ? 1 : -1; + signY = 1; + borderSkipped = vm.borderSkipped || 'left'; + } + + // Canvas doesn't allow us to stroke inside the width so we can + // adjust the sizes to fit if we're setting a stroke on the line + if (borderWidth) { + // borderWidth shold be less than bar width and bar height. + var barSize = Math.min(Math.abs(left - right), Math.abs(top - bottom)); + borderWidth = borderWidth > barSize ? barSize : borderWidth; + var halfStroke = borderWidth / 2; + // Adjust borderWidth when bar top position is near vm.base(zero). + var borderLeft = left + (borderSkipped !== 'left' ? halfStroke * signX : 0); + var borderRight = right + (borderSkipped !== 'right' ? -halfStroke * signX : 0); + var borderTop = top + (borderSkipped !== 'top' ? halfStroke * signY : 0); + var borderBottom = bottom + (borderSkipped !== 'bottom' ? -halfStroke * signY : 0); + // not become a vertical line? + if (borderLeft !== borderRight) { + top = borderTop; + bottom = borderBottom; + } + // not become a horizontal line? + if (borderTop !== borderBottom) { + left = borderLeft; + right = borderRight; + } + } + + ctx.beginPath(); + ctx.fillStyle = vm.backgroundColor; + ctx.strokeStyle = vm.borderColor; + ctx.lineWidth = borderWidth; + + // Corner points, from bottom-left to bottom-right clockwise + // | 1 2 | + // | 0 3 | + var corners = [ + [left, bottom], + [left, top], + [right, top], + [right, bottom] + ]; + + // Find first (starting) corner with fallback to 'bottom' + var borders = ['bottom', 'left', 'top', 'right']; + var startCorner = borders.indexOf(borderSkipped, 0); + if (startCorner === -1) { + startCorner = 0; + } + + function cornerAt(index) { + return corners[(startCorner + index) % 4]; + } + + // Draw rectangle from 'startCorner' + var corner = cornerAt(0); + ctx.moveTo(corner[0], corner[1]); + + for (var i = 1; i < 4; i++) { + corner = cornerAt(i); + ctx.lineTo(corner[0], corner[1]); + } + + ctx.fill(); + if (borderWidth) { + ctx.stroke(); + } + }, + + height: function () { + var vm = this._view; + return vm.base - vm.y; + }, + + inRange: function (mouseX, mouseY) { + var inRange = false; + + if (this._view) { + var bounds = getBarBounds(this); + inRange = mouseX >= bounds.left && mouseX <= bounds.right && mouseY >= bounds.top && mouseY <= bounds.bottom; + } + + return inRange; + }, + + inLabelRange: function (mouseX, mouseY) { + var me = this; + if (!me._view) { + return false; + } + + var inRange = false; + var bounds = getBarBounds(me); + + if (isVertical(me)) { + inRange = mouseX >= bounds.left && mouseX <= bounds.right; + } else { + inRange = mouseY >= bounds.top && mouseY <= bounds.bottom; + } + + return inRange; + }, + + inXRange: function (mouseX) { + var bounds = getBarBounds(this); + return mouseX >= bounds.left && mouseX <= bounds.right; + }, + + inYRange: function (mouseY) { + var bounds = getBarBounds(this); + return mouseY >= bounds.top && mouseY <= bounds.bottom; + }, + + getCenterPoint: function () { + var vm = this._view; + var x, y; + if (isVertical(this)) { + 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 vm.width * Math.abs(vm.y - vm.base); + }, + + tooltipPosition: function () { + var vm = this._view; + return { + x: vm.x, + y: vm.y + }; + } + }); + + }, { "25": 25, "26": 26 }], 40: [function (require, module, exports) { + 'use strict'; + + module.exports = {}; + module.exports.Arc = require(36); + module.exports.Line = require(37); + module.exports.Point = require(38); + module.exports.Rectangle = require(39); + + }, { "36": 36, "37": 37, "38": 38, "39": 39 }], 41: [function (require, module, exports) { + 'use strict'; + + var helpers = require(42); + + /** + * @namespace Chart.helpers.canvas + */ + var exports = module.exports = { + /** + * 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 rx = Math.min(radius, width / 2); + var ry = Math.min(radius, height / 2); + + ctx.moveTo(x + rx, y); + ctx.lineTo(x + width - rx, y); + ctx.quadraticCurveTo(x + width, y, x + width, y + ry); + ctx.lineTo(x + width, y + height - ry); + ctx.quadraticCurveTo(x + width, y + height, x + width - rx, y + height); + ctx.lineTo(x + rx, y + height); + ctx.quadraticCurveTo(x, y + height, x, y + height - ry); + ctx.lineTo(x, y + ry); + ctx.quadraticCurveTo(x, y, x + rx, y); + } else { + ctx.rect(x, y, width, height); + } + }, + + drawPoint: function (ctx, style, radius, x, y) { + var type, edgeLength, xOffset, yOffset, height, size; + + if (style && typeof style === 'object') { + type = style.toString(); + if (type === '[object HTMLImageElement]' || type === '[object HTMLCanvasElement]') { + ctx.drawImage(style, x - style.width / 2, y - style.height / 2, style.width, style.height); + return; + } + } + + if (isNaN(radius) || radius <= 0) { + return; + } + + switch (style) { + // Default includes circle + default: + ctx.beginPath(); + ctx.arc(x, y, radius, 0, Math.PI * 2); + ctx.closePath(); + ctx.fill(); + break; + case 'triangle': + ctx.beginPath(); + edgeLength = 3 * radius / Math.sqrt(3); + height = edgeLength * Math.sqrt(3) / 2; + ctx.moveTo(x - edgeLength / 2, y + height / 3); + ctx.lineTo(x + edgeLength / 2, y + height / 3); + ctx.lineTo(x, y - 2 * height / 3); + ctx.closePath(); + ctx.fill(); + break; + case 'rect': + size = 1 / Math.SQRT2 * radius; + ctx.beginPath(); + ctx.fillRect(x - size, y - size, 2 * size, 2 * size); + ctx.strokeRect(x - size, y - size, 2 * size, 2 * size); + break; + case 'rectRounded': + var offset = radius / Math.SQRT2; + var leftX = x - offset; + var topY = y - offset; + var sideSize = Math.SQRT2 * radius; + ctx.beginPath(); + this.roundedRect(ctx, leftX, topY, sideSize, sideSize, radius / 2); + ctx.closePath(); + ctx.fill(); + break; + case 'rectRot': + size = 1 / Math.SQRT2 * radius; + ctx.beginPath(); + ctx.moveTo(x - size, y); + ctx.lineTo(x, y + size); + ctx.lineTo(x + size, y); + ctx.lineTo(x, y - size); + ctx.closePath(); + ctx.fill(); + break; + case 'cross': + ctx.beginPath(); + ctx.moveTo(x, y + radius); + ctx.lineTo(x, y - radius); + ctx.moveTo(x - radius, y); + ctx.lineTo(x + radius, y); + ctx.closePath(); + break; + case 'crossRot': + ctx.beginPath(); + xOffset = Math.cos(Math.PI / 4) * radius; + yOffset = Math.sin(Math.PI / 4) * radius; + ctx.moveTo(x - xOffset, y - yOffset); + ctx.lineTo(x + xOffset, y + yOffset); + ctx.moveTo(x - xOffset, y + yOffset); + ctx.lineTo(x + xOffset, y - yOffset); + ctx.closePath(); + break; + case 'star': + ctx.beginPath(); + ctx.moveTo(x, y + radius); + ctx.lineTo(x, y - radius); + ctx.moveTo(x - radius, y); + ctx.lineTo(x + radius, y); + xOffset = Math.cos(Math.PI / 4) * radius; + yOffset = Math.sin(Math.PI / 4) * radius; + ctx.moveTo(x - xOffset, y - yOffset); + ctx.lineTo(x + xOffset, y + yOffset); + ctx.moveTo(x - xOffset, y + yOffset); + ctx.lineTo(x + xOffset, y - yOffset); + ctx.closePath(); + break; + case 'line': + ctx.beginPath(); + ctx.moveTo(x - radius, y); + ctx.lineTo(x + radius, y); + ctx.closePath(); + break; + case 'dash': + ctx.beginPath(); + ctx.moveTo(x, y); + ctx.lineTo(x + radius, y); + ctx.closePath(); + break; + } + + ctx.stroke(); + }, + + 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) { + if (target.steppedLine) { + if ((target.steppedLine === 'after' && !flip) || (target.steppedLine !== '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); + } + }; + + // 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.clear = exports.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.drawRoundedRectangle = function (ctx) { + ctx.beginPath(); + exports.roundedRect.apply(exports, arguments); + ctx.closePath(); + }; + + }, { "42": 42 }], 42: [function (require, module, exports) { + 'use strict'; + + /** + * @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, else returns false. + * @param {*} value - The value to test. + * @returns {Boolean} + * @function + */ + isArray: Array.isArray ? Array.isArray : function (value) { + return Object.prototype.toString.call(value) === '[object Array]'; + }, + + /** + * 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 `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 http://stackoverflow.com/a/14853974 + * @param {Array} a0 - The array to compare + * @param {Array} a1 - The array to compare + * @returns {Boolean} + */ + arrayEquals: function (a0, a1) { + var i, ilen, v0, v1; + + if (!a0 || !a1 || a0.length !== a1.length) { + return false; + } + + for (i = 0, ilen = a0.length; i < ilen; ++i) { + v0 = a0[i]; + v1 = a1[i]; + + if (v0 instanceof Array && v1 instanceof Array) { + if (!helpers.arrayEquals(v0, v1)) { + return false; + } + } else if (v0 !== v1) { + // NOTE: two different object instances will never be equal: {x:20} != {x:20} + return false; + } + } + + return true; + }, + + /** + * Returns a deep copy of `source` without keeping references on objects and arrays. + * @param {*} source - The value to clone. + * @returns {*} + */ + clone: function (source) { + if (helpers.isArray(source)) { + return source.map(helpers.clone); + } + + if (helpers.isObject(source)) { + var target = {}; + var keys = Object.keys(source); + var klen = keys.length; + var k = 0; + + for (; k < klen; ++k) { + target[keys[k]] = helpers.clone(source[keys[k]]); + } + + return target; + } + + return source; + }, + + /** + * The default merger when Chart.helpers.merge is called without merger option. + * Note(SB): this method is also used by configMerge and scaleMerge as fallback. + * @private + */ + _merger: function (key, target, source, options) { + var tval = target[key]; + var sval = source[key]; + + if (helpers.isObject(tval) && helpers.isObject(sval)) { + helpers.merge(tval, sval, options); + } else { + target[key] = helpers.clone(sval); + } + }, + + /** + * Merges source[key] in target[key] only if target[key] is undefined. + * @private + */ + _mergerIf: function (key, target, source) { + var tval = target[key]; + var sval = source[key]; + + if (helpers.isObject(tval) && helpers.isObject(sval)) { + helpers.mergeIf(tval, sval); + } else if (!target.hasOwnProperty(key)) { + target[key] = helpers.clone(sval); + } + }, + + /** + * Recursively deep copies `source` properties into `target` with the given `options`. + * IMPORTANT: `target` is not cloned and will be updated with `source` properties. + * @param {Object} target - The target object in which all sources are merged into. + * @param {Object|Array(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|Array(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: function (target) { + var setFn = function (value, key) { + target[key] = value; + }; + for (var i = 1, ilen = arguments.length; i < ilen; ++i) { + helpers.each(arguments[i], setFn); + } + return target; + }, + + /** + * 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; + } + }; + + module.exports = 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; + + }, {}], 43: [function (require, module, exports) { + 'use strict'; + + var helpers = require(42); + + /** + * 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; + } + }; + + module.exports = { + 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.easingEffects = effects; + + }, { "42": 42 }], 44: [function (require, module, exports) { + 'use strict'; + + var helpers = require(42); + + /** + * @alias Chart.helpers.options + * @namespace + */ + module.exports = { + /** + * 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; + default: + 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.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 + }; + }, + + /** + * 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. + * @since 2.7.0 + */ + resolve: function (inputs, context, index) { + 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); + } + if (index !== undefined && helpers.isArray(value)) { + value = value[index]; + } + if (value !== undefined) { + return value; + } + } + } + }; + + }, { "42": 42 }], 45: [function (require, module, exports) { + 'use strict'; + + module.exports = require(42); + module.exports.easing = require(43); + module.exports.canvas = require(41); + module.exports.options = require(44); + + }, { "41": 41, "42": 42, "43": 43, "44": 44 }], 46: [function (require, module, exports) { + /** + * Platform fallback implementation (minimal). + * @see https://github.com/chartjs/Chart.js/pull/4591#issuecomment-319575939 + */ + + module.exports = { + 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; + } + }; + + }, {}], 47: [function (require, module, exports) { + /** + * Chart.Platform implementation for targeting a web browser + */ + + 'use strict'; + + var helpers = require(45); + + var EXPANDO_KEY = '$chartjs'; + var CSS_PREFIX = 'chartjs-'; + 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.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', { + 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 addEventListener(node, type, listener) { + node.addEventListener(type, listener, eventListenerOptions); + } + + function removeEventListener(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.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.requestAnimFrame.call(window, function () { + ticking = false; + fn.apply(thisArg, args); + }); + } + }; + } + + // Implementation based on https://github.com/marcj/css-element-queries + function createResizer(handler) { + var resizer = document.createElement('div'); + var cls = CSS_PREFIX + 'size-monitor'; + var maxSize = 1000000; + var style = + 'position:absolute;' + + 'left:0;' + + 'top:0;' + + 'right:0;' + + 'bottom:0;' + + 'overflow:hidden;' + + 'pointer-events:none;' + + 'visibility:hidden;' + + 'z-index:-1;'; + + resizer.style.cssText = style; + resizer.className = cls; + resizer.innerHTML = + '
    ' + + '
    ' + + '
    ' + + '
    ' + + '
    ' + + '
    ' + + '
    ' + + '
    '; + + var expand = resizer.childNodes[0]; + var shrink = resizer.childNodes[1]; + + resizer._reset = function () { + expand.scrollLeft = maxSize; + expand.scrollTop = maxSize; + shrink.scrollLeft = maxSize; + shrink.scrollTop = maxSize; + }; + var onScroll = function () { + resizer._reset(); + handler(); + }; + + addEventListener(expand, 'scroll', onScroll.bind(expand, 'expand')); + addEventListener(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.each(ANIMATION_START_EVENTS, function (type) { + addEventListener(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.each(ANIMATION_START_EVENTS, function (type) { + removeEventListener(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) { + return 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); + } + } + + function injectCSS(platform, css) { + // http://stackoverflow.com/q/3922139 + var style = platform._style || document.createElement('style'); + if (!platform._style) { + platform._style = style; + css = '/* Chart.js */\n' + css; + style.setAttribute('type', 'text/css'); + document.getElementsByTagName('head')[0].appendChild(style); + } + + style.appendChild(document.createTextNode(css)); + } + + module.exports = { + /** + * This property holds whether this platform is enabled for the current environment. + * Currently used by platform.js to select the proper implementation. + * @private + */ + _enabled: typeof window !== 'undefined' && typeof document !== 'undefined', + + initialize: function () { + var keyframes = 'from{opacity:0.99}to{opacity:1}'; + + injectCSS(this, + // DOM rendering detection + // https://davidwalsh.name/detect-node-insertion + '@-webkit-keyframes ' + CSS_RENDER_ANIMATION + '{' + keyframes + '}' + + '@keyframes ' + CSS_RENDER_ANIMATION + '{' + keyframes + '}' + + '.' + CSS_RENDER_MONITOR + '{' + + '-webkit-animation:' + CSS_RENDER_ANIMATION + ' 0.001s;' + + 'animation:' + CSS_RENDER_ANIMATION + ' 0.001s;' + + '}' + ); + }, + + acquireContext: function (item, config) { + if (typeof item === 'string') { + item = document.getElementById(item); + } else if (item.length) { + // Support for array based queries (such as jQuery) + item = item[0]; + } + + if (item && item.canvas) { + // Support for any object associated to a canvas (including a context2d) + item = item.canvas; + } + + // To prevent canvas fingerprinting, some add-ons undefine the getContext + // method, for example: https://github.com/kkapsner/CanvasBlocker + // https://github.com/chartjs/Chart.js/issues/2807 + var context = item && item.getContext && item.getContext('2d'); + + // `instanceof HTMLCanvasElement/CanvasRenderingContext2D` fails when the item is + // inside an iframe or when running in a protected environment. We could guess the + // types from their toString() value but let's keep things flexible and assume it's + // a sufficient condition if the item has a context2D which has item as `canvas`. + // https://github.com/chartjs/Chart.js/issues/3887 + // https://github.com/chartjs/Chart.js/issues/4102 + // https://github.com/chartjs/Chart.js/issues/4152 + if (context && context.canvas === item) { + initCanvas(item, config); + return context; + } + + return null; + }, + + releaseContext: function (context) { + var canvas = context.canvas; + if (!canvas[EXPANDO_KEY]) { + return; + } + + var initial = canvas[EXPANDO_KEY].initial; + ['height', 'width'].forEach(function (prop) { + var value = initial[prop]; + if (helpers.isNullOrUndef(value)) { + canvas.removeAttribute(prop); + } else { + canvas.setAttribute(prop, value); + } + }); + + helpers.each(initial.style || {}, function (value, key) { + canvas.style[key] = value; + }); + + // The canvas render size might have been changed (and thus the state stack discarded), + // we can't use save() and restore() to restore the initial state. So make sure that at + // least the canvas context is reset to the default state by setting the canvas width. + // https://www.w3.org/TR/2011/WD-html5-20110525/the-canvas-element.html + canvas.width = canvas.width; + + delete canvas[EXPANDO_KEY]; + }, + + addEventListener: function (chart, type, listener) { + var canvas = chart.canvas; + if (type === 'resize') { + // Note: the resize event is not supported on all browsers. + addResizeListener(canvas, listener, chart); + return; + } + + var expando = listener[EXPANDO_KEY] || (listener[EXPANDO_KEY] = {}); + var proxies = expando.proxies || (expando.proxies = {}); + var proxy = proxies[chart.id + '_' + type] = function (event) { + listener(fromNativeEvent(event, chart)); + }; + + addEventListener(canvas, type, proxy); + }, + + removeEventListener: function (chart, type, listener) { + var canvas = chart.canvas; + if (type === 'resize') { + // Note: the resize event is not supported on all browsers. + removeResizeListener(canvas, listener); + return; + } + + var expando = listener[EXPANDO_KEY] || {}; + var proxies = expando.proxies || {}; + var proxy = proxies[chart.id + '_' + type]; + if (!proxy) { + return; + } + + removeEventListener(canvas, type, proxy); + } + }; + + // DEPRECATIONS + + /** + * Provided for backward compatibility, use EventTarget.addEventListener instead. + * EventTarget.addEventListener compatibility: Chrome, Opera 7, Safari, FF1.5+, IE9+ + * @see https://developer.mozilla.org/en-US/docs/Web/API/EventTarget/addEventListener + * @function Chart.helpers.addEvent + * @deprecated since version 2.7.0 + * @todo remove at version 3 + * @private + */ + helpers.addEvent = addEventListener; + + /** + * Provided for backward compatibility, use EventTarget.removeEventListener instead. + * EventTarget.removeEventListener compatibility: Chrome, Opera 7, Safari, FF1.5+, IE9+ + * @see https://developer.mozilla.org/en-US/docs/Web/API/EventTarget/removeEventListener + * @function Chart.helpers.removeEvent + * @deprecated since version 2.7.0 + * @todo remove at version 3 + * @private + */ + helpers.removeEvent = removeEventListener; + + }, { "45": 45 }], 48: [function (require, module, exports) { + 'use strict'; + + var helpers = require(45); + var basic = require(46); + var dom = require(47); + + // @TODO Make possible to select another platform at build time. + var implementation = dom._enabled ? dom : basic; + + /** + * @namespace Chart.platform + * @see https://chartjs.gitbooks.io/proposals/content/Platform.html + * @since 2.4.0 + */ + module.exports = helpers.extend({ + /** + * @since 2.7.0 + */ + initialize: function () { }, + + /** + * Called at chart construction time, returns a context2d instance implementing + * the [W3C Canvas 2D Context API standard]{@link https://www.w3.org/TR/2dcontext/}. + * @param {*} item - The native item from which to acquire context (platform specific) + * @param {Object} options - The chart options + * @returns {CanvasRenderingContext2D} context2d instance + */ + acquireContext: function () { }, + + /** + * Called at chart destruction time, releases any resources associated to the context + * previously returned by the acquireContext() method. + * @param {CanvasRenderingContext2D} context - The context2d instance + * @returns {Boolean} true if the method succeeded, else false + */ + releaseContext: function () { }, + + /** + * Registers the specified listener on the given chart. + * @param {Chart} chart - Chart from which to listen for event + * @param {String} type - The ({@link IEvent}) type to listen for + * @param {Function} listener - Receives a notification (an object that implements + * the {@link IEvent} interface) when an event of the specified type occurs. + */ + addEventListener: function () { }, + + /** + * Removes the specified listener previously registered with addEventListener. + * @param {Chart} chart -Chart from which to remove the listener + * @param {String} type - The ({@link IEvent}) type to remove + * @param {Function} listener - The listener function to remove from the event target. + */ + removeEventListener: function () { } + + }, implementation); + + /** + * @interface IPlatform + * Allows abstracting platform dependencies away from the chart + * @borrows Chart.platform.acquireContext as acquireContext + * @borrows Chart.platform.releaseContext as releaseContext + * @borrows Chart.platform.addEventListener as addEventListener + * @borrows Chart.platform.removeEventListener as removeEventListener + */ + + /** + * @interface IEvent + * @prop {String} type - The event type name, possible values are: + * 'contextmenu', 'mouseenter', 'mousedown', 'mousemove', 'mouseup', 'mouseout', + * 'click', 'dblclick', 'keydown', 'keypress', 'keyup' and 'resize' + * @prop {*} native - The original native event (null for emulated events, e.g. 'resize') + * @prop {Number} x - The mouse x position, relative to the canvas (null for incompatible events) + * @prop {Number} y - The mouse y position, relative to the canvas (null for incompatible events) + */ + + }, { "45": 45, "46": 46, "47": 47 }], 49: [function (require, module, exports) { + 'use strict'; + + module.exports = {}; + module.exports.filler = require(50); + module.exports.legend = require(51); + module.exports.title = require(52); + + }, { "50": 50, "51": 51, "52": 52 }], 50: [function (require, module, exports) { + /** + * Plugin based on discussion from the following Chart.js issues: + * @see https://github.com/chartjs/Chart.js/issues/2380#issuecomment-279961569 + * @see https://github.com/chartjs/Chart.js/issues/2440#issuecomment-256461897 + */ + + 'use strict'; + + var defaults = require(25); + var elements = require(40); + var helpers = require(45); + + defaults._set('global', { + plugins: { + filler: { + propagate: true + } + } + }); + + var mappers = { + dataset: function (source) { + var index = source.fill; + var chart = source.chart; + var meta = chart.getDatasetMeta(index); + var visible = meta && chart.isDatasetVisible(index); + var points = (visible && meta.dataset._children) || []; + var length = points.length || 0; + + return !length ? null : function (point, i) { + return (i < length && points[i]._view) || null; + }; + }, + + boundary: function (source) { + var boundary = source.boundary; + var x = boundary ? boundary.x : null; + var y = boundary ? boundary.y : null; + + return function (point) { + return { + x: x === null ? point.x : x, + y: y === null ? point.y : y, + }; + }; + } + }; + + // @todo if (fill[0] === '#') + function decodeFill(el, index, count) { + var model = el._model || {}; + var fill = model.fill; + var target; + + if (fill === undefined) { + fill = !!model.backgroundColor; + } + + if (fill === false || fill === null) { + return false; + } + + if (fill === true) { + return 'origin'; + } + + target = parseFloat(fill, 10); + if (isFinite(target) && Math.floor(target) === target) { + if (fill[0] === '-' || fill[0] === '+') { + target = index + target; + } + + if (target === index || target < 0 || target >= count) { + return false; + } + + return target; + } + + switch (fill) { + // compatibility + case 'bottom': + return 'start'; + case 'top': + return 'end'; + case 'zero': + return 'origin'; + // supported boundaries + case 'origin': + case 'start': + case 'end': + return fill; + // invalid fill values + default: + return false; + } + } + + function computeBoundary(source) { + var model = source.el._model || {}; + var scale = source.el._scale || {}; + var fill = source.fill; + var target = null; + var horizontal; + + if (isFinite(fill)) { + return null; + } + + // Backward compatibility: until v3, we still need to support boundary values set on + // the model (scaleTop, scaleBottom and scaleZero) because some external plugins and + // controllers might still use it (e.g. the Smith chart). + + if (fill === 'start') { + target = model.scaleBottom === undefined ? scale.bottom : model.scaleBottom; + } else if (fill === 'end') { + target = model.scaleTop === undefined ? scale.top : model.scaleTop; + } else if (model.scaleZero !== undefined) { + target = model.scaleZero; + } else if (scale.getBasePosition) { + target = scale.getBasePosition(); + } else if (scale.getBasePixel) { + target = scale.getBasePixel(); + } + + if (target !== undefined && target !== null) { + if (target.x !== undefined && target.y !== undefined) { + return target; + } + + if (typeof target === 'number' && isFinite(target)) { + horizontal = scale.isHorizontal(); + return { + x: horizontal ? target : null, + y: horizontal ? null : target + }; + } + } + + return null; + } + + function resolveTarget(sources, index, propagate) { + var source = sources[index]; + var fill = source.fill; + var visited = [index]; + var target; + + if (!propagate) { + return fill; + } + + while (fill !== false && visited.indexOf(fill) === -1) { + if (!isFinite(fill)) { + return fill; + } + + target = sources[fill]; + if (!target) { + return false; + } + + if (target.visible) { + return fill; + } + + visited.push(fill); + fill = target.fill; + } + + return false; + } + + function createMapper(source) { + var fill = source.fill; + var type = 'dataset'; + + if (fill === false) { + return null; + } + + if (!isFinite(fill)) { + type = 'boundary'; + } + + return mappers[type](source); + } + + function isDrawable(point) { + return point && !point.skip; + } + + function drawArea(ctx, curve0, curve1, len0, len1) { + var i; + + if (!len0 || !len1) { + return; + } + + // building first area curve (normal) + ctx.moveTo(curve0[0].x, curve0[0].y); + for (i = 1; i < len0; ++i) { + helpers.canvas.lineTo(ctx, curve0[i - 1], curve0[i]); + } + + // joining the two area curves + ctx.lineTo(curve1[len1 - 1].x, curve1[len1 - 1].y); + + // building opposite area curve (reverse) + for (i = len1 - 1; i > 0; --i) { + helpers.canvas.lineTo(ctx, curve1[i], curve1[i - 1], true); + } + } + + function doFill(ctx, points, mapper, view, color, loop) { + var count = points.length; + var span = view.spanGaps; + var curve0 = []; + var curve1 = []; + var len0 = 0; + var len1 = 0; + var i, ilen, index, p0, p1, d0, d1; + + ctx.beginPath(); + + for (i = 0, ilen = (count + !!loop); i < ilen; ++i) { + index = i % count; + p0 = points[index]._view; + p1 = mapper(p0, index, view); + d0 = isDrawable(p0); + d1 = isDrawable(p1); + + if (d0 && d1) { + len0 = curve0.push(p0); + len1 = curve1.push(p1); + } else if (len0 && len1) { + if (!span) { + drawArea(ctx, curve0, curve1, len0, len1); + len0 = len1 = 0; + curve0 = []; + curve1 = []; + } else { + if (d0) { + curve0.push(p0); + } + if (d1) { + curve1.push(p1); + } + } + } + } + + drawArea(ctx, curve0, curve1, len0, len1); + + ctx.closePath(); + ctx.fillStyle = color; + ctx.fill(); + } + + module.exports = { + id: 'filler', + + afterDatasetsUpdate: function (chart, options) { + var count = (chart.data.datasets || []).length; + var propagate = options.propagate; + var sources = []; + var meta, i, el, source; + + for (i = 0; i < count; ++i) { + meta = chart.getDatasetMeta(i); + el = meta.dataset; + source = null; + + if (el && el._model && el instanceof elements.Line) { + source = { + visible: chart.isDatasetVisible(i), + fill: decodeFill(el, i, count), + chart: chart, + el: el + }; + } + + meta.$filler = source; + sources.push(source); + } + + for (i = 0; i < count; ++i) { + source = sources[i]; + if (!source) { + continue; + } + + source.fill = resolveTarget(sources, i, propagate); + source.boundary = computeBoundary(source); + source.mapper = createMapper(source); + } + }, + + beforeDatasetDraw: function (chart, args) { + var meta = args.meta.$filler; + if (!meta) { + return; + } + + var ctx = chart.ctx; + var el = meta.el; + var view = el._view; + var points = el._children || []; + var mapper = meta.mapper; + var color = view.backgroundColor || defaults.global.defaultColor; + + if (mapper && color && points.length) { + helpers.canvas.clipArea(ctx, chart.chartArea); + doFill(ctx, points, mapper, view, color, el._loop); + helpers.canvas.unclipArea(ctx); + } + } + }; + + }, { "25": 25, "40": 40, "45": 45 }], 51: [function (require, module, exports) { + 'use strict'; + + var defaults = require(25); + var Element = require(26); + var helpers = require(45); + var layouts = require(30); + + var noop = helpers.noop; + + defaults._set('global', { + legend: { + display: true, + position: 'top', + fullWidth: true, + reverse: false, + weight: 1000, + + // a callback that will handle + onClick: function (e, legendItem) { + var index = legendItem.datasetIndex; + var ci = this.chart; + var meta = ci.getDatasetMeta(index); + + // See controller.isDatasetVisible comment + meta.hidden = meta.hidden === null ? !ci.data.datasets[index].hidden : null; + + // We hid a dataset ... rerender the chart + ci.update(); + }, + + onHover: null, + + labels: { + boxWidth: 40, + padding: 10, + // Generates labels shown in the legend + // Valid properties to return: + // text : text to display + // fillStyle : fill of coloured box + // strokeStyle: stroke of coloured box + // hidden : if this legend item refers to a hidden item + // lineCap : cap style for line + // lineDash + // lineDashOffset : + // lineJoin : + // lineWidth : + generateLabels: function (chart) { + var data = chart.data; + return helpers.isArray(data.datasets) ? data.datasets.map(function (dataset, i) { + return { + text: dataset.label, + fillStyle: (!helpers.isArray(dataset.backgroundColor) ? dataset.backgroundColor : dataset.backgroundColor[0]), + hidden: !chart.isDatasetVisible(i), + lineCap: dataset.borderCapStyle, + lineDash: dataset.borderDash, + lineDashOffset: dataset.borderDashOffset, + lineJoin: dataset.borderJoinStyle, + lineWidth: dataset.borderWidth, + strokeStyle: dataset.borderColor, + pointStyle: dataset.pointStyle, + + // Below is extra data used for toggling the datasets + datasetIndex: i + }; + }, this) : []; + } + } + }, + + legendCallback: function (chart) { + var text = []; + text.push('
      '); + for (var i = 0; i < chart.data.datasets.length; i++) { + text.push('
    • '); + if (chart.data.datasets[i].label) { + text.push(chart.data.datasets[i].label); + } + text.push('
    • '); + } + text.push('
    '); + return text.join(''); + } + }); + + /** + * Helper function to get the box width based on the usePointStyle option + * @param labelopts {Object} the label options on the legend + * @param fontSize {Number} the label font size + * @return {Number} width of the color box area + */ + function getBoxWidth(labelOpts, fontSize) { + return labelOpts.usePointStyle ? + fontSize * Math.SQRT2 : + labelOpts.boxWidth; + } + + /** + * IMPORTANT: this class is exposed publicly as Chart.Legend, backward compatibility required! + */ + var Legend = Element.extend({ + + initialize: function (config) { + helpers.extend(this, config); + + // Contains hit boxes for each dataset (in dataset order) + this.legendHitBoxes = []; + + // Are we in doughnut mode which has a different data type + this.doughnutMode = false; + }, + + // These methods are ordered by lifecycle. Utilities then follow. + // Any function defined here is inherited by all legend types. + // Any function can be extended by the legend type + + beforeUpdate: noop, + update: function (maxWidth, maxHeight, margins) { + var me = this; + + // Update Lifecycle - Probably don't want to ever extend or overwrite this function ;) + me.beforeUpdate(); + + // Absorb the master measurements + me.maxWidth = maxWidth; + me.maxHeight = maxHeight; + me.margins = margins; + + // Dimensions + me.beforeSetDimensions(); + me.setDimensions(); + me.afterSetDimensions(); + // Labels + me.beforeBuildLabels(); + me.buildLabels(); + me.afterBuildLabels(); + + // Fit + me.beforeFit(); + me.fit(); + me.afterFit(); + // + me.afterUpdate(); + + return me.minSize; + }, + afterUpdate: noop, + + // + + beforeSetDimensions: noop, + setDimensions: function () { + var me = this; + // Set the unconstrained dimension before label rotation + if (me.isHorizontal()) { + // Reset position before calculating rotation + me.width = me.maxWidth; + me.left = 0; + me.right = me.width; + } else { + me.height = me.maxHeight; + + // Reset position before calculating rotation + me.top = 0; + me.bottom = me.height; + } + + // Reset padding + me.paddingLeft = 0; + me.paddingTop = 0; + me.paddingRight = 0; + me.paddingBottom = 0; + + // Reset minSize + me.minSize = { + width: 0, + height: 0 + }; + }, + afterSetDimensions: noop, + + // + + beforeBuildLabels: noop, + buildLabels: function () { + var me = this; + var labelOpts = me.options.labels || {}; + var legendItems = helpers.callback(labelOpts.generateLabels, [me.chart], me) || []; + + if (labelOpts.filter) { + legendItems = legendItems.filter(function (item) { + return labelOpts.filter(item, me.chart.data); + }); + } + + if (me.options.reverse) { + legendItems.reverse(); + } + + me.legendItems = legendItems; + }, + afterBuildLabels: noop, + + // + + beforeFit: noop, + fit: function () { + var me = this; + var opts = me.options; + var labelOpts = opts.labels; + var display = opts.display; + + var ctx = me.ctx; + + var globalDefault = defaults.global; + var valueOrDefault = helpers.valueOrDefault; + var fontSize = valueOrDefault(labelOpts.fontSize, globalDefault.defaultFontSize); + var fontStyle = valueOrDefault(labelOpts.fontStyle, globalDefault.defaultFontStyle); + var fontFamily = valueOrDefault(labelOpts.fontFamily, globalDefault.defaultFontFamily); + var labelFont = helpers.fontString(fontSize, fontStyle, fontFamily); + + // Reset hit boxes + var hitboxes = me.legendHitBoxes = []; + + var minSize = me.minSize; + var isHorizontal = me.isHorizontal(); + + if (isHorizontal) { + minSize.width = me.maxWidth; // fill all the width + minSize.height = display ? 10 : 0; + } else { + minSize.width = display ? 10 : 0; + minSize.height = me.maxHeight; // fill all the height + } + + // Increase sizes here + if (display) { + ctx.font = labelFont; + + if (isHorizontal) { + // Labels + + // Width of each line of legend boxes. Labels wrap onto multiple lines when there are too many to fit on one + var lineWidths = me.lineWidths = [0]; + var totalHeight = me.legendItems.length ? fontSize + (labelOpts.padding) : 0; + + ctx.textAlign = 'left'; + ctx.textBaseline = 'top'; + + helpers.each(me.legendItems, function (legendItem, i) { + var boxWidth = getBoxWidth(labelOpts, fontSize); + var width = boxWidth + (fontSize / 2) + ctx.measureText(legendItem.text).width; + + if (lineWidths[lineWidths.length - 1] + width + labelOpts.padding >= me.width) { + totalHeight += fontSize + (labelOpts.padding); + lineWidths[lineWidths.length] = me.left; + } + + // Store the hitbox width and height here. Final position will be updated in `draw` + hitboxes[i] = { + left: 0, + top: 0, + width: width, + height: fontSize + }; + + lineWidths[lineWidths.length - 1] += width + labelOpts.padding; + }); + + minSize.height += totalHeight; + + } else { + var vPadding = labelOpts.padding; + var columnWidths = me.columnWidths = []; + var totalWidth = labelOpts.padding; + var currentColWidth = 0; + var currentColHeight = 0; + var itemHeight = fontSize + vPadding; + + helpers.each(me.legendItems, function (legendItem, i) { + var boxWidth = getBoxWidth(labelOpts, fontSize); + var itemWidth = boxWidth + (fontSize / 2) + ctx.measureText(legendItem.text).width; + + // If too tall, go to new column + if (currentColHeight + itemHeight > minSize.height) { + totalWidth += currentColWidth + labelOpts.padding; + columnWidths.push(currentColWidth); // previous column width + + currentColWidth = 0; + currentColHeight = 0; + } + + // Get max width + currentColWidth = Math.max(currentColWidth, itemWidth); + currentColHeight += itemHeight; + + // Store the hitbox width and height here. Final position will be updated in `draw` + hitboxes[i] = { + left: 0, + top: 0, + width: itemWidth, + height: fontSize + }; + }); + + totalWidth += currentColWidth; + columnWidths.push(currentColWidth); + minSize.width += totalWidth; + } + } + + me.width = minSize.width; + me.height = minSize.height; + }, + afterFit: noop, + + // Shared Methods + isHorizontal: function () { + return this.options.position === 'top' || this.options.position === 'bottom'; + }, + + // Actually draw the legend on the canvas + draw: function () { + var me = this; + var opts = me.options; + var labelOpts = opts.labels; + var globalDefault = defaults.global; + var lineDefault = globalDefault.elements.line; + var legendWidth = me.width; + var lineWidths = me.lineWidths; + + if (opts.display) { + var ctx = me.ctx; + var valueOrDefault = helpers.valueOrDefault; + var fontColor = valueOrDefault(labelOpts.fontColor, globalDefault.defaultFontColor); + var fontSize = valueOrDefault(labelOpts.fontSize, globalDefault.defaultFontSize); + var fontStyle = valueOrDefault(labelOpts.fontStyle, globalDefault.defaultFontStyle); + var fontFamily = valueOrDefault(labelOpts.fontFamily, globalDefault.defaultFontFamily); + var labelFont = helpers.fontString(fontSize, fontStyle, fontFamily); + var cursor; + + // Canvas setup + ctx.textAlign = 'left'; + ctx.textBaseline = 'middle'; + ctx.lineWidth = 0.5; + ctx.strokeStyle = fontColor; // for strikethrough effect + ctx.fillStyle = fontColor; // render in correct colour + ctx.font = labelFont; + + var boxWidth = getBoxWidth(labelOpts, fontSize); + var hitboxes = me.legendHitBoxes; + + // current position + var drawLegendBox = function (x, y, legendItem) { + if (isNaN(boxWidth) || boxWidth <= 0) { + return; + } + + // Set the ctx for the box + ctx.save(); + + ctx.fillStyle = valueOrDefault(legendItem.fillStyle, globalDefault.defaultColor); + ctx.lineCap = valueOrDefault(legendItem.lineCap, lineDefault.borderCapStyle); + ctx.lineDashOffset = valueOrDefault(legendItem.lineDashOffset, lineDefault.borderDashOffset); + ctx.lineJoin = valueOrDefault(legendItem.lineJoin, lineDefault.borderJoinStyle); + ctx.lineWidth = valueOrDefault(legendItem.lineWidth, lineDefault.borderWidth); + ctx.strokeStyle = valueOrDefault(legendItem.strokeStyle, globalDefault.defaultColor); + var isLineWidthZero = (valueOrDefault(legendItem.lineWidth, lineDefault.borderWidth) === 0); + + if (ctx.setLineDash) { + // IE 9 and 10 do not support line dash + ctx.setLineDash(valueOrDefault(legendItem.lineDash, lineDefault.borderDash)); + } + + if (opts.labels && opts.labels.usePointStyle) { + // Recalculate x and y for drawPoint() because its expecting + // x and y to be center of figure (instead of top left) + var radius = fontSize * Math.SQRT2 / 2; + var offSet = radius / Math.SQRT2; + var centerX = x + offSet; + var centerY = y + offSet; + + // Draw pointStyle as legend symbol + helpers.canvas.drawPoint(ctx, legendItem.pointStyle, radius, centerX, centerY); + } else { + // Draw box as legend symbol + if (!isLineWidthZero) { + ctx.strokeRect(x, y, boxWidth, fontSize); + } + ctx.fillRect(x, y, boxWidth, fontSize); + } + + ctx.restore(); + }; + var fillText = function (x, y, legendItem, textWidth) { + var halfFontSize = fontSize / 2; + var xLeft = boxWidth + halfFontSize + x; + var yMiddle = y + halfFontSize; + + ctx.fillText(legendItem.text, xLeft, yMiddle); + + if (legendItem.hidden) { + // Strikethrough the text if hidden + ctx.beginPath(); + ctx.lineWidth = 2; + ctx.moveTo(xLeft, yMiddle); + ctx.lineTo(xLeft + textWidth, yMiddle); + ctx.stroke(); + } + }; + + // Horizontal + var isHorizontal = me.isHorizontal(); + if (isHorizontal) { + cursor = { + x: me.left + ((legendWidth - lineWidths[0]) / 2), + y: me.top + labelOpts.padding, + line: 0 + }; + } else { + cursor = { + x: me.left + labelOpts.padding, + y: me.top + labelOpts.padding, + line: 0 + }; + } + + var itemHeight = fontSize + labelOpts.padding; + helpers.each(me.legendItems, function (legendItem, i) { + var textWidth = ctx.measureText(legendItem.text).width; + var width = boxWidth + (fontSize / 2) + textWidth; + var x = cursor.x; + var y = cursor.y; + + if (isHorizontal) { + if (x + width >= legendWidth) { + y = cursor.y += itemHeight; + cursor.line++; + x = cursor.x = me.left + ((legendWidth - lineWidths[cursor.line]) / 2); + } + } else if (y + itemHeight > me.bottom) { + x = cursor.x = x + me.columnWidths[cursor.line] + labelOpts.padding; + y = cursor.y = me.top + labelOpts.padding; + cursor.line++; + } + + drawLegendBox(x, y, legendItem); + + hitboxes[i].left = x; + hitboxes[i].top = y; + + // Fill the actual label + fillText(x, y, legendItem, textWidth); + + if (isHorizontal) { + cursor.x += width + (labelOpts.padding); + } else { + cursor.y += itemHeight; + } + + }); + } + }, + + /** + * Handle an event + * @private + * @param {IEvent} event - The event to handle + * @return {Boolean} true if a change occured + */ + handleEvent: function (e) { + var me = this; + var opts = me.options; + var type = e.type === 'mouseup' ? 'click' : e.type; + var changed = false; + + if (type === 'mousemove') { + if (!opts.onHover) { + return; + } + } else if (type === 'click') { + if (!opts.onClick) { + return; + } + } else { + return; + } + + // Chart event already has relative position in it + var x = e.x; + var y = e.y; + + if (x >= me.left && x <= me.right && y >= me.top && y <= me.bottom) { + // See if we are touching one of the dataset boxes + var lh = me.legendHitBoxes; + for (var i = 0; i < lh.length; ++i) { + var hitBox = lh[i]; + + if (x >= hitBox.left && x <= hitBox.left + hitBox.width && y >= hitBox.top && y <= hitBox.top + hitBox.height) { + // Touching an element + if (type === 'click') { + // use e.native for backwards compatibility + opts.onClick.call(me, e.native, me.legendItems[i]); + changed = true; + break; + } else if (type === 'mousemove') { + // use e.native for backwards compatibility + opts.onHover.call(me, e.native, me.legendItems[i]); + changed = true; + break; + } + } + } + } + + return changed; + } + }); + + function createNewLegendAndAttach(chart, legendOpts) { + var legend = new Legend({ + ctx: chart.ctx, + options: legendOpts, + chart: chart + }); + + layouts.configure(chart, legend, legendOpts); + layouts.addBox(chart, legend); + chart.legend = legend; + } + + module.exports = { + id: 'legend', + + /** + * Backward compatibility: since 2.1.5, the legend is registered as a plugin, making + * Chart.Legend obsolete. To avoid a breaking change, we export the Legend as part of + * the plugin, which one will be re-exposed in the chart.js file. + * https://github.com/chartjs/Chart.js/pull/2640 + * @private + */ + _element: Legend, + + beforeInit: function (chart) { + var legendOpts = chart.options.legend; + + if (legendOpts) { + createNewLegendAndAttach(chart, legendOpts); + } + }, + + beforeUpdate: function (chart) { + var legendOpts = chart.options.legend; + var legend = chart.legend; + + if (legendOpts) { + helpers.mergeIf(legendOpts, defaults.global.legend); + + if (legend) { + layouts.configure(chart, legend, legendOpts); + legend.options = legendOpts; + } else { + createNewLegendAndAttach(chart, legendOpts); + } + } else if (legend) { + layouts.removeBox(chart, legend); + delete chart.legend; + } + }, + + afterEvent: function (chart, e) { + var legend = chart.legend; + if (legend) { + legend.handleEvent(e); + } + } + }; + + }, { "25": 25, "26": 26, "30": 30, "45": 45 }], 52: [function (require, module, exports) { + 'use strict'; + + var defaults = require(25); + var Element = require(26); + var helpers = require(45); + var layouts = require(30); + + var noop = helpers.noop; + + defaults._set('global', { + title: { + display: false, + fontStyle: 'bold', + fullWidth: true, + lineHeight: 1.2, + padding: 10, + position: 'top', + text: '', + weight: 2000 // by default greater than legend (1000) to be above + } + }); + + /** + * IMPORTANT: this class is exposed publicly as Chart.Legend, backward compatibility required! + */ + var Title = Element.extend({ + initialize: function (config) { + var me = this; + helpers.extend(me, config); + + // Contains hit boxes for each dataset (in dataset order) + me.legendHitBoxes = []; + }, + + // These methods are ordered by lifecycle. Utilities then follow. + + beforeUpdate: noop, + update: function (maxWidth, maxHeight, margins) { + var me = this; + + // Update Lifecycle - Probably don't want to ever extend or overwrite this function ;) + me.beforeUpdate(); + + // Absorb the master measurements + me.maxWidth = maxWidth; + me.maxHeight = maxHeight; + me.margins = margins; + + // Dimensions + me.beforeSetDimensions(); + me.setDimensions(); + me.afterSetDimensions(); + // Labels + me.beforeBuildLabels(); + me.buildLabels(); + me.afterBuildLabels(); + + // Fit + me.beforeFit(); + me.fit(); + me.afterFit(); + // + me.afterUpdate(); + + return me.minSize; + + }, + afterUpdate: noop, + + // + + beforeSetDimensions: noop, + setDimensions: function () { + var me = this; + // Set the unconstrained dimension before label rotation + if (me.isHorizontal()) { + // Reset position before calculating rotation + me.width = me.maxWidth; + me.left = 0; + me.right = me.width; + } else { + me.height = me.maxHeight; + + // Reset position before calculating rotation + me.top = 0; + me.bottom = me.height; + } + + // Reset padding + me.paddingLeft = 0; + me.paddingTop = 0; + me.paddingRight = 0; + me.paddingBottom = 0; + + // Reset minSize + me.minSize = { + width: 0, + height: 0 + }; + }, + afterSetDimensions: noop, + + // + + beforeBuildLabels: noop, + buildLabels: noop, + afterBuildLabels: noop, + + // + + beforeFit: noop, + fit: function () { + var me = this; + var valueOrDefault = helpers.valueOrDefault; + var opts = me.options; + var display = opts.display; + var fontSize = valueOrDefault(opts.fontSize, defaults.global.defaultFontSize); + var minSize = me.minSize; + var lineCount = helpers.isArray(opts.text) ? opts.text.length : 1; + var lineHeight = helpers.options.toLineHeight(opts.lineHeight, fontSize); + var textSize = display ? (lineCount * lineHeight) + (opts.padding * 2) : 0; + + if (me.isHorizontal()) { + minSize.width = me.maxWidth; // fill all the width + minSize.height = textSize; + } else { + minSize.width = textSize; + minSize.height = me.maxHeight; // fill all the height + } + + me.width = minSize.width; + me.height = minSize.height; + + }, + afterFit: noop, + + // Shared Methods + isHorizontal: function () { + var pos = this.options.position; + return pos === 'top' || pos === 'bottom'; + }, + + // Actually draw the title block on the canvas + draw: function () { + var me = this; + var ctx = me.ctx; + var valueOrDefault = helpers.valueOrDefault; + var opts = me.options; + var globalDefaults = defaults.global; + + if (opts.display) { + var fontSize = valueOrDefault(opts.fontSize, globalDefaults.defaultFontSize); + var fontStyle = valueOrDefault(opts.fontStyle, globalDefaults.defaultFontStyle); + var fontFamily = valueOrDefault(opts.fontFamily, globalDefaults.defaultFontFamily); + var titleFont = helpers.fontString(fontSize, fontStyle, fontFamily); + var lineHeight = helpers.options.toLineHeight(opts.lineHeight, fontSize); + var offset = lineHeight / 2 + opts.padding; + var rotation = 0; + var top = me.top; + var left = me.left; + var bottom = me.bottom; + var right = me.right; + var maxWidth, titleX, titleY; + + ctx.fillStyle = valueOrDefault(opts.fontColor, globalDefaults.defaultFontColor); // render in correct colour + ctx.font = titleFont; + + // Horizontal + if (me.isHorizontal()) { + titleX = left + ((right - left) / 2); // midpoint of the width + titleY = top + offset; + maxWidth = right - left; + } else { + titleX = opts.position === 'left' ? left + offset : right - offset; + titleY = top + ((bottom - top) / 2); + maxWidth = bottom - top; + rotation = Math.PI * (opts.position === 'left' ? -0.5 : 0.5); + } + + ctx.save(); + ctx.translate(titleX, titleY); + ctx.rotate(rotation); + ctx.textAlign = 'center'; + ctx.textBaseline = 'middle'; + + var text = opts.text; + if (helpers.isArray(text)) { + var y = 0; + for (var i = 0; i < text.length; ++i) { + ctx.fillText(text[i], 0, y, maxWidth); + y += lineHeight; + } + } else { + ctx.fillText(text, 0, 0, maxWidth); + } + + ctx.restore(); + } + } + }); + + function createNewTitleBlockAndAttach(chart, titleOpts) { + var title = new Title({ + ctx: chart.ctx, + options: titleOpts, + chart: chart + }); + + layouts.configure(chart, title, titleOpts); + layouts.addBox(chart, title); + chart.titleBlock = title; + } + + module.exports = { + id: 'title', + + /** + * Backward compatibility: since 2.1.5, the title is registered as a plugin, making + * Chart.Title obsolete. To avoid a breaking change, we export the Title as part of + * the plugin, which one will be re-exposed in the chart.js file. + * https://github.com/chartjs/Chart.js/pull/2640 + * @private + */ + _element: Title, + + beforeInit: function (chart) { + var titleOpts = chart.options.title; + + if (titleOpts) { + createNewTitleBlockAndAttach(chart, titleOpts); + } + }, + + beforeUpdate: function (chart) { + var titleOpts = chart.options.title; + var titleBlock = chart.titleBlock; + + if (titleOpts) { + helpers.mergeIf(titleOpts, defaults.global.title); + + if (titleBlock) { + layouts.configure(chart, titleBlock, titleOpts); + titleBlock.options = titleOpts; + } else { + createNewTitleBlockAndAttach(chart, titleOpts); + } + } else if (titleBlock) { + layouts.removeBox(chart, titleBlock); + delete chart.titleBlock; + } + } + }; + + }, { "25": 25, "26": 26, "30": 30, "45": 45 }], 53: [function (require, module, exports) { + 'use strict'; + + module.exports = function (Chart) { + + // Default config for a category scale + var defaultConfig = { + position: 'bottom' + }; + + var DatasetScale = Chart.Scale.extend({ + /** + * Internal function to get the correct labels. If data.xLabels or data.yLabels are defined, use those + * else fall back to data.labels + * @private + */ + getLabels: function () { + var data = this.chart.data; + return this.options.labels || (this.isHorizontal() ? data.xLabels : data.yLabels) || data.labels; + }, + + determineDataLimits: function () { + var me = this; + var labels = me.getLabels(); + me.minIndex = 0; + me.maxIndex = labels.length - 1; + var findIndex; + + if (me.options.ticks.min !== undefined) { + // user specified min value + findIndex = labels.indexOf(me.options.ticks.min); + me.minIndex = findIndex !== -1 ? findIndex : me.minIndex; + } + + if (me.options.ticks.max !== undefined) { + // user specified max value + findIndex = labels.indexOf(me.options.ticks.max); + me.maxIndex = findIndex !== -1 ? findIndex : me.maxIndex; + } + + me.min = labels[me.minIndex]; + me.max = labels[me.maxIndex]; + }, + + buildTicks: function () { + var me = this; + var labels = me.getLabels(); + // If we are viewing some subset of labels, slice the original array + me.ticks = (me.minIndex === 0 && me.maxIndex === labels.length - 1) ? labels : labels.slice(me.minIndex, me.maxIndex + 1); + }, + + getLabelForIndex: function (index, datasetIndex) { + var me = this; + var data = me.chart.data; + var isHorizontal = me.isHorizontal(); + + if (data.yLabels && !isHorizontal) { + return me.getRightValue(data.datasets[datasetIndex].data[index]); + } + return me.ticks[index - me.minIndex]; + }, + + // Used to get data value locations. Value can either be an index or a numerical value + getPixelForValue: function (value, index) { + var me = this; + var offset = me.options.offset; + // 1 is added because we need the length but we have the indexes + var offsetAmt = Math.max((me.maxIndex + 1 - me.minIndex - (offset ? 0 : 1)), 1); + + // If value is a data object, then index is the index in the data array, + // not the index of the scale. We need to change that. + var valueCategory; + if (value !== undefined && value !== null) { + valueCategory = me.isHorizontal() ? value.x : value.y; + } + if (valueCategory !== undefined || (value !== undefined && isNaN(index))) { + var labels = me.getLabels(); + value = valueCategory || value; + var idx = labels.indexOf(value); + index = idx !== -1 ? idx : index; + } + + if (me.isHorizontal()) { + var valueWidth = me.width / offsetAmt; + var widthOffset = (valueWidth * (index - me.minIndex)); + + if (offset) { + widthOffset += (valueWidth / 2); + } + + return me.left + Math.round(widthOffset); + } + var valueHeight = me.height / offsetAmt; + var heightOffset = (valueHeight * (index - me.minIndex)); + + if (offset) { + heightOffset += (valueHeight / 2); + } + + return me.top + Math.round(heightOffset); + }, + getPixelForTick: function (index) { + return this.getPixelForValue(this.ticks[index], index + this.minIndex, null); + }, + getValueForPixel: function (pixel) { + var me = this; + var offset = me.options.offset; + var value; + var offsetAmt = Math.max((me._ticks.length - (offset ? 0 : 1)), 1); + var horz = me.isHorizontal(); + var valueDimension = (horz ? me.width : me.height) / offsetAmt; + + pixel -= horz ? me.left : me.top; + + if (offset) { + pixel -= (valueDimension / 2); + } + + if (pixel <= 0) { + value = 0; + } else { + value = Math.round(pixel / valueDimension); + } + + return value + me.minIndex; + }, + getBasePixel: function () { + return this.bottom; + } + }); + + Chart.scaleService.registerScaleType('category', DatasetScale, defaultConfig); + + }; + + }, {}], 54: [function (require, module, exports) { + 'use strict'; + + var defaults = require(25); + var helpers = require(45); + var Ticks = require(34); + + module.exports = function (Chart) { + + var defaultConfig = { + position: 'left', + ticks: { + callback: Ticks.formatters.linear + } + }; + + var LinearScale = Chart.LinearScaleBase.extend({ + + determineDataLimits: function () { + var me = this; + var opts = me.options; + var chart = me.chart; + var data = chart.data; + var datasets = data.datasets; + var isHorizontal = me.isHorizontal(); + var DEFAULT_MIN = 0; + var DEFAULT_MAX = 1; + + function IDMatches(meta) { + return isHorizontal ? meta.xAxisID === me.id : meta.yAxisID === me.id; + } + + // First Calculate the range + me.min = null; + me.max = null; + + var hasStacks = opts.stacked; + if (hasStacks === undefined) { + helpers.each(datasets, function (dataset, datasetIndex) { + if (hasStacks) { + return; + } + + var meta = chart.getDatasetMeta(datasetIndex); + if (chart.isDatasetVisible(datasetIndex) && IDMatches(meta) && + meta.stack !== undefined) { + hasStacks = true; + } + }); + } + + if (opts.stacked || hasStacks) { + var valuesPerStack = {}; + + helpers.each(datasets, function (dataset, datasetIndex) { + var meta = chart.getDatasetMeta(datasetIndex); + var key = [ + meta.type, + // we have a separate stack for stack=undefined datasets when the opts.stacked is undefined + ((opts.stacked === undefined && meta.stack === undefined) ? datasetIndex : ''), + meta.stack + ].join('.'); + + if (valuesPerStack[key] === undefined) { + valuesPerStack[key] = { + positiveValues: [], + negativeValues: [] + }; + } + + // Store these per type + var positiveValues = valuesPerStack[key].positiveValues; + var negativeValues = valuesPerStack[key].negativeValues; + + if (chart.isDatasetVisible(datasetIndex) && IDMatches(meta)) { + helpers.each(dataset.data, function (rawValue, index) { + var value = +me.getRightValue(rawValue); + if (isNaN(value) || meta.data[index].hidden) { + return; + } + + positiveValues[index] = positiveValues[index] || 0; + negativeValues[index] = negativeValues[index] || 0; + + if (opts.relativePoints) { + positiveValues[index] = 100; + } else if (value < 0) { + negativeValues[index] += value; + } else { + positiveValues[index] += value; + } + }); + } + }); + + helpers.each(valuesPerStack, function (valuesForType) { + var values = valuesForType.positiveValues.concat(valuesForType.negativeValues); + var minVal = helpers.min(values); + var maxVal = helpers.max(values); + me.min = me.min === null ? minVal : Math.min(me.min, minVal); + me.max = me.max === null ? maxVal : Math.max(me.max, maxVal); + }); + + } else { + helpers.each(datasets, function (dataset, datasetIndex) { + var meta = chart.getDatasetMeta(datasetIndex); + if (chart.isDatasetVisible(datasetIndex) && IDMatches(meta)) { + helpers.each(dataset.data, function (rawValue, index) { + var value = +me.getRightValue(rawValue); + if (isNaN(value) || meta.data[index].hidden) { + return; + } + + if (me.min === null) { + me.min = value; + } else if (value < me.min) { + me.min = value; + } + + if (me.max === null) { + me.max = value; + } else if (value > me.max) { + me.max = value; + } + }); + } + }); + } + + me.min = isFinite(me.min) && !isNaN(me.min) ? me.min : DEFAULT_MIN; + me.max = isFinite(me.max) && !isNaN(me.max) ? me.max : DEFAULT_MAX; + + // Common base implementation to handle ticks.min, ticks.max, ticks.beginAtZero + this.handleTickRangeOptions(); + }, + getTickLimit: function () { + var maxTicks; + var me = this; + var tickOpts = me.options.ticks; + + if (me.isHorizontal()) { + maxTicks = Math.min(tickOpts.maxTicksLimit ? tickOpts.maxTicksLimit : 11, Math.ceil(me.width / 50)); + } else { + // The factor of 2 used to scale the font size has been experimentally determined. + var tickFontSize = helpers.valueOrDefault(tickOpts.fontSize, defaults.global.defaultFontSize); + maxTicks = Math.min(tickOpts.maxTicksLimit ? tickOpts.maxTicksLimit : 11, Math.ceil(me.height / (2 * tickFontSize))); + } + + return maxTicks; + }, + // Called after the ticks are built. We need + handleDirectionalChanges: function () { + if (!this.isHorizontal()) { + // We are in a vertical orientation. The top value is the highest. So reverse the array + this.ticks.reverse(); + } + }, + getLabelForIndex: function (index, datasetIndex) { + return +this.getRightValue(this.chart.data.datasets[datasetIndex].data[index]); + }, + // Utils + getPixelForValue: function (value) { + // This must be called after fit has been run so that + // this.left, this.top, this.right, and this.bottom have been defined + var me = this; + var start = me.start; + + var rightValue = +me.getRightValue(value); + var pixel; + var range = me.end - start; + + if (me.isHorizontal()) { + pixel = me.left + (me.width / range * (rightValue - start)); + } else { + pixel = me.bottom - (me.height / range * (rightValue - start)); + } + return pixel; + }, + getValueForPixel: function (pixel) { + var me = this; + var isHorizontal = me.isHorizontal(); + var innerDimension = isHorizontal ? me.width : me.height; + var offset = (isHorizontal ? pixel - me.left : me.bottom - pixel) / innerDimension; + return me.start + ((me.end - me.start) * offset); + }, + getPixelForTick: function (index) { + return this.getPixelForValue(this.ticksAsNumbers[index]); + } + }); + Chart.scaleService.registerScaleType('linear', LinearScale, defaultConfig); + + }; + + }, { "25": 25, "34": 34, "45": 45 }], 55: [function (require, module, exports) { + 'use strict'; + + var helpers = require(45); + + /** + * Generate a set of linear ticks + * @param generationOptions the options used to generate the ticks + * @param dataRange the range of the data + * @returns {Array} array of tick values + */ + function generateTicks(generationOptions, dataRange) { + var ticks = []; + // To get a "nice" value for the tick spacing, we will use the appropriately named + // "nice number" algorithm. See http://stackoverflow.com/questions/8506881/nice-label-algorithm-for-charts-with-minimum-ticks + // for details. + + var spacing; + if (generationOptions.stepSize && generationOptions.stepSize > 0) { + spacing = generationOptions.stepSize; + } else { + var niceRange = helpers.niceNum(dataRange.max - dataRange.min, false); + spacing = helpers.niceNum(niceRange / (generationOptions.maxTicks - 1), true); + } + var niceMin = Math.floor(dataRange.min / spacing) * spacing; + var niceMax = Math.ceil(dataRange.max / spacing) * spacing; + + // If min, max and stepSize is set and they make an evenly spaced scale use it. + if (generationOptions.min && generationOptions.max && generationOptions.stepSize) { + // If very close to our whole number, use it. + if (helpers.almostWhole((generationOptions.max - generationOptions.min) / generationOptions.stepSize, spacing / 1000)) { + niceMin = generationOptions.min; + niceMax = generationOptions.max; + } + } + + var numSpaces = (niceMax - niceMin) / spacing; + // If very close to our rounded value, use it. + if (helpers.almostEquals(numSpaces, Math.round(numSpaces), spacing / 1000)) { + numSpaces = Math.round(numSpaces); + } else { + numSpaces = Math.ceil(numSpaces); + } + + var precision = 1; + if (spacing < 1) { + precision = Math.pow(10, spacing.toString().length - 2); + niceMin = Math.round(niceMin * precision) / precision; + niceMax = Math.round(niceMax * precision) / precision; + } + ticks.push(generationOptions.min !== undefined ? generationOptions.min : niceMin); + for (var j = 1; j < numSpaces; ++j) { + ticks.push(Math.round((niceMin + j * spacing) * precision) / precision); + } + ticks.push(generationOptions.max !== undefined ? generationOptions.max : niceMax); + + return ticks; + } + + + module.exports = function (Chart) { + + var noop = helpers.noop; + + Chart.LinearScaleBase = Chart.Scale.extend({ + getRightValue: function (value) { + if (typeof value === 'string') { + return +value; + } + return Chart.Scale.prototype.getRightValue.call(this, value); + }, + + handleTickRangeOptions: function () { + var me = this; + var opts = me.options; + var tickOpts = opts.ticks; + + // If we are forcing it to begin at 0, but 0 will already be rendered on the chart, + // do nothing since that would make the chart weird. If the user really wants a weird chart + // axis, they can manually override it + if (tickOpts.beginAtZero) { + var minSign = helpers.sign(me.min); + var maxSign = helpers.sign(me.max); + + if (minSign < 0 && maxSign < 0) { + // move the top up to 0 + me.max = 0; + } else if (minSign > 0 && maxSign > 0) { + // move the bottom down to 0 + me.min = 0; + } + } + + var setMin = tickOpts.min !== undefined || tickOpts.suggestedMin !== undefined; + var setMax = tickOpts.max !== undefined || tickOpts.suggestedMax !== undefined; + + if (tickOpts.min !== undefined) { + me.min = tickOpts.min; + } else if (tickOpts.suggestedMin !== undefined) { + if (me.min === null) { + me.min = tickOpts.suggestedMin; + } else { + me.min = Math.min(me.min, tickOpts.suggestedMin); + } + } + + if (tickOpts.max !== undefined) { + me.max = tickOpts.max; + } else if (tickOpts.suggestedMax !== undefined) { + if (me.max === null) { + me.max = tickOpts.suggestedMax; + } else { + me.max = Math.max(me.max, tickOpts.suggestedMax); + } + } + + if (setMin !== setMax) { + // We set the min or the max but not both. + // So ensure that our range is good + // Inverted or 0 length range can happen when + // ticks.min is set, and no datasets are visible + if (me.min >= me.max) { + if (setMin) { + me.max = me.min + 1; + } else { + me.min = me.max - 1; + } + } + } + + if (me.min === me.max) { + me.max++; + + if (!tickOpts.beginAtZero) { + me.min--; + } + } + }, + getTickLimit: noop, + handleDirectionalChanges: noop, + + buildTicks: function () { + var me = this; + var opts = me.options; + var tickOpts = opts.ticks; + + // Figure out what the max number of ticks we can support it is based on the size of + // the axis area. For now, we say that the minimum tick spacing in pixels must be 50 + // We also limit the maximum number of ticks to 11 which gives a nice 10 squares on + // the graph. Make sure we always have at least 2 ticks + var maxTicks = me.getTickLimit(); + maxTicks = Math.max(2, maxTicks); + + var numericGeneratorOptions = { + maxTicks: maxTicks, + min: tickOpts.min, + max: tickOpts.max, + stepSize: helpers.valueOrDefault(tickOpts.fixedStepSize, tickOpts.stepSize) + }; + var ticks = me.ticks = generateTicks(numericGeneratorOptions, me); + + me.handleDirectionalChanges(); + + // At this point, we need to update our max and min given the tick values since we have expanded the + // range of the scale + me.max = helpers.max(ticks); + me.min = helpers.min(ticks); + + if (tickOpts.reverse) { + ticks.reverse(); + + me.start = me.max; + me.end = me.min; + } else { + me.start = me.min; + me.end = me.max; + } + }, + convertTicksToLabels: function () { + var me = this; + me.ticksAsNumbers = me.ticks.slice(); + me.zeroLineIndex = me.ticks.indexOf(0); + + Chart.Scale.prototype.convertTicksToLabels.call(me); + } + }); + }; + + }, { "45": 45 }], 56: [function (require, module, exports) { + 'use strict'; + + var helpers = require(45); + var Ticks = require(34); + + /** + * Generate a set of logarithmic ticks + * @param generationOptions the options used to generate the ticks + * @param dataRange the range of the data + * @returns {Array} array of tick values + */ + function generateTicks(generationOptions, dataRange) { + var ticks = []; + var valueOrDefault = helpers.valueOrDefault; + + // Figure out what the max number of ticks we can support it is based on the size of + // the axis area. For now, we say that the minimum tick spacing in pixels must be 50 + // We also limit the maximum number of ticks to 11 which gives a nice 10 squares on + // the graph + var tickVal = valueOrDefault(generationOptions.min, Math.pow(10, Math.floor(helpers.log10(dataRange.min)))); + + var endExp = Math.floor(helpers.log10(dataRange.max)); + var endSignificand = Math.ceil(dataRange.max / Math.pow(10, endExp)); + var exp, significand; + + if (tickVal === 0) { + exp = Math.floor(helpers.log10(dataRange.minNotZero)); + significand = Math.floor(dataRange.minNotZero / Math.pow(10, exp)); + + ticks.push(tickVal); + tickVal = significand * Math.pow(10, exp); + } else { + exp = Math.floor(helpers.log10(tickVal)); + significand = Math.floor(tickVal / Math.pow(10, exp)); + } + var precision = exp < 0 ? Math.pow(10, Math.abs(exp)) : 1; + + do { + ticks.push(tickVal); + + ++significand; + if (significand === 10) { + significand = 1; + ++exp; + precision = exp >= 0 ? 1 : precision; + } + + tickVal = Math.round(significand * Math.pow(10, exp) * precision) / precision; + } while (exp < endExp || (exp === endExp && significand < endSignificand)); + + var lastTick = valueOrDefault(generationOptions.max, tickVal); + ticks.push(lastTick); + + return ticks; + } + + + module.exports = function (Chart) { + + var defaultConfig = { + position: 'left', + + // label settings + ticks: { + callback: Ticks.formatters.logarithmic + } + }; + + var LogarithmicScale = Chart.Scale.extend({ + determineDataLimits: function () { + var me = this; + var opts = me.options; + var chart = me.chart; + var data = chart.data; + var datasets = data.datasets; + var isHorizontal = me.isHorizontal(); + function IDMatches(meta) { + return isHorizontal ? meta.xAxisID === me.id : meta.yAxisID === me.id; + } + + // Calculate Range + me.min = null; + me.max = null; + me.minNotZero = null; + + var hasStacks = opts.stacked; + if (hasStacks === undefined) { + helpers.each(datasets, function (dataset, datasetIndex) { + if (hasStacks) { + return; + } + + var meta = chart.getDatasetMeta(datasetIndex); + if (chart.isDatasetVisible(datasetIndex) && IDMatches(meta) && + meta.stack !== undefined) { + hasStacks = true; + } + }); + } + + if (opts.stacked || hasStacks) { + var valuesPerStack = {}; + + helpers.each(datasets, function (dataset, datasetIndex) { + var meta = chart.getDatasetMeta(datasetIndex); + var key = [ + meta.type, + // we have a separate stack for stack=undefined datasets when the opts.stacked is undefined + ((opts.stacked === undefined && meta.stack === undefined) ? datasetIndex : ''), + meta.stack + ].join('.'); + + if (chart.isDatasetVisible(datasetIndex) && IDMatches(meta)) { + if (valuesPerStack[key] === undefined) { + valuesPerStack[key] = []; + } + + helpers.each(dataset.data, function (rawValue, index) { + var values = valuesPerStack[key]; + var value = +me.getRightValue(rawValue); + // invalid, hidden and negative values are ignored + if (isNaN(value) || meta.data[index].hidden || value < 0) { + return; + } + values[index] = values[index] || 0; + values[index] += value; + }); + } + }); + + helpers.each(valuesPerStack, function (valuesForType) { + if (valuesForType.length > 0) { + var minVal = helpers.min(valuesForType); + var maxVal = helpers.max(valuesForType); + me.min = me.min === null ? minVal : Math.min(me.min, minVal); + me.max = me.max === null ? maxVal : Math.max(me.max, maxVal); + } + }); + + } else { + helpers.each(datasets, function (dataset, datasetIndex) { + var meta = chart.getDatasetMeta(datasetIndex); + if (chart.isDatasetVisible(datasetIndex) && IDMatches(meta)) { + helpers.each(dataset.data, function (rawValue, index) { + var value = +me.getRightValue(rawValue); + // invalid, hidden and negative values are ignored + if (isNaN(value) || meta.data[index].hidden || value < 0) { + return; + } + + if (me.min === null) { + me.min = value; + } else if (value < me.min) { + me.min = value; + } + + if (me.max === null) { + me.max = value; + } else if (value > me.max) { + me.max = value; + } + + if (value !== 0 && (me.minNotZero === null || value < me.minNotZero)) { + me.minNotZero = value; + } + }); + } + }); + } + + // Common base implementation to handle ticks.min, ticks.max + this.handleTickRangeOptions(); + }, + handleTickRangeOptions: function () { + var me = this; + var opts = me.options; + var tickOpts = opts.ticks; + var valueOrDefault = helpers.valueOrDefault; + var DEFAULT_MIN = 1; + var DEFAULT_MAX = 10; + + me.min = valueOrDefault(tickOpts.min, me.min); + me.max = valueOrDefault(tickOpts.max, me.max); + + if (me.min === me.max) { + if (me.min !== 0 && me.min !== null) { + me.min = Math.pow(10, Math.floor(helpers.log10(me.min)) - 1); + me.max = Math.pow(10, Math.floor(helpers.log10(me.max)) + 1); + } else { + me.min = DEFAULT_MIN; + me.max = DEFAULT_MAX; + } + } + if (me.min === null) { + me.min = Math.pow(10, Math.floor(helpers.log10(me.max)) - 1); + } + if (me.max === null) { + me.max = me.min !== 0 + ? Math.pow(10, Math.floor(helpers.log10(me.min)) + 1) + : DEFAULT_MAX; + } + if (me.minNotZero === null) { + if (me.min > 0) { + me.minNotZero = me.min; + } else if (me.max < 1) { + me.minNotZero = Math.pow(10, Math.floor(helpers.log10(me.max))); + } else { + me.minNotZero = DEFAULT_MIN; + } + } + }, + buildTicks: function () { + var me = this; + var opts = me.options; + var tickOpts = opts.ticks; + var reverse = !me.isHorizontal(); + + var generationOptions = { + min: tickOpts.min, + max: tickOpts.max + }; + var ticks = me.ticks = generateTicks(generationOptions, me); + + // At this point, we need to update our max and min given the tick values since we have expanded the + // range of the scale + me.max = helpers.max(ticks); + me.min = helpers.min(ticks); + + if (tickOpts.reverse) { + reverse = !reverse; + me.start = me.max; + me.end = me.min; + } else { + me.start = me.min; + me.end = me.max; + } + if (reverse) { + ticks.reverse(); + } + }, + convertTicksToLabels: function () { + this.tickValues = this.ticks.slice(); + + Chart.Scale.prototype.convertTicksToLabels.call(this); + }, + // Get the correct tooltip label + getLabelForIndex: function (index, datasetIndex) { + return +this.getRightValue(this.chart.data.datasets[datasetIndex].data[index]); + }, + getPixelForTick: function (index) { + return this.getPixelForValue(this.tickValues[index]); + }, + /** + * Returns the value of the first tick. + * @param {Number} value - The minimum not zero value. + * @return {Number} The first tick value. + * @private + */ + _getFirstTickValue: function (value) { + var exp = Math.floor(helpers.log10(value)); + var significand = Math.floor(value / Math.pow(10, exp)); + + return significand * Math.pow(10, exp); + }, + getPixelForValue: function (value) { + var me = this; + var reverse = me.options.ticks.reverse; + var log10 = helpers.log10; + var firstTickValue = me._getFirstTickValue(me.minNotZero); + var offset = 0; + var innerDimension, pixel, start, end, sign; + + value = +me.getRightValue(value); + if (reverse) { + start = me.end; + end = me.start; + sign = -1; + } else { + start = me.start; + end = me.end; + sign = 1; + } + if (me.isHorizontal()) { + innerDimension = me.width; + pixel = reverse ? me.right : me.left; + } else { + innerDimension = me.height; + sign *= -1; // invert, since the upper-left corner of the canvas is at pixel (0, 0) + pixel = reverse ? me.top : me.bottom; + } + if (value !== start) { + if (start === 0) { // include zero tick + offset = helpers.getValueOrDefault( + me.options.ticks.fontSize, + Chart.defaults.global.defaultFontSize + ); + innerDimension -= offset; + start = firstTickValue; + } + if (value !== 0) { + offset += innerDimension / (log10(end) - log10(start)) * (log10(value) - log10(start)); + } + pixel += sign * offset; + } + return pixel; + }, + getValueForPixel: function (pixel) { + var me = this; + var reverse = me.options.ticks.reverse; + var log10 = helpers.log10; + var firstTickValue = me._getFirstTickValue(me.minNotZero); + var innerDimension, start, end, value; + + if (reverse) { + start = me.end; + end = me.start; + } else { + start = me.start; + end = me.end; + } + if (me.isHorizontal()) { + innerDimension = me.width; + value = reverse ? me.right - pixel : pixel - me.left; + } else { + innerDimension = me.height; + value = reverse ? pixel - me.top : me.bottom - pixel; + } + if (value !== start) { + if (start === 0) { // include zero tick + var offset = helpers.getValueOrDefault( + me.options.ticks.fontSize, + Chart.defaults.global.defaultFontSize + ); + value -= offset; + innerDimension -= offset; + start = firstTickValue; + } + value *= log10(end) - log10(start); + value /= innerDimension; + value = Math.pow(10, log10(start) + value); + } + return value; + } + }); + Chart.scaleService.registerScaleType('logarithmic', LogarithmicScale, defaultConfig); + + }; + + }, { "34": 34, "45": 45 }], 57: [function (require, module, exports) { + 'use strict'; + + var defaults = require(25); + var helpers = require(45); + var Ticks = require(34); + + module.exports = function (Chart) { + + var globalDefaults = defaults.global; + + var defaultConfig = { + display: true, + + // Boolean - Whether to animate scaling the chart from the centre + animate: true, + position: 'chartArea', + + angleLines: { + display: true, + color: 'rgba(0, 0, 0, 0.1)', + lineWidth: 1 + }, + + gridLines: { + circular: false + }, + + // label settings + ticks: { + // Boolean - Show a backdrop to the scale label + showLabelBackdrop: true, + + // String - The colour of the label backdrop + backdropColor: 'rgba(255,255,255,0.75)', + + // Number - The backdrop padding above & below the label in pixels + backdropPaddingY: 2, + + // Number - The backdrop padding to the side of the label in pixels + backdropPaddingX: 2, + + callback: Ticks.formatters.linear + }, + + pointLabels: { + // Boolean - if true, show point labels + display: true, + + // Number - Point label font size in pixels + fontSize: 10, + + // Function - Used to convert point labels + callback: function (label) { + return label; + } + } + }; + + function getValueCount(scale) { + var opts = scale.options; + return opts.angleLines.display || opts.pointLabels.display ? scale.chart.data.labels.length : 0; + } + + function getPointLabelFontOptions(scale) { + var pointLabelOptions = scale.options.pointLabels; + var fontSize = helpers.valueOrDefault(pointLabelOptions.fontSize, globalDefaults.defaultFontSize); + var fontStyle = helpers.valueOrDefault(pointLabelOptions.fontStyle, globalDefaults.defaultFontStyle); + var fontFamily = helpers.valueOrDefault(pointLabelOptions.fontFamily, globalDefaults.defaultFontFamily); + var font = helpers.fontString(fontSize, fontStyle, fontFamily); + + return { + size: fontSize, + style: fontStyle, + family: fontFamily, + font: font + }; + } + + function measureLabelSize(ctx, fontSize, label) { + if (helpers.isArray(label)) { + return { + w: helpers.longestText(ctx, ctx.font, label), + h: (label.length * fontSize) + ((label.length - 1) * 1.5 * fontSize) + }; + } + + return { + w: ctx.measureText(label).width, + h: fontSize + }; + } + + function determineLimits(angle, pos, size, min, max) { + if (angle === min || angle === max) { + return { + start: pos - (size / 2), + end: pos + (size / 2) + }; + } else if (angle < min || angle > max) { + return { + start: pos - size - 5, + end: pos + }; + } + + return { + start: pos, + end: pos + size + 5 + }; + } + + /** + * Helper function to fit a radial linear scale with point labels + */ + function fitWithPointLabels(scale) { + /* + * Right, this is really confusing and there is a lot of maths going on here + * The gist of the problem is here: https://gist.github.com/nnnick/696cc9c55f4b0beb8fe9 + * + * Reaction: https://dl.dropboxusercontent.com/u/34601363/toomuchscience.gif + * + * Solution: + * + * We assume the radius of the polygon is half the size of the canvas at first + * at each index we check if the text overlaps. + * + * Where it does, we store that angle and that index. + * + * After finding the largest index and angle we calculate how much we need to remove + * from the shape radius to move the point inwards by that x. + * + * We average the left and right distances to get the maximum shape radius that can fit in the box + * along with labels. + * + * Once we have that, we can find the centre point for the chart, by taking the x text protrusion + * on each side, removing that from the size, halving it and adding the left x protrusion width. + * + * This will mean we have a shape fitted to the canvas, as large as it can be with the labels + * and position it in the most space efficient manner + * + * https://dl.dropboxusercontent.com/u/34601363/yeahscience.gif + */ + + var plFont = getPointLabelFontOptions(scale); + + // Get maximum radius of the polygon. Either half the height (minus the text width) or half the width. + // Use this to calculate the offset + change. - Make sure L/R protrusion is at least 0 to stop issues with centre points + var largestPossibleRadius = Math.min(scale.height / 2, scale.width / 2); + var furthestLimits = { + r: scale.width, + l: 0, + t: scale.height, + b: 0 + }; + var furthestAngles = {}; + var i, textSize, pointPosition; + + scale.ctx.font = plFont.font; + scale._pointLabelSizes = []; + + var valueCount = getValueCount(scale); + for (i = 0; i < valueCount; i++) { + pointPosition = scale.getPointPosition(i, largestPossibleRadius); + textSize = measureLabelSize(scale.ctx, plFont.size, scale.pointLabels[i] || ''); + scale._pointLabelSizes[i] = textSize; + + // Add quarter circle to make degree 0 mean top of circle + var angleRadians = scale.getIndexAngle(i); + var angle = helpers.toDegrees(angleRadians) % 360; + var hLimits = determineLimits(angle, pointPosition.x, textSize.w, 0, 180); + var vLimits = determineLimits(angle, pointPosition.y, textSize.h, 90, 270); + + if (hLimits.start < furthestLimits.l) { + furthestLimits.l = hLimits.start; + furthestAngles.l = angleRadians; + } + + if (hLimits.end > furthestLimits.r) { + furthestLimits.r = hLimits.end; + furthestAngles.r = angleRadians; + } + + if (vLimits.start < furthestLimits.t) { + furthestLimits.t = vLimits.start; + furthestAngles.t = angleRadians; + } + + if (vLimits.end > furthestLimits.b) { + furthestLimits.b = vLimits.end; + furthestAngles.b = angleRadians; + } + } + + scale.setReductions(largestPossibleRadius, furthestLimits, furthestAngles); + } + + /** + * Helper function to fit a radial linear scale with no point labels + */ + function fit(scale) { + var largestPossibleRadius = Math.min(scale.height / 2, scale.width / 2); + scale.drawingArea = Math.round(largestPossibleRadius); + scale.setCenterPoint(0, 0, 0, 0); + } + + function getTextAlignForAngle(angle) { + if (angle === 0 || angle === 180) { + return 'center'; + } else if (angle < 180) { + return 'left'; + } + + return 'right'; + } + + function fillText(ctx, text, position, fontSize) { + if (helpers.isArray(text)) { + var y = position.y; + var spacing = 1.5 * fontSize; + + for (var i = 0; i < text.length; ++i) { + ctx.fillText(text[i], position.x, y); + y += spacing; + } + } else { + ctx.fillText(text, position.x, position.y); + } + } + + function adjustPointPositionForLabelHeight(angle, textSize, position) { + if (angle === 90 || angle === 270) { + position.y -= (textSize.h / 2); + } else if (angle > 270 || angle < 90) { + position.y -= textSize.h; + } + } + + function drawPointLabels(scale) { + var ctx = scale.ctx; + var opts = scale.options; + var angleLineOpts = opts.angleLines; + var pointLabelOpts = opts.pointLabels; + + ctx.lineWidth = angleLineOpts.lineWidth; + ctx.strokeStyle = angleLineOpts.color; + + var outerDistance = scale.getDistanceFromCenterForValue(opts.ticks.reverse ? scale.min : scale.max); + + // Point Label Font + var plFont = getPointLabelFontOptions(scale); + + ctx.textBaseline = 'top'; + + for (var i = getValueCount(scale) - 1; i >= 0; i--) { + if (angleLineOpts.display) { + var outerPosition = scale.getPointPosition(i, outerDistance); + ctx.beginPath(); + ctx.moveTo(scale.xCenter, scale.yCenter); + ctx.lineTo(outerPosition.x, outerPosition.y); + ctx.stroke(); + ctx.closePath(); + } + + if (pointLabelOpts.display) { + // Extra 3px out for some label spacing + var pointLabelPosition = scale.getPointPosition(i, outerDistance + 5); + + // Keep this in loop since we may support array properties here + var pointLabelFontColor = helpers.valueAtIndexOrDefault(pointLabelOpts.fontColor, i, globalDefaults.defaultFontColor); + ctx.font = plFont.font; + ctx.fillStyle = pointLabelFontColor; + + var angleRadians = scale.getIndexAngle(i); + var angle = helpers.toDegrees(angleRadians); + ctx.textAlign = getTextAlignForAngle(angle); + adjustPointPositionForLabelHeight(angle, scale._pointLabelSizes[i], pointLabelPosition); + fillText(ctx, scale.pointLabels[i] || '', pointLabelPosition, plFont.size); + } + } + } + + function drawRadiusLine(scale, gridLineOpts, radius, index) { + var ctx = scale.ctx; + ctx.strokeStyle = helpers.valueAtIndexOrDefault(gridLineOpts.color, index - 1); + ctx.lineWidth = helpers.valueAtIndexOrDefault(gridLineOpts.lineWidth, index - 1); + + if (scale.options.gridLines.circular) { + // Draw circular arcs between the points + ctx.beginPath(); + ctx.arc(scale.xCenter, scale.yCenter, radius, 0, Math.PI * 2); + ctx.closePath(); + ctx.stroke(); + } else { + // Draw straight lines connecting each index + var valueCount = getValueCount(scale); + + if (valueCount === 0) { + return; + } + + ctx.beginPath(); + var pointPosition = scale.getPointPosition(0, radius); + ctx.moveTo(pointPosition.x, pointPosition.y); + + for (var i = 1; i < valueCount; i++) { + pointPosition = scale.getPointPosition(i, radius); + ctx.lineTo(pointPosition.x, pointPosition.y); + } + + ctx.closePath(); + ctx.stroke(); + } + } + + function numberOrZero(param) { + return helpers.isNumber(param) ? param : 0; + } + + var LinearRadialScale = Chart.LinearScaleBase.extend({ + setDimensions: function () { + var me = this; + var opts = me.options; + var tickOpts = opts.ticks; + // Set the unconstrained dimension before label rotation + me.width = me.maxWidth; + me.height = me.maxHeight; + me.xCenter = Math.round(me.width / 2); + me.yCenter = Math.round(me.height / 2); + + var minSize = helpers.min([me.height, me.width]); + var tickFontSize = helpers.valueOrDefault(tickOpts.fontSize, globalDefaults.defaultFontSize); + me.drawingArea = opts.display ? (minSize / 2) - (tickFontSize / 2 + tickOpts.backdropPaddingY) : (minSize / 2); + }, + determineDataLimits: function () { + var me = this; + var chart = me.chart; + var min = Number.POSITIVE_INFINITY; + var max = Number.NEGATIVE_INFINITY; + + helpers.each(chart.data.datasets, function (dataset, datasetIndex) { + if (chart.isDatasetVisible(datasetIndex)) { + var meta = chart.getDatasetMeta(datasetIndex); + + helpers.each(dataset.data, function (rawValue, index) { + var value = +me.getRightValue(rawValue); + if (isNaN(value) || meta.data[index].hidden) { + return; + } + + min = Math.min(value, min); + max = Math.max(value, max); + }); + } + }); + + me.min = (min === Number.POSITIVE_INFINITY ? 0 : min); + me.max = (max === Number.NEGATIVE_INFINITY ? 0 : max); + + // Common base implementation to handle ticks.min, ticks.max, ticks.beginAtZero + me.handleTickRangeOptions(); + }, + getTickLimit: function () { + var tickOpts = this.options.ticks; + var tickFontSize = helpers.valueOrDefault(tickOpts.fontSize, globalDefaults.defaultFontSize); + return Math.min(tickOpts.maxTicksLimit ? tickOpts.maxTicksLimit : 11, Math.ceil(this.drawingArea / (1.5 * tickFontSize))); + }, + convertTicksToLabels: function () { + var me = this; + + Chart.LinearScaleBase.prototype.convertTicksToLabels.call(me); + + // Point labels + me.pointLabels = me.chart.data.labels.map(me.options.pointLabels.callback, me); + }, + getLabelForIndex: function (index, datasetIndex) { + return +this.getRightValue(this.chart.data.datasets[datasetIndex].data[index]); + }, + fit: function () { + if (this.options.pointLabels.display) { + fitWithPointLabels(this); + } else { + fit(this); + } + }, + /** + * Set radius reductions and determine new radius and center point + * @private + */ + setReductions: function (largestPossibleRadius, furthestLimits, furthestAngles) { + var me = this; + var radiusReductionLeft = furthestLimits.l / Math.sin(furthestAngles.l); + var radiusReductionRight = Math.max(furthestLimits.r - me.width, 0) / Math.sin(furthestAngles.r); + var radiusReductionTop = -furthestLimits.t / Math.cos(furthestAngles.t); + var radiusReductionBottom = -Math.max(furthestLimits.b - me.height, 0) / Math.cos(furthestAngles.b); + + radiusReductionLeft = numberOrZero(radiusReductionLeft); + radiusReductionRight = numberOrZero(radiusReductionRight); + radiusReductionTop = numberOrZero(radiusReductionTop); + radiusReductionBottom = numberOrZero(radiusReductionBottom); + + me.drawingArea = Math.min( + Math.round(largestPossibleRadius - (radiusReductionLeft + radiusReductionRight) / 2), + Math.round(largestPossibleRadius - (radiusReductionTop + radiusReductionBottom) / 2)); + me.setCenterPoint(radiusReductionLeft, radiusReductionRight, radiusReductionTop, radiusReductionBottom); + }, + setCenterPoint: function (leftMovement, rightMovement, topMovement, bottomMovement) { + var me = this; + var maxRight = me.width - rightMovement - me.drawingArea; + var maxLeft = leftMovement + me.drawingArea; + var maxTop = topMovement + me.drawingArea; + var maxBottom = me.height - bottomMovement - me.drawingArea; + + me.xCenter = Math.round(((maxLeft + maxRight) / 2) + me.left); + me.yCenter = Math.round(((maxTop + maxBottom) / 2) + me.top); + }, + + getIndexAngle: function (index) { + var angleMultiplier = (Math.PI * 2) / getValueCount(this); + var startAngle = this.chart.options && this.chart.options.startAngle ? + this.chart.options.startAngle : + 0; + + var startAngleRadians = startAngle * Math.PI * 2 / 360; + + // Start from the top instead of right, so remove a quarter of the circle + return index * angleMultiplier + startAngleRadians; + }, + getDistanceFromCenterForValue: function (value) { + var me = this; + + if (value === null) { + return 0; // null always in center + } + + // Take into account half font size + the yPadding of the top value + var scalingFactor = me.drawingArea / (me.max - me.min); + if (me.options.ticks.reverse) { + return (me.max - value) * scalingFactor; + } + return (value - me.min) * scalingFactor; + }, + getPointPosition: function (index, distanceFromCenter) { + var me = this; + var thisAngle = me.getIndexAngle(index) - (Math.PI / 2); + return { + x: Math.round(Math.cos(thisAngle) * distanceFromCenter) + me.xCenter, + y: Math.round(Math.sin(thisAngle) * distanceFromCenter) + me.yCenter + }; + }, + getPointPositionForValue: function (index, value) { + return this.getPointPosition(index, this.getDistanceFromCenterForValue(value)); + }, + + getBasePosition: function () { + var me = this; + var min = me.min; + var max = me.max; + + return me.getPointPositionForValue(0, + me.beginAtZero ? 0 : + min < 0 && max < 0 ? max : + min > 0 && max > 0 ? min : + 0); + }, + + draw: function () { + var me = this; + var opts = me.options; + var gridLineOpts = opts.gridLines; + var tickOpts = opts.ticks; + var valueOrDefault = helpers.valueOrDefault; + + if (opts.display) { + var ctx = me.ctx; + var startAngle = this.getIndexAngle(0); + + // Tick Font + var tickFontSize = valueOrDefault(tickOpts.fontSize, globalDefaults.defaultFontSize); + var tickFontStyle = valueOrDefault(tickOpts.fontStyle, globalDefaults.defaultFontStyle); + var tickFontFamily = valueOrDefault(tickOpts.fontFamily, globalDefaults.defaultFontFamily); + var tickLabelFont = helpers.fontString(tickFontSize, tickFontStyle, tickFontFamily); + + helpers.each(me.ticks, function (label, index) { + // Don't draw a centre value (if it is minimum) + if (index > 0 || tickOpts.reverse) { + var yCenterOffset = me.getDistanceFromCenterForValue(me.ticksAsNumbers[index]); + + // Draw circular lines around the scale + if (gridLineOpts.display && index !== 0) { + drawRadiusLine(me, gridLineOpts, yCenterOffset, index); + } + + if (tickOpts.display) { + var tickFontColor = valueOrDefault(tickOpts.fontColor, globalDefaults.defaultFontColor); + ctx.font = tickLabelFont; + + ctx.save(); + ctx.translate(me.xCenter, me.yCenter); + ctx.rotate(startAngle); + + if (tickOpts.showLabelBackdrop) { + var labelWidth = ctx.measureText(label).width; + ctx.fillStyle = tickOpts.backdropColor; + ctx.fillRect( + -labelWidth / 2 - tickOpts.backdropPaddingX, + -yCenterOffset - tickFontSize / 2 - tickOpts.backdropPaddingY, + labelWidth + tickOpts.backdropPaddingX * 2, + tickFontSize + tickOpts.backdropPaddingY * 2 + ); + } + + ctx.textAlign = 'center'; + ctx.textBaseline = 'middle'; + ctx.fillStyle = tickFontColor; + ctx.fillText(label, 0, -yCenterOffset); + ctx.restore(); + } + } + }); + + if (opts.angleLines.display || opts.pointLabels.display) { + drawPointLabels(me); + } + } + } + }); + Chart.scaleService.registerScaleType('radialLinear', LinearRadialScale, defaultConfig); + + }; + + }, { "25": 25, "34": 34, "45": 45 }], 58: [function (require, module, exports) { + /* global window: false */ + 'use strict'; + + var moment = require(6); + moment = typeof moment === 'function' ? moment : window.moment; + + var defaults = require(25); + var helpers = require(45); + + // Integer constants are from the ES6 spec. + var MIN_INTEGER = Number.MIN_SAFE_INTEGER || -9007199254740991; + var MAX_INTEGER = Number.MAX_SAFE_INTEGER || 9007199254740991; + + var INTERVALS = { + millisecond: { + common: true, + size: 1, + steps: [1, 2, 5, 10, 20, 50, 100, 250, 500] + }, + second: { + common: true, + size: 1000, + steps: [1, 2, 5, 10, 30] + }, + minute: { + common: true, + size: 60000, + steps: [1, 2, 5, 10, 30] + }, + hour: { + common: true, + size: 3600000, + steps: [1, 2, 3, 6, 12] + }, + day: { + common: true, + size: 86400000, + steps: [1, 2, 5] + }, + week: { + common: false, + size: 604800000, + steps: [1, 2, 3, 4] + }, + month: { + common: true, + size: 2.628e9, + steps: [1, 2, 3] + }, + quarter: { + common: false, + size: 7.884e9, + steps: [1, 2, 3, 4] + }, + year: { + common: true, + size: 3.154e10 + } + }; + + var UNITS = Object.keys(INTERVALS); + + function sorter(a, b) { + return a - b; + } + + function arrayUnique(items) { + var hash = {}; + var out = []; + var i, ilen, item; + + for (i = 0, ilen = items.length; i < ilen; ++i) { + item = items[i]; + if (!hash[item]) { + hash[item] = true; + out.push(item); + } + } + + return out; + } + + /** + * Returns an array of {time, pos} objects used to interpolate a specific `time` or position + * (`pos`) on the scale, by searching entries before and after the requested value. `pos` is + * a decimal between 0 and 1: 0 being the start of the scale (left or top) and 1 the other + * extremity (left + width or top + height). Note that it would be more optimized to directly + * store pre-computed pixels, but the scale dimensions are not guaranteed at the time we need + * to create the lookup table. The table ALWAYS contains at least two items: min and max. + * + * @param {Number[]} timestamps - timestamps sorted from lowest to highest. + * @param {String} distribution - If 'linear', timestamps will be spread linearly along the min + * and max range, so basically, the table will contains only two items: {min, 0} and {max, 1}. + * If 'series', timestamps will be positioned at the same distance from each other. In this + * case, only timestamps that break the time linearity are registered, meaning that in the + * best case, all timestamps are linear, the table contains only min and max. + */ + function buildLookupTable(timestamps, min, max, distribution) { + if (distribution === 'linear' || !timestamps.length) { + return [ + { time: min, pos: 0 }, + { time: max, pos: 1 } + ]; + } + + var table = []; + var items = [min]; + var i, ilen, prev, curr, next; + + for (i = 0, ilen = timestamps.length; i < ilen; ++i) { + curr = timestamps[i]; + if (curr > min && curr < max) { + items.push(curr); + } + } + + items.push(max); + + for (i = 0, ilen = items.length; i < ilen; ++i) { + next = items[i + 1]; + prev = items[i - 1]; + curr = items[i]; + + // only add points that breaks the scale linearity + if (prev === undefined || next === undefined || Math.round((next + prev) / 2) !== curr) { + table.push({ time: curr, pos: i / (ilen - 1) }); + } + } + + return table; + } + + // @see adapted from http://www.anujgakhar.com/2014/03/01/binary-search-in-javascript/ + function lookup(table, key, value) { + var lo = 0; + var hi = table.length - 1; + var mid, i0, i1; + + while (lo >= 0 && lo <= hi) { + mid = (lo + hi) >> 1; + i0 = table[mid - 1] || null; + i1 = table[mid]; + + if (!i0) { + // given value is outside table (before first item) + return { lo: null, hi: i1 }; + } else if (i1[key] < value) { + lo = mid + 1; + } else if (i0[key] > value) { + hi = mid - 1; + } else { + return { lo: i0, hi: i1 }; + } + } + + // given value is outside table (after last item) + return { lo: i1, hi: null }; + } + + /** + * Linearly interpolates the given source `value` using the table items `skey` values and + * returns the associated `tkey` value. For example, interpolate(table, 'time', 42, 'pos') + * returns the position for a timestamp equal to 42. If value is out of bounds, values at + * index [0, 1] or [n - 1, n] are used for the interpolation. + */ + function interpolate(table, skey, sval, tkey) { + var range = lookup(table, skey, sval); + + // Note: the lookup table ALWAYS contains at least 2 items (min and max) + var prev = !range.lo ? table[0] : !range.hi ? table[table.length - 2] : range.lo; + var next = !range.lo ? table[1] : !range.hi ? table[table.length - 1] : range.hi; + + var span = next[skey] - prev[skey]; + var ratio = span ? (sval - prev[skey]) / span : 0; + var offset = (next[tkey] - prev[tkey]) * ratio; + + return prev[tkey] + offset; + } + + /** + * Convert the given value to a moment object using the given time options. + * @see http://momentjs.com/docs/#/parsing/ + */ + function momentify(value, options) { + var parser = options.parser; + var format = options.parser || options.format; + + if (typeof parser === 'function') { + return parser(value); + } + + if (typeof value === 'string' && typeof format === 'string') { + return moment(value, format); + } + + if (!(value instanceof moment)) { + value = moment(value); + } + + if (value.isValid()) { + return value; + } + + // Labels are in an incompatible moment format and no `parser` has been provided. + // The user might still use the deprecated `format` option to convert his inputs. + if (typeof format === 'function') { + return format(value); + } + + return value; + } + + function parse(input, scale) { + if (helpers.isNullOrUndef(input)) { + return null; + } + + var options = scale.options.time; + var value = momentify(scale.getRightValue(input), options); + if (!value.isValid()) { + return null; + } + + if (options.round) { + value.startOf(options.round); + } + + return value.valueOf(); + } + + /** + * Returns the number of unit to skip to be able to display up to `capacity` number of ticks + * in `unit` for the given `min` / `max` range and respecting the interval steps constraints. + */ + function determineStepSize(min, max, unit, capacity) { + var range = max - min; + var interval = INTERVALS[unit]; + var milliseconds = interval.size; + var steps = interval.steps; + var i, ilen, factor; + + if (!steps) { + return Math.ceil(range / (capacity * milliseconds)); + } + + for (i = 0, ilen = steps.length; i < ilen; ++i) { + factor = steps[i]; + if (Math.ceil(range / (milliseconds * factor)) <= capacity) { + break; + } + } + + return factor; + } + + /** + * Figures out what unit results in an appropriate number of auto-generated ticks + */ + function determineUnitForAutoTicks(minUnit, min, max, capacity) { + var ilen = UNITS.length; + var i, interval, factor; + + for (i = UNITS.indexOf(minUnit); i < ilen - 1; ++i) { + interval = INTERVALS[UNITS[i]]; + factor = interval.steps ? interval.steps[interval.steps.length - 1] : MAX_INTEGER; + + if (interval.common && Math.ceil((max - min) / (factor * interval.size)) <= capacity) { + return UNITS[i]; + } + } + + return UNITS[ilen - 1]; + } + + /** + * Figures out what unit to format a set of ticks with + */ + function determineUnitForFormatting(ticks, minUnit, min, max) { + var duration = moment.duration(moment(max).diff(moment(min))); + var ilen = UNITS.length; + var i, unit; + + for (i = ilen - 1; i >= UNITS.indexOf(minUnit); i--) { + unit = UNITS[i]; + if (INTERVALS[unit].common && duration.as(unit) >= ticks.length) { + return unit; + } + } + + return UNITS[minUnit ? UNITS.indexOf(minUnit) : 0]; + } + + function determineMajorUnit(unit) { + for (var i = UNITS.indexOf(unit) + 1, ilen = UNITS.length; i < ilen; ++i) { + if (INTERVALS[UNITS[i]].common) { + return UNITS[i]; + } + } + } + + /** + * Generates a maximum of `capacity` timestamps between min and max, rounded to the + * `minor` unit, aligned on the `major` unit and using the given scale time `options`. + * Important: this method can return ticks outside the min and max range, it's the + * responsibility of the calling code to clamp values if needed. + */ + function generate(min, max, capacity, options) { + var timeOpts = options.time; + var minor = timeOpts.unit || determineUnitForAutoTicks(timeOpts.minUnit, min, max, capacity); + var major = determineMajorUnit(minor); + var stepSize = helpers.valueOrDefault(timeOpts.stepSize, timeOpts.unitStepSize); + var weekday = minor === 'week' ? timeOpts.isoWeekday : false; + var majorTicksEnabled = options.ticks.major.enabled; + var interval = INTERVALS[minor]; + var first = moment(min); + var last = moment(max); + var ticks = []; + var time; + + if (!stepSize) { + stepSize = determineStepSize(min, max, minor, capacity); + } + + // For 'week' unit, handle the first day of week option + if (weekday) { + first = first.isoWeekday(weekday); + last = last.isoWeekday(weekday); + } + + // Align first/last ticks on unit + first = first.startOf(weekday ? 'day' : minor); + last = last.startOf(weekday ? 'day' : minor); + + // Make sure that the last tick include max + if (last < max) { + last.add(1, minor); + } + + time = moment(first); + + if (majorTicksEnabled && major && !weekday && !timeOpts.round) { + // Align the first tick on the previous `minor` unit aligned on the `major` unit: + // we first aligned time on the previous `major` unit then add the number of full + // stepSize there is between first and the previous major time. + time.startOf(major); + time.add(~~((first - time) / (interval.size * stepSize)) * stepSize, minor); + } + + for (; time < last; time.add(stepSize, minor)) { + ticks.push(+time); + } + + ticks.push(+time); + + return ticks; + } + + /** + * Returns the right and left offsets from edges in the form of {left, right}. + * Offsets are added when the `offset` option is true. + */ + function computeOffsets(table, ticks, min, max, options) { + var left = 0; + var right = 0; + var upper, lower; + + if (options.offset && ticks.length) { + if (!options.time.min) { + upper = ticks.length > 1 ? ticks[1] : max; + lower = ticks[0]; + left = ( + interpolate(table, 'time', upper, 'pos') - + interpolate(table, 'time', lower, 'pos') + ) / 2; + } + if (!options.time.max) { + upper = ticks[ticks.length - 1]; + lower = ticks.length > 1 ? ticks[ticks.length - 2] : min; + right = ( + interpolate(table, 'time', upper, 'pos') - + interpolate(table, 'time', lower, 'pos') + ) / 2; + } + } + + return { left: left, right: right }; + } + + function ticksFromTimestamps(values, majorUnit) { + var ticks = []; + var i, ilen, value, major; + + for (i = 0, ilen = values.length; i < ilen; ++i) { + value = values[i]; + major = majorUnit ? value === +moment(value).startOf(majorUnit) : false; + + ticks.push({ + value: value, + major: major + }); + } + + return ticks; + } + + function determineLabelFormat(data, timeOpts) { + var i, momentDate, hasTime; + var ilen = data.length; + + // find the label with the most parts (milliseconds, minutes, etc.) + // format all labels with the same level of detail as the most specific label + for (i = 0; i < ilen; i++) { + momentDate = momentify(data[i], timeOpts); + if (momentDate.millisecond() !== 0) { + return 'MMM D, YYYY h:mm:ss.SSS a'; + } + if (momentDate.second() !== 0 || momentDate.minute() !== 0 || momentDate.hour() !== 0) { + hasTime = true; + } + } + if (hasTime) { + return 'MMM D, YYYY h:mm:ss a'; + } + return 'MMM D, YYYY'; + } + + module.exports = function (Chart) { + + var defaultConfig = { + position: 'bottom', + + /** + * Data distribution along the scale: + * - 'linear': data are spread according to their time (distances can vary), + * - 'series': data are spread at the same distance from each other. + * @see https://github.com/chartjs/Chart.js/pull/4507 + * @since 2.7.0 + */ + distribution: 'linear', + + /** + * Scale boundary strategy (bypassed by min/max time options) + * - `data`: make sure data are fully visible, ticks outside are removed + * - `ticks`: make sure ticks are fully visible, data outside are truncated + * @see https://github.com/chartjs/Chart.js/pull/4556 + * @since 2.7.0 + */ + bounds: 'data', + + time: { + parser: false, // false == a pattern string from http://momentjs.com/docs/#/parsing/string-format/ or a custom callback that converts its argument to a moment + format: false, // DEPRECATED false == date objects, moment object, callback or a pattern string from http://momentjs.com/docs/#/parsing/string-format/ + unit: false, // false == automatic or override with week, month, year, etc. + round: false, // none, or override with week, month, year, etc. + displayFormat: false, // DEPRECATED + isoWeekday: false, // override week start day - see http://momentjs.com/docs/#/get-set/iso-weekday/ + minUnit: 'millisecond', + + // defaults to unit's corresponding unitFormat below or override using pattern string from http://momentjs.com/docs/#/displaying/format/ + displayFormats: { + millisecond: 'h:mm:ss.SSS a', // 11:20:01.123 AM, + second: 'h:mm:ss a', // 11:20:01 AM + minute: 'h:mm a', // 11:20 AM + hour: 'hA', // 5PM + day: 'MMM D', // Sep 4 + week: 'll', // Week 46, or maybe "[W]WW - YYYY" ? + month: 'MMM YYYY', // Sept 2015 + quarter: '[Q]Q - YYYY', // Q3 + year: 'YYYY' // 2015 + }, + }, + ticks: { + autoSkip: false, + + /** + * Ticks generation input values: + * - 'auto': generates "optimal" ticks based on scale size and time options. + * - 'data': generates ticks from data (including labels from data {t|x|y} objects). + * - 'labels': generates ticks from user given `data.labels` values ONLY. + * @see https://github.com/chartjs/Chart.js/pull/4507 + * @since 2.7.0 + */ + source: 'auto', + + major: { + enabled: false + } + } + }; + + var TimeScale = Chart.Scale.extend({ + initialize: function () { + if (!moment) { + throw new Error('Chart.js - Moment.js could not be found! You must include it before Chart.js to use the time scale. Download at https://momentjs.com'); + } + + this.mergeTicksOptions(); + + Chart.Scale.prototype.initialize.call(this); + }, + + update: function () { + var me = this; + var options = me.options; + + // DEPRECATIONS: output a message only one time per update + if (options.time && options.time.format) { + console.warn('options.time.format is deprecated and replaced by options.time.parser.'); + } + + return Chart.Scale.prototype.update.apply(me, arguments); + }, + + /** + * Allows data to be referenced via 't' attribute + */ + getRightValue: function (rawValue) { + if (rawValue && rawValue.t !== undefined) { + rawValue = rawValue.t; + } + return Chart.Scale.prototype.getRightValue.call(this, rawValue); + }, + + determineDataLimits: function () { + var me = this; + var chart = me.chart; + var timeOpts = me.options.time; + var unit = timeOpts.unit || 'day'; + var min = MAX_INTEGER; + var max = MIN_INTEGER; + var timestamps = []; + var datasets = []; + var labels = []; + var i, j, ilen, jlen, data, timestamp; + + // Convert labels to timestamps + for (i = 0, ilen = chart.data.labels.length; i < ilen; ++i) { + labels.push(parse(chart.data.labels[i], me)); + } + + // Convert data to timestamps + for (i = 0, ilen = (chart.data.datasets || []).length; i < ilen; ++i) { + if (chart.isDatasetVisible(i)) { + data = chart.data.datasets[i].data; + + // Let's consider that all data have the same format. + if (helpers.isObject(data[0])) { + datasets[i] = []; + + for (j = 0, jlen = data.length; j < jlen; ++j) { + timestamp = parse(data[j], me); + timestamps.push(timestamp); + datasets[i][j] = timestamp; + } + } else { + timestamps.push.apply(timestamps, labels); + datasets[i] = labels.slice(0); + } + } else { + datasets[i] = []; + } + } + + if (labels.length) { + // Sort labels **after** data have been converted + labels = arrayUnique(labels).sort(sorter); + min = Math.min(min, labels[0]); + max = Math.max(max, labels[labels.length - 1]); + } + + if (timestamps.length) { + timestamps = arrayUnique(timestamps).sort(sorter); + min = Math.min(min, timestamps[0]); + max = Math.max(max, timestamps[timestamps.length - 1]); + } + + min = parse(timeOpts.min, me) || min; + max = parse(timeOpts.max, me) || max; + + // In case there is no valid min/max, set limits based on unit time option + min = min === MAX_INTEGER ? +moment().startOf(unit) : min; + max = max === MIN_INTEGER ? +moment().endOf(unit) + 1 : max; + + // Make sure that max is strictly higher than min (required by the lookup table) + me.min = Math.min(min, max); + me.max = Math.max(min + 1, max); + + // PRIVATE + me._horizontal = me.isHorizontal(); + me._table = []; + me._timestamps = { + data: timestamps, + datasets: datasets, + labels: labels + }; + }, + + buildTicks: function () { + var me = this; + var min = me.min; + var max = me.max; + var options = me.options; + var timeOpts = options.time; + var timestamps = []; + var ticks = []; + var i, ilen, timestamp; + + switch (options.ticks.source) { + case 'data': + timestamps = me._timestamps.data; + break; + case 'labels': + timestamps = me._timestamps.labels; + break; + case 'auto': + default: + timestamps = generate(min, max, me.getLabelCapacity(min), options); + } + + if (options.bounds === 'ticks' && timestamps.length) { + min = timestamps[0]; + max = timestamps[timestamps.length - 1]; + } + + // Enforce limits with user min/max options + min = parse(timeOpts.min, me) || min; + max = parse(timeOpts.max, me) || max; + + // Remove ticks outside the min/max range + for (i = 0, ilen = timestamps.length; i < ilen; ++i) { + timestamp = timestamps[i]; + if (timestamp >= min && timestamp <= max) { + ticks.push(timestamp); + } + } + + me.min = min; + me.max = max; + + // PRIVATE + me._unit = timeOpts.unit || determineUnitForFormatting(ticks, timeOpts.minUnit, me.min, me.max); + me._majorUnit = determineMajorUnit(me._unit); + me._table = buildLookupTable(me._timestamps.data, min, max, options.distribution); + me._offsets = computeOffsets(me._table, ticks, min, max, options); + me._labelFormat = determineLabelFormat(me._timestamps.data, timeOpts); + + return ticksFromTimestamps(ticks, me._majorUnit); + }, + + getLabelForIndex: function (index, datasetIndex) { + var me = this; + var data = me.chart.data; + var timeOpts = me.options.time; + var label = data.labels && index < data.labels.length ? data.labels[index] : ''; + var value = data.datasets[datasetIndex].data[index]; + + if (helpers.isObject(value)) { + label = me.getRightValue(value); + } + if (timeOpts.tooltipFormat) { + return momentify(label, timeOpts).format(timeOpts.tooltipFormat); + } + if (typeof label === 'string') { + return label; + } + + return momentify(label, timeOpts).format(me._labelFormat); + }, + + /** + * Function to format an individual tick mark + * @private + */ + tickFormatFunction: function (tick, index, ticks, formatOverride) { + var me = this; + var options = me.options; + var time = tick.valueOf(); + var formats = options.time.displayFormats; + var minorFormat = formats[me._unit]; + var majorUnit = me._majorUnit; + var majorFormat = formats[majorUnit]; + var majorTime = tick.clone().startOf(majorUnit).valueOf(); + var majorTickOpts = options.ticks.major; + var major = majorTickOpts.enabled && majorUnit && majorFormat && time === majorTime; + var label = tick.format(formatOverride ? formatOverride : major ? majorFormat : minorFormat); + var tickOpts = major ? majorTickOpts : options.ticks.minor; + var formatter = helpers.valueOrDefault(tickOpts.callback, tickOpts.userCallback); + + return formatter ? formatter(label, index, ticks) : label; + }, + + convertTicksToLabels: function (ticks) { + var labels = []; + var i, ilen; + + for (i = 0, ilen = ticks.length; i < ilen; ++i) { + labels.push(this.tickFormatFunction(moment(ticks[i].value), i, ticks)); + } + + return labels; + }, + + /** + * @private + */ + getPixelForOffset: function (time) { + var me = this; + var size = me._horizontal ? me.width : me.height; + var start = me._horizontal ? me.left : me.top; + var pos = interpolate(me._table, 'time', time, 'pos'); + + return start + size * (me._offsets.left + pos) / (me._offsets.left + 1 + me._offsets.right); + }, + + getPixelForValue: function (value, index, datasetIndex) { + var me = this; + var time = null; + + if (index !== undefined && datasetIndex !== undefined) { + time = me._timestamps.datasets[datasetIndex][index]; + } + + if (time === null) { + time = parse(value, me); + } + + if (time !== null) { + return me.getPixelForOffset(time); + } + }, + + getPixelForTick: function (index) { + var ticks = this.getTicks(); + return index >= 0 && index < ticks.length ? + this.getPixelForOffset(ticks[index].value) : + null; + }, + + getValueForPixel: function (pixel) { + var me = this; + var size = me._horizontal ? me.width : me.height; + var start = me._horizontal ? me.left : me.top; + var pos = (size ? (pixel - start) / size : 0) * (me._offsets.left + 1 + me._offsets.left) - me._offsets.right; + var time = interpolate(me._table, 'pos', pos, 'time'); + + return moment(time); + }, + + /** + * Crude approximation of what the label width might be + * @private + */ + getLabelWidth: function (label) { + var me = this; + var ticksOpts = me.options.ticks; + var tickLabelWidth = me.ctx.measureText(label).width; + var angle = helpers.toRadians(ticksOpts.maxRotation); + var cosRotation = Math.cos(angle); + var sinRotation = Math.sin(angle); + var tickFontSize = helpers.valueOrDefault(ticksOpts.fontSize, defaults.global.defaultFontSize); + + return (tickLabelWidth * cosRotation) + (tickFontSize * sinRotation); + }, + + /** + * @private + */ + getLabelCapacity: function (exampleTime) { + var me = this; + + var formatOverride = me.options.time.displayFormats.millisecond; // Pick the longest format for guestimation + + var exampleLabel = me.tickFormatFunction(moment(exampleTime), 0, [], formatOverride); + var tickLabelWidth = me.getLabelWidth(exampleLabel); + var innerWidth = me.isHorizontal() ? me.width : me.height; + + var capacity = Math.floor(innerWidth / tickLabelWidth); + return capacity > 0 ? capacity : 1; + } + }); + + Chart.scaleService.registerScaleType('time', TimeScale, defaultConfig); + }; + + }, { "25": 25, "45": 45, "6": 6 }] + }, {}, [7])(7) +}); \ No newline at end of file diff --git a/public/styles/style.css b/public/styles/style.css index 8fcb0dc4..df4790d6 100644 --- a/public/styles/style.css +++ b/public/styles/style.css @@ -236,6 +236,14 @@ a { border: none; } +.lb6 { + background: url(../images/leftbar-62.png) -360px 0px; + height: 62px; + width: 62px; + cursor: pointer; + border: none; +} + .m0 { background : url(../images/images16.png) -32px 0px; height : 16px; width : 16px; border:none; float:left } .m1 { background : url(../images/images16.png) -16px 0px; height : 16px; width : 16px; border:none; float:left } .m2 { background : url(../images/images16.png) -96px 0px; height : 16px; width : 16px; border:none; float:left } diff --git a/views/default-min.handlebars b/views/default-min.handlebars index 7529cd57..df24f6d9 100644 --- a/views/default-min.handlebars +++ b/views/default-min.handlebars @@ -1 +1 @@ - MeshCentral
    {{{title}}}
    {{{title2}}}

    {{{logoutControl}}}

     

    \ No newline at end of file + MeshCentral
    {{{title}}}
    {{{title2}}}

    {{{logoutControl}}}

     

    \ No newline at end of file diff --git a/views/default.handlebars b/views/default.handlebars index 6e4423eb..41fda3cc 100644 --- a/views/default.handlebars +++ b/views/default.handlebars @@ -23,6 +23,7 @@ + @@ -79,6 +80,9 @@
    +
    @@ -95,6 +99,7 @@ My Events My Files My Users + My Server   @@ -237,13 +242,6 @@ Delete account

    -

    Server actions

    -

    - Download server backup
    - Restore server with backup
    - Check server version
    - Show server error log
    -


    Device Groups ( New ) @@ -342,6 +340,29 @@  
    +