diff --git a/.DS_Store b/.DS_Store index 5ca49c84f..fb46d6fdb 100644 Binary files a/.DS_Store and b/.DS_Store differ diff --git a/.gitignore b/.gitignore index 09c790035..9bb3364c9 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,6 @@ node_modules .eslintrc.js package-lock.json -settings.json -.DS_Store \ No newline at end of file +.DS_Store +user/settings.json +dist \ No newline at end of file diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 000000000..3fca7639a --- /dev/null +++ b/.travis.yml @@ -0,0 +1,10 @@ +language: node_js +node_js: + - "stable" +os: + osx +# jobs: +# allow_failures: +# - os: osx +install: + - npm install \ No newline at end of file diff --git a/Main.js b/Main.js index 9af5fbcc2..ce2ab014f 100644 --- a/Main.js +++ b/Main.js @@ -1,7 +1,5 @@ // node requirements -const { - dialog, app, BrowserWindow, ipcMain, -} = require('electron'); +const { dialog, app, BrowserWindow, ipcMain } = require('electron'); const fs = require('fs'); const path = require('path'); const connectSQL = require('./model/sql-connect'); @@ -20,9 +18,9 @@ function createWindow() { // assign win to an instance of a new browser window. win = new BrowserWindow({ // giving our window its width - width: 900, + width: 1920, // giving our window its hieght - height: 800, + height: 1080, // specify the path of the icon -- Which icon is this?.. note too tsure --> Ousman icon: path.join(__dirname, 'app/assets/icons/icon.png'), // enable node inegreation --> node intgeration, default is usally false --> Ousman @@ -44,11 +42,16 @@ function createWindow() { // read json from settings.json fs.readFileSync(path.resolve(__dirname, './user/settings.json'), { encoding: 'UTF-8', - }), + }) ); // reassign state.splash state.splash = true; - fs.writeFileSync(path.resolve(__dirname, './user/settings.json'), JSON.stringify(state), { encoding: 'UTF-8' }); win = null; + fs.writeFileSync( + path.resolve(__dirname, './user/settings.json'), + JSON.stringify(state), + { encoding: 'UTF-8' } + ); + win = null; }); } @@ -62,11 +65,15 @@ app.on('window-all-closed', () => { // read json from settings.json fs.readFileSync(path.resolve(__dirname, './user/settings.json'), { encoding: 'UTF-8', - }), + }) ); // reassign state.splash state.splash = true; - fs.writeFileSync(path.resolve(__dirname, './user/settings.json'), JSON.stringify(state), { encoding: 'UTF-8' }); + fs.writeFileSync( + path.resolve(__dirname, './user/settings.json'), + JSON.stringify(state), + { encoding: 'UTF-8' } + ); // process platform is a property that return a string identifying the OS platform on which NodeJs process is running --> Ousman if (process.platform !== 'darwin') { // quits application @@ -85,29 +92,33 @@ app.on('activate', () => { // Fired by the useEffect hook inside of the Splash.jsx component, this message route will toggle // splash property inside of settings.json to false once the Splash page renders itself just once ipcMain.on('toggleSplash', (message) => { - //console.log('toggleSplash message received'); + // console.log('toggleSplash message received'); const state = JSON.parse( // read json from settings.json fs.readFileSync(path.resolve(__dirname, './user/settings.json'), { encoding: 'UTF-8', - }), + }) ); // reassign state.splash to false state.splash = false; // overwrite settings.json with false splash property - fs.writeFileSync(path.resolve(__dirname, './user/settings.json'), JSON.stringify(state), { encoding: 'UTF-8' }); + fs.writeFileSync( + path.resolve(__dirname, './user/settings.json'), + JSON.stringify(state), + { encoding: 'UTF-8' } + ); message.returnValue = state.splash; }); ipcMain.on('checkSplash', (message) => { - //sconsole.log('checkSplash message received'); + // sconsole.log('checkSplash message received'); const state = JSON.parse( // read json from settings.json fs.readFileSync(path.resolve(__dirname, './user/settings.json'), { encoding: 'UTF-8', - }), + }) ); message.returnValue = state.splash; @@ -116,13 +127,13 @@ ipcMain.on('checkSplash', (message) => { // Load settings JSON and returns current setup status back to the render process. // ipc 'setup' route --> Ousman ipcMain.on('setup', (message) => { - //console.log('setup message received'); + // console.log('setup message received'); // assigns state to the returned the object returned from settings.json --> Ousman const state = JSON.parse( // read json from settings.json fs.readFileSync(path.resolve(__dirname, './user/settings.json'), { encoding: 'UTF-8', - }), + }) ); // destructure setupRequired from state constant ---> Ousman const { setupRequired } = state; @@ -137,10 +148,10 @@ ipcMain.on('submit', (message, newService) => { const state = JSON.parse( fs.readFileSync(path.resolve(__dirname, './user/settings.json'), { encoding: 'UTF-8', - }), + }) ); - // Checks if setup is required by checking if the value for the state key 'setupRequired' is true + // Checks if setup is required by checking if the value for the state key 'setupRequired' is true if (state.setupRequired) { // If setup is required, the value for key 'setupRequired' is reassign to false and the value for key 'services' is reassign to an array with newService as its only element state.setupRequired = false; @@ -148,10 +159,13 @@ ipcMain.on('submit', (message, newService) => { } else { // Else the newService is pushed into the services array state.services.push(JSON.parse(newService)); - } + } // Rewrites user/settings.json to show state - fs.writeFileSync(path.resolve(__dirname, './user/settings.json'), JSON.stringify(state)); + fs.writeFileSync( + path.resolve(__dirname, './user/settings.json'), + JSON.stringify(state) + ); }); // Load settings JSON and returns updated state back to the render process. @@ -161,7 +175,7 @@ ipcMain.on('dashboard', (message) => { const state = JSON.parse( fs.readFileSync(path.resolve(__dirname, './user/settings.json'), { encoding: 'UTF-8', - }), + }) ); // destructure services from state... what is services? --> Ousman const { services } = state; @@ -180,31 +194,36 @@ ipcMain.on('deleteService', (message, index) => { let state = JSON.parse( fs.readFileSync(path.resolve(__dirname, './user/settings.json'), { encoding: 'UTF-8', - }), + }) ); // Send a response back with the updated services const { splash } = state; -// Checks if there is more than one services in the services array + // Checks if there is more than one services in the services array if (state.services.length > 1) { - // If true, removes the service at position 'index' + // If true, removes the service at position 'index' state.services.splice(index, 1); } else { - // Else reassign state to what the user/setting.json file was originally before any database was save + // Else reassign state to what the user/setting.json file was originally before any database was save state = { setupRequired: true, services: ['hard', 'coded', 'in'], splash }; } // Rewrites json from settings.json - fs.writeFileSync(path.resolve(__dirname, './user/settings.json'), JSON.stringify(state), { encoding: 'UTF-8' }); + fs.writeFileSync( + path.resolve(__dirname, './user/settings.json'), + JSON.stringify(state), + { encoding: 'UTF-8' } + ); message.sender.send('deleteResponse', state.services); }); - // Queries the database for communications information and returns it back to the render process. ipcMain.on('overviewRequest', (message, index) => { console.log('hello from overview request'); const { services } = JSON.parse( - fs.readFileSync(path.resolve(__dirname, './user/settings.json'), { encoding: 'UTF-8' }), + fs.readFileSync(path.resolve(__dirname, './user/settings.json'), { + encoding: 'UTF-8', + }) ); const databaseType = services[index][1]; @@ -233,14 +252,16 @@ ipcMain.on('overviewRequest', (message, index) => { const errorAlert = { type: 'error', title: 'Error in Main process', - message: 'Database information could not be retreived. Check that table exists.', + message: + 'Database information could not be retreived. Check that table exists.', }; // after requiring dialog in the topmost section of main. We invoke the method showMessagebox passing the error object we created --> Ousman dialog.showMessageBox(errorAlert); - - message.sender.send(JSON.stringify('Database info could not be retreived.')); + message.sender.send( + JSON.stringify('Database info could not be retreived.') + ); } else { console.log('Connected to SQL Database'); const queryResults = JSON.stringify(result.rows); @@ -256,7 +277,9 @@ ipcMain.on('overviewRequest', (message, index) => { ipcMain.on('detailsRequest', (message, index) => { console.log('detailsRequest message received'); const databaseType = JSON.parse( - fs.readFileSync(path.resolve(__dirname, './user/settings.json'), { encoding: 'UTF-8' }), + fs.readFileSync(path.resolve(__dirname, './user/settings.json'), { + encoding: 'UTF-8', + }) ).services[index][1]; if (databaseType === 'MongoDB') { @@ -267,6 +290,7 @@ ipcMain.on('detailsRequest', (message, index) => { const queryResults = JSON.stringify(data); // Asynchronous event emitter used to transmit query results back to the render process. message.sender.send('detailsResponse', queryResults); + console.log('Message Sent'); }); } @@ -274,7 +298,10 @@ ipcMain.on('detailsRequest', (message, index) => { const getHealth = 'SELECT * FROM healthInfo'; pool.query(getHealth, (err, result) => { if (err) { - message.sender.send('detailsResponse', JSON.stringify('Database info could not be retreived.')); + message.sender.send( + 'detailsResponse', + JSON.stringify('Database info could not be retreived.') + ); } const queryResults = JSON.stringify(result.rows); // Asynchronous event emitter used to transmit query results back to the render process. @@ -283,4 +310,3 @@ ipcMain.on('detailsRequest', (message, index) => { }); } }); - diff --git a/README.md b/README.md index c84df50e4..67bb04eed 100644 --- a/README.md +++ b/README.md @@ -1,24 +1,34 @@ ![Chronos logo](https://raw.githubusercontent.com/Chronos2-0/Chronos/master/app/assets/logo2.png) ## Chronos -Microservice communication and health visualizer. - -[![NPM Version][npm-image]][npm-url] -[![NPM Downloads][downloads-image]][downloads-url] - -```js -const cmd = require('chronos-microservice-debugger3') -cmd.propagate() - -app.use('/', cmd.microCom('microserviceName', 'databaseType', 'databaseURL', 'wantMicroHealth', 'queryFrequency')) -``` +Microservice communication, health, and Docker container visualizer. +Comes with a middleware and an Electron app. ## Features + * NEW (3.0+): Docker container stats (e.g. ID, memory usage %, CPU usage %, running processes, etc.) (New middleware compiled from TypeScript.) * HTTP request tracing * Speed and latency tracking * Process monitoring * Memory usage +## NEW FEATURE FOR 3.0+ - Logging Docker Container Stats + +IMPORTANT: Give your containers the same names you use for arguments for microservice names. Read more about it under the INSTALLATION section below. + +IMPORTANT: In order to have container stats saved to your database along with other health info, when starting up the containers, bind volumes to this path: +`/var/run/docker.sock` + +For example, you can type the following when starting up a container: +`docker run -v /var/run/docker.sock:/var/run/docker.sock [your-image-tag]` + +If you're using docker-compose to start up multiple containers at once, you can add a `volumes` key for each of your services in the YAML file: +``` +volumes: + - "/var/run/docker.sock:/var/run/docker.sock" +``` + +*Note: This module leverages the features of [systeminformation](https://systeminformation.io/). + ## Installation Chronos consists of a [Node](https://nodejs.org/en/) module available through the @@ -26,43 +36,58 @@ Chronos consists of a [Node](https://nodejs.org/en/) module available through th #### Node module -To begin, install the [Chronos](https://www.npmjs.com/package/chronos-microservice-debugger3) node module within each microservice of your application using the +To begin, install the [Chronos](https://www.npmjs.com/package/chronos-microservice-debugger4) node module within each microservice of your application using the [`npm install`](https://docs.npmjs.com/getting-started/installing-npm-packages-locally)command: ``` -npm install chronos-microservice-debugger3 +npm install chronos-microservice-debugger4 ``` Once installed, write the following two lines at the top of each microservice's server file: ```javascript -const cmd = require('chronos-microservice-debugger3'); +const cmd = require('chronos-microservice-debugger4'); cmd.propagate(); ``` Then add a route handler for all incoming requests: ```js -app.use('/', cmd.microCom('microserviceName', 'databaseType', 'databaseURL', 'wantMicroHealth', 'queryFrequency')) +app.use('/', + cmd.microCom( + 'microserviceName', + 'databaseType', + 'databaseURL', + 'wantMicroHealth', + 'queryFrequency', + 'isDockerized' + ) +) ``` The cmd.microCom handler function logs communication and health data to a user-provided database. This is to ensure that your private data stays private. We currently support MongoDB and SQL/PostgreSQL databases. -cmd.microCom takes four parameters and an optional fifth parameter. You can enter the arguments as individual strings or as an array. +cmd.microCom takes six parameters. You can enter the arguments as individual strings or as an array. The parameters are: -1. microserviceName: To identify the microservice (i.e. "payments") -2. databaseType: Enter either "mongo" or "sql" -3. databaseURL: Enter the URL of your database -4. wantMicroHealth: Do you want to monitor the health of this microservice? Enter "yes" or "no" -5. queryFrequency (optional): How frequently do you want to log the health of this microservice? It defaults to every minute, but you can choose: - * "s" : every second - * "m" : every minute (default) - * "h" : every hour - * "d" : once per day - * "w" : once per week +* [1] microserviceName: To identify the microservice (i.e. "payments"). + - Make sure this name matches your container name. More details more below (param #6). + - Your input name for the microservice will be turned to an all-lowercase string. +* [2] databaseType: Enter either "mongo" or "sql". +* [3] databaseURL: Enter the URL of your database. +* [4] wantMicroHealth: Do you want to monitor the health of this microservice? Enter "yes" or "no". + - Note: If you choose "yes" for this param, the middleware will NOT log container stats. In other words, if you want container stats instead, input "no" here and "yes" for param #6. +* [5] queryFrequency (optional): How frequently do you want to log the health of this microservice? It defaults to every minute, but you can choose: + - "s" : every second + - "m" : every minute (default) + - "h" : every hour + - "d" : once per day + - "w" : once per week +* [6] isDockerized: Is this microservice running in a Docker container? Enter "yes" or "no". (Defaults to "no".) + - IMPORTANT: When starting up the container, give it the same name that you used for the microservice, because the middleware finds the correct container ID of your container by matching the container name to the microservice name you input as 1st argument. + - Don't forget to bind mount to Docker socket. See NEW FEATURE section above. String parameter example: ```javascript -app.use('/', cmd.microCom('payments', 'mongo', 'mongodb+srv://user:password@cluster0-abc.mongodb.net/','yes','h')) +app.use('/', cmd.microCom('payments', 'mongo', 'mongodb+srv://user:password@cluster0-abc.mongodb.net/','yes','m','no')) ``` Array parameter example: @@ -71,16 +96,24 @@ let values = [ 'payments', 'mongo', 'mongodb+srv://user:password@cluster0-abc.mongodb.net/', - 'yes', - 'h' + 'no', + 'h', + 'yes' ] app.use('/', cmd.microCom(values) ``` +#### Microservice Test Suite + +Additionally, the repo includes a test suite of microservices utilizing the Chronos node module so that their communication, health, and container data can be logged. You can then visualize the data with the Electron app. + +The microservices include individual Dockerfiles in their respective directories. A docker-compose.yml is in the root directory in case you'd like to deploy all services together. + +Refer to the [README](https://github.com/oslabs-beta/Chronos/tree/docker/microservice) of that branch for more details. #### Electron desktop application -After installing the node module in each microservice, download the Electron desktop application from the public [Chronos](https://github.com/Chronos2-0/Chronos) repo. +After installing the node module in each microservice, download the Electron desktop application from the public [Chronos](https://github.com/oslabs-beta/Chronos) repo. Inside the downloaded directory, install all dependencies using the `npm install` command followed by the `npm start` command to start the Electron desktop application. @@ -90,6 +123,13 @@ Chronos hopes to inspire an active community of both users and developers. For q ## People +* v3 Team: +[Alan Lee](https://github.com/ajlee12/), +[Alon Ofengart](https://github.com/alon25), +[Brian Bui](https://github.com/Umius-Brian), +[Brianna Sookhoo](https://github.com/briannasookhoo) + +* Previous teams who laid the foundation and put in invaluable work: [Tim Atapagra](https://github.com/timpagra), [Mohtasim Chowdhury](https://github.com/mohtasim317), [Ousman Diallo](https://github.com/Dialloousman), @@ -102,9 +142,4 @@ Chronos hopes to inspire an active community of both users and developers. For q ## License - [MIT](LICENSE) - -[npm-image]: https://img.shields.io/npm/v/chronos-microservice-debugger3.svg -[npm-url]: https://www.npmjs.com/package/chronos-microservice-debugger3 -[downloads-image]: https://img.shields.io/npm/dm/chronos-microservice-debugger3.svg -[downloads-url]: https://npmjs.org/package/chronos-microservice-debugger3 +[MIT](https://github.com/oslabs-beta/Chronos/blob/master/LICENSE.md) \ No newline at end of file diff --git a/app/.DS_Store b/app/.DS_Store index c90510208..b8e93c287 100644 Binary files a/app/.DS_Store and b/app/.DS_Store differ diff --git a/app/AComp/Extras.jsx b/app/AComp/Extras.jsx new file mode 100644 index 000000000..7c2f30a10 --- /dev/null +++ b/app/AComp/Extras.jsx @@ -0,0 +1,36 @@ +import React from 'react'; +import '../stylesheets/overviewSubmit.css'; + + +const Extras = (props) => { + const { AddClick, DeleteClick, RefreshClick } = props; + return ( +
+ + + +
+ ); +}; + +export default Extras; diff --git a/app/AComp/Microservices.jsx b/app/AComp/Microservices.jsx new file mode 100644 index 000000000..491c5b853 --- /dev/null +++ b/app/AComp/Microservices.jsx @@ -0,0 +1,27 @@ +import React from 'react'; +import '../stylesheets/Microservices.css'; + + +const Microservices = (props) => { + const { context, Click, isclicked } = props; + const buttonStore = []; + for (let i = 0; i < context.length; i += 1) { + buttonStore.push( + + ); + } + return ( + buttonStore + ); +}; + +export default Microservices; diff --git a/app/AComp/ServicesList.jsx b/app/AComp/ServicesList.jsx new file mode 100644 index 000000000..ee52b8f7e --- /dev/null +++ b/app/AComp/ServicesList.jsx @@ -0,0 +1,108 @@ +import React, { useContext, useState, useEffect } from 'react'; +import OverviewContext from '../context/OverviewContext'; +import HealthInformationContext from '../context/DetailsContext'; +import ServiceDetails from '../components/ServiceDetails.jsx'; +import '../stylesheets/Services.css'; + +const { ipcRenderer } = window.require('electron'); + +const ServicesList = (props) => { + const { index, setDetails } = props; + // Overview state used to create service buttons + const [overviewState, setOverviewState] = useState([]); + + // Contexts have data added to them following successful IPC return. Data is later used to create charts. + const serviceComponents = useContext(OverviewContext); + + useEffect(() => { + // IPC communication used to initiate query for information on microservices. + ipcRenderer.send('overviewRequest', index); + // IPC listener responsible for retrieving infomation from asynchronous main process message. + ipcRenderer.on('overviewResponse', (event, data) => { + // Adds to state and context. + // console.log(JSON.parse(data)); + setOverviewState(Object.values(JSON.parse(data))); + serviceComponents.overviewData = JSON.parse(data); + }); + }, []); + + // Holds the buttons generated for unique services. + const componentButtons = []; + + // Tracks which services already have button created. + const serviceCache = {}; + // Contexts have data added to them following successful IPC return. Data is later used to create charts. + const healthdata = useContext(HealthInformationContext); + + for (let i = 0; i < overviewState.length; i += 1) { + const element = overviewState[i]; + // If SQL + if (element.currentmicroservice) { + if (!(element.currentmicroservice in serviceCache)) { + const button = ( + + ); + componentButtons.push(button); + serviceCache[element.currentmicroservice] = true; + } + } else if (element.currentMicroservice) { + // If Mongo + if (element.currentMicroservice) { + if (!(element.currentMicroservice in serviceCache)) { + const button = ( + + ); + componentButtons.push(button); + serviceCache[element.currentMicroservice] = true; + } + } + } + } + // If there's no data, return 'No data present', else return microservices button + if (componentButtons.length === 0) { + return

Loading

; + } + return componentButtons; +}; + +export default ServicesList; diff --git a/app/AComp/SidebarHeader.jsx b/app/AComp/SidebarHeader.jsx new file mode 100644 index 000000000..44f4a182a --- /dev/null +++ b/app/AComp/SidebarHeader.jsx @@ -0,0 +1,17 @@ +import React from 'react'; +import '../stylesheets/SidebarHeader.css'; + +const Header = (props) => { + return ( + <> + +

Databases

+ + ); +}; + +export default Header; diff --git a/app/assets/chartModal.png b/app/assets/chartModal.png deleted file mode 100644 index 67254df0c..000000000 Binary files a/app/assets/chartModal.png and /dev/null differ diff --git a/app/assets/icons/icon.png b/app/assets/icons/icon.png deleted file mode 100644 index 009a6c7b8..000000000 Binary files a/app/assets/icons/icon.png and /dev/null differ diff --git a/app/assets/latencyChart.png b/app/assets/latencyChart.png deleted file mode 100644 index 233ffe38b..000000000 Binary files a/app/assets/latencyChart.png and /dev/null differ diff --git a/app/assets/memoryChart.png b/app/assets/memoryChart.png deleted file mode 100644 index 19296acc2..000000000 Binary files a/app/assets/memoryChart.png and /dev/null differ diff --git a/app/assets/pieChart.png b/app/assets/pieChart.png deleted file mode 100644 index 889a6e496..000000000 Binary files a/app/assets/pieChart.png and /dev/null differ diff --git a/app/assets/processingChart.png b/app/assets/processingChart.png deleted file mode 100644 index b237305b8..000000000 Binary files a/app/assets/processingChart.png and /dev/null differ diff --git a/app/assets/routeChart.png b/app/assets/routeChart.png deleted file mode 100644 index 5dfd28c88..000000000 Binary files a/app/assets/routeChart.png and /dev/null differ diff --git a/app/assets/speedChart.png b/app/assets/speedChart.png deleted file mode 100644 index 9041d5a27..000000000 Binary files a/app/assets/speedChart.png and /dev/null differ diff --git a/app/assets/tempChart.png b/app/assets/tempChart.png deleted file mode 100644 index cc7136100..000000000 Binary files a/app/assets/tempChart.png and /dev/null differ diff --git a/app/assets/tooltip.jpg b/app/assets/tooltip.jpg deleted file mode 100644 index b21f2a0cf..000000000 Binary files a/app/assets/tooltip.jpg and /dev/null differ diff --git a/app/charts/docker-stats-chart.jsx b/app/charts/docker-stats-chart.jsx new file mode 100644 index 000000000..e6cf1a7d1 --- /dev/null +++ b/app/charts/docker-stats-chart.jsx @@ -0,0 +1,99 @@ +import React, { useContext, useState, useEffect } from 'react'; +import HealthContext from '../context/DetailsContext'; +// import Plot from 'react-plotly.js'; + +// A table that displays real-time Docker container stats. + // Just need to pull data from most recent (last) obj in healthData. +const DockerStatsChart = (props) => { + const healthData = useContext(HealthContext).detailData; + + // console.log('healthData (in docker-stats-chart):', healthData); + + // Declare a dockerStats obj to store extracted Docker stats. + let dockerStats = {}; + // This var will store the ind of the most recent data pt in healthData that matches currently selected microsvc. + let index; + + // Scan from the end of the data collection in heathData. (End = most recent) + // Use the loop to find the index of the data pt that matches current microsrvc (props.service). + // We are only extracting this most recent data pt. + for (let i = healthData.length - 1; i >= 0; i -= 1) { + // If user-chosen DB is NoSQL (Mongo): + if (healthData[i].currentMicroservice === props.service) { + index = i; + // db = 'mongo'; + break; + } + // If SQL: + if (healthData[i].currentmicroservice === props.service) { + index = i; + // db = 'sql'; + break; + } + } + + // Note: The difference btw MongoDB and pSQL is whether a stat title is camelCased (mongo = yes, psql = no). + // Extract Docker-related MongoDB data (if exists) and save to dockerStats obj. + if (healthData[index].containerId) { + dockerStats = { + 'Containerized service': healthData[index].currentMicroservice, + 'Container ID': healthData[index].containerId.slice(0, 7) + '[...]', + 'CPU usage %': parseFloat(healthData[index].containerCpuPercent).toFixed(2) + '%', + 'Mem usage %': parseFloat(healthData[index].containerMemPercent).toFixed(2) + '%', + 'Mem limit (Mb)': parseFloat(healthData[index].containerMemLimit / 1000000).toFixed(2), + 'Mem usage (Mb)': parseFloat(healthData[index].containerMemUsage / 1000000).toFixed(2), + 'Network I/O - Received (Kb)': parseFloat(healthData[index].networkReceived / 1000).toFixed(2), + 'Network I/O - Sent (Kb)': parseFloat(healthData[index].networkSent / 1000).toFixed(2), + 'Process count': healthData[index].containerProcessCount, + 'Restart count': healthData[index].containerRestartCount, + }; + } + // Extract Docker-related SQL data (if exists) and save to dockerStats obj. + if (healthData[index].containerid) { + dockerStats = { + 'Containerized service': healthData[index].currentmicroservice, + 'Container ID': healthData[index].containerid.slice(0, 7) + '[...]', + 'CPU usage %': parseFloat(healthData[index].containercpupercent).toFixed(2) + '%', + 'Mem usage %': parseFloat(healthData[index].containermempercent).toFixed(2) + '%', + 'Mem limit (Mb)': parseFloat(healthData[index].containermemlimit / 1000000).toFixed(2), + 'Mem usage (Mb)': parseFloat(healthData[index].containermemusage / 1000000).toFixed(2), + 'Network I/O - Received (Kb)': parseFloat(healthData[index].networkreceived / 1000).toFixed(2), + 'Network I/O - Sent (Kb)': parseFloat(healthData[index].networksent / 1000).toFixed(2), + 'Process count': healthData[index].containerprocesscount, + 'Restart count': healthData[index].containerrestartcount, + }; + } + + // Conditional rendering, depending on if dockerStats obj contains anything. + if (Object.keys(dockerStats).length > 0) { + // Build an array of s that'll be rendered. Each contains one stat. + const dockerStatsArr = []; + + for (let stat in dockerStats) { + dockerStatsArr.push( + + {stat}: {dockerStats[stat]} + + ) + } + + console.log('dockerStatsArr (docker stats chart):', dockerStatsArr); + return ( +
+
Docker Container Stats
+ {dockerStatsArr} +
+ ) + } else { // If no Docker data: + return ( +
+
+ No valid container ID for current microservice. + Please ensure the microservice is running in a Docker container. +
+
+ ) + } +} + +export default DockerStatsChart; \ No newline at end of file diff --git a/app/charts/latency-chart.jsx b/app/charts/latency-chart.jsx index c315aeac7..6fb72b495 100644 --- a/app/charts/latency-chart.jsx +++ b/app/charts/latency-chart.jsx @@ -1,37 +1,64 @@ import React, { useContext } from 'react'; -import { Line } from 'react-chartjs-2'; import HealthContext from '../context/DetailsContext'; +import Plot from 'react-plotly.js'; const LatencyChart = (props) => { - const xAxis = []; - const yAxis = []; const healthData = useContext(HealthContext).detailData; - for (let i = 0; i < healthData.length; i++) { - const element = healthData[i]; - if (element.currentmicroservice === props.service || element.currentMicroservice === props.service) { - xAxis.push(i); - yAxis.push(element.latency); + + const createChart = () => { + const xAxis = []; + const yAxis = []; + + for (let i = 0; i < healthData.length; i++) { + const element = healthData[i]; + if (element.currentmicroservice === props.service || element.currentMicroservice === props.service) { + xAxis.push(i); + yAxis.push(element.latency); + } } - } - const chartData = { - datasets: [ - { - label: `CPU latency of ${props.service}`, - data: yAxis, - backgroundColor: ['rgb(254, 255, 0)'], - }, - ], - options: { - xAxisID: 'TBD', - yAxisID: 'TBD', - }, - labels: xAxis, + + return ( + + ) }; - return ( -
- -
- ); + + return
{createChart()}
; }; export default LatencyChart; diff --git a/app/charts/memory-chart.jsx b/app/charts/memory-chart.jsx index 9ce96e480..e1105006b 100644 --- a/app/charts/memory-chart.jsx +++ b/app/charts/memory-chart.jsx @@ -1,6 +1,6 @@ import React, { useContext } from 'react'; -import { Bar } from 'react-chartjs-2'; import HealthContext from '../context/DetailsContext'; +import Plot from 'react-plotly.js'; const MemoryChart = (props) => { const healthData = useContext(HealthContext).detailData; @@ -31,40 +31,68 @@ const MemoryChart = (props) => { } } - const chartData = { - datasets: [ - { - label: 'Free Memory', - backgroundColor: 'rgb(2, 210, 249)', - data: free, - // showLine: true, - }, - { - label: 'Used Memory', - backgroundColor: 'rgb(239, 91, 145)', - data: used, - // showLine: true, - }, - { - label: 'Active Memory', - backgroundColor: 'rgb(182, 219, 26)', - data: active, - // showLine: true, - }, - { - label: 'Total Memory', - backgroundColor: 'rgb(252, 170, 52)', - data: total, - // showLine: true, - }, - ], - labels: xAxis, - }; - - return ; + return ( + + ) }; - return
{createChart()}
; + return
{createChart()}
; }; export default MemoryChart; diff --git a/app/charts/microservice-traffic.jsx b/app/charts/microservice-traffic.jsx index 24ae0bbdd..92eda1d00 100644 --- a/app/charts/microservice-traffic.jsx +++ b/app/charts/microservice-traffic.jsx @@ -1,106 +1,114 @@ import React, { useContext } from 'react'; -import { Bar } from 'react-chartjs-2'; +import Plot from 'react-plotly.js'; import CommunicationsContext from '../context/OverviewContext'; const MicroServiceTraffic = (props) => { const communicationsData = useContext(CommunicationsContext).overviewData; - //initialize an empty object resObj. This object will store the microservice names as values and its corresponding correlatingId or correlatingid as keys. The microservice names will be stored in array within the order it was to the database. + // initialize an empty object resObj. This object will store the microservice names as values and its corresponding correlatingId or correlatingid as keys. The microservice names will be stored in array within the order it was to the database. const resObj = {}; - if(communicationsData.length>0 && communicationsData[0]["_id"]){ - //Sort the communication array from latest to earliest document - communicationsData.sort((a,b)=>{ - if(new Date(a.timeSent) > new Date(b.timeSent)) return 1; - if(new Date(a.timeSent) < new Date(b.timeSent)) return -1; + if (communicationsData.length > 0 && communicationsData[0]._id) { + // Sort the communication array from latest to earliest document + communicationsData.sort((a, b) => { + if (new Date(a.timeSent) > new Date(b.timeSent)) return 1; + if (new Date(a.timeSent) < new Date(b.timeSent)) return -1; return 0; }); - - //Iterate over sorted communicationsData array from the end to the beginning - for (let i = 0; i < communicationsData.length; i+=1) { - //declare a constant element and initialize it as the object at index i of the communicationsData array + + // Iterate over sorted communicationsData array from the end to the beginning + for (let i = 0; i < communicationsData.length; i += 1) { + // declare a constant element and initialize it as the object at index i of the communicationsData array const element = communicationsData[i]; - //Pushes the microservice name into the object + // Pushes the microservice name into the object if (resObj[element.correlatingId]) { resObj[element.correlatingId].push(element.currentMicroservice); - } - else resObj[element.correlatingId] = [element.currentMicroservice]; + } else resObj[element.correlatingId] = [element.currentMicroservice]; } - } - - else { - for (let i = communicationsData.length-1; i >= 0; i--) { + } else { + for (let i = communicationsData.length - 1; i >= 0; i--) { const element = communicationsData[i]; - if (resObj[element.correlatingid]) resObj[element.correlatingid].push(element.currentmicroservice); + if (resObj[element.correlatingid]) + resObj[element.correlatingid].push(element.currentmicroservice); else resObj[element.correlatingid] = [element.currentmicroservice]; // initializing the object with the first microservice - }; + } } - //use object values to destructure locations + // use object values to destructure locations const tracePoints = Object.values(resObj); // Declare Micro-server-count dictinary to capture the amount of times a particular server is hit - const microServiceCountdictionary = {}; + const microServiceCountdictionary = {}; - //array logging every ping present in communications table ---> flat used to flatten multidimensional array and return 1d array + // array logging every ping present in communications table ---> flat used to flatten multidimensional array and return 1d array const tracePointLog = tracePoints.flat(Infinity); // iterate over Trace Points - for (let i = 0; i < tracePointLog.length; i+=1) { - + for (let i = 0; i < tracePointLog.length; i += 1) { // populate Micro-count dictionary if (!microServiceCountdictionary[tracePointLog[i]]) { microServiceCountdictionary[tracePointLog[i]] = 1; } else { - microServiceCountdictionary[tracePointLog[i]]+=1 + microServiceCountdictionary[tracePointLog[i]] += 1; } - }; + } // capture values of microServiceCountdictionary to use as data to populate chart object const serverPingCount = Object.values(microServiceCountdictionary); // variable 10 points higher than max number in microservicesDictionary aggregation --> variable allows for top level spacing on bar graph - const yAxisHeadRoom = (Math.max(...serverPingCount) + 10); + const yAxisHeadRoom = Math.max(...serverPingCount) + 10; - // Create chart object data to feed into bar component + // Create chart object data to feed into bar component const myChart = { - //spread dictionary keys inorder to properly label chart x axis + // spread dictionary keys inorder to properly label chart x axis labels: [...Object.keys(microServiceCountdictionary)], datasets: [ - { - label: 'Times server Pinged', - backgroundColor: 'rgba(241, 207, 70,1)', - borderColor: 'rgba(0,0,0,1)', - borderWidth: 1, - data: [...serverPingCount, 0, yAxisHeadRoom] // spread ping count array into data array to have chart populate the Y axis - } - ], - } + { + label: 'Times server Pinged', + backgroundColor: 'rgba(241, 207, 70,1)', + borderColor: 'rgba(0,0,0,1)', + borderWidth: 1, + data: [...serverPingCount, 0, yAxisHeadRoom], // spread ping count array into data array to have chart populate the Y axis + }, + ], + }; - - // return div with Bar component and Trace Points data return ( -
- -
- ) + + ); }; export default MicroServiceTraffic; - diff --git a/app/charts/processes-chart.jsx b/app/charts/processes-chart.jsx index c8c99d8be..57ccaeae3 100644 --- a/app/charts/processes-chart.jsx +++ b/app/charts/processes-chart.jsx @@ -1,9 +1,10 @@ import React, { useContext } from 'react'; -import { Bar } from 'react-chartjs-2'; +import Plot from 'react-plotly.js'; import HealthContext from '../context/DetailsContext'; const ProcessesChart = (props) => { const healthData = useContext(HealthContext).detailData; + const createChart = () => { const communicationLabel = []; const totalProcesses = []; @@ -31,31 +32,69 @@ const ProcessesChart = (props) => { } } - const chartData = { - datasets: [{ - label: 'Blocked Processes', - backgroundColor: 'rgb(198, 42, 177)', - data: blockedProcesses, - }, { - label: 'Sleeping Processes', - backgroundColor: 'rgb(252, 170, 52)', - data: sleepingProcesses, - }, { - label: 'Running Processes', - backgroundColor: 'rgb(239, 91, 145)', - data: runningProcesses, - }, { - label: 'Total Processes', - backgroundColor: 'rgb(182, 219, 26)', - data: totalProcesses, - }], - labels: communicationLabel, - }; - - return ; + return ( + + ) }; - - return
{createChart()}
; + + return
{createChart()}
; }; export default ProcessesChart; diff --git a/app/charts/request-type-chart.jsx b/app/charts/request-type-chart.jsx index 42d025742..e2de60421 100644 --- a/app/charts/request-type-chart.jsx +++ b/app/charts/request-type-chart.jsx @@ -1,11 +1,11 @@ import React, { useContext } from 'react'; -import { Doughnut } from 'react-chartjs-2'; +import Plot from 'react-plotly.js'; import CommunicationsContext from '../context/OverviewContext'; const RequestTypesChart = (props) => { const communicationsData = useContext(CommunicationsContext).overviewData; - const createRequestChart = () => { + const createRequestChart = () => { const requestObj = { DELETE: 0, GET: 0, @@ -23,27 +23,49 @@ const RequestTypesChart = (props) => { else if (element.currentmicroservice === props.service && element.reqtype in requestObj) requestObj[element.reqtype] += 1; } - const chartData = { - datasets: [ - { - data: Object.values(requestObj), - backgroundColor: [ - 'rgb(2, 210, 249)', - 'rgb(198, 42, 177)', - 'rgb(252, 170, 52)', - 'rgb(239, 91, 145)', - 'rgb(182, 219, 26)', - 'rgb(254, 255, 0)', - ], - }, - ], - labels: ['DELETE', 'GET', 'PATCH', 'POST', 'PUSH', 'PUT'], - }; - - return ; + return ( + + ) }; - return
{createRequestChart()}
; + return
{createRequestChart()}
; }; -export default RequestTypesChart; +export default RequestTypesChart; \ No newline at end of file diff --git a/app/charts/response-code-chart.jsx b/app/charts/response-code-chart.jsx index 1dd3575e2..50a90e1d6 100644 --- a/app/charts/response-code-chart.jsx +++ b/app/charts/response-code-chart.jsx @@ -1,12 +1,10 @@ import React, { useContext } from 'react'; -import { Doughnut } from 'react-chartjs-2'; +import Plot from 'react-plotly.js'; import CommunicationsContext from '../context/OverviewContext'; const ResponseCodeChart = (props) => { const communicationsData = useContext(CommunicationsContext).overviewData; - - const createChart = () => { const responseCodes = { '100-199': 0, @@ -14,13 +12,13 @@ const ResponseCodeChart = (props) => { '300-399': 0, '400-499': 0, '500-599': 0, - 'NULL': 0, + NULL: 0, }; for (let i = 0; i < communicationsData.length; i += 1) { const element = communicationsData[i]; // If Mongo Else SQL - if ((element.currentMicroservice === props.service) && element.resStatus) { + if (element.currentMicroservice === props.service && element.resStatus) { const statusCode = element.resStatus; if (statusCode <= 199) { responseCodes['100-199'] += 1; @@ -33,9 +31,12 @@ const ResponseCodeChart = (props) => { } else if (statusCode <= 599) { responseCodes['500-599'] += 1; } else { - responseCodes['NULL'] += 1; + responseCodes.NULL += 1; } - } else if ((element.currentmicroservice === props.service) && element.resstatus) { + } else if ( + element.currentmicroservice === props.service && + element.resstatus + ) { const statusCode = element.resstatus; if (statusCode <= 199) { responseCodes['100-199'] += 1; @@ -48,30 +49,60 @@ const ResponseCodeChart = (props) => { } else if (statusCode <= 599) { responseCodes['500-599'] += 1; } else { - responseCodes['NULL'] += 1; + responseCodes.NULL += 1; } } } - const chartData = { - datasets: [ - { - data: Object.values(responseCodes), - backgroundColor: [ - 'rgb(2, 210, 249)', - 'rgb(198, 42, 177)', - 'rgb(252, 170, 52)', - 'rgb(239, 91, 145)', - 'rgb(182, 219, 26)', - 'rgb(254, 255, 0)', + return ( + ; + type: 'pie', + textposition: 'inside', + domain: { y: [0, 2] }, + marker: { + colors: [ + '#fa1a58', + '#f3f5fe', + '#00eda0', + '#00fff2', + '#73605b', + ], + }, + }]} + layout={{ + title: { + text: 'Response Status Codes', + font: {size: 22} + }, + height: 400, + width: 400, + font: { + color: 'black', + size: 15, + family: 'Nunito, san serif' + }, + displaylogo: false, + paper_bgcolor: 'white', + legend: { + orientation: 'h', + xanchor: 'center', + x: .5 + }, + }} + /> + ); }; - return
{createChart()}
; + + return
{createChart()}
; }; export default ResponseCodeChart; diff --git a/app/charts/route-copy.jsx b/app/charts/route-copy.jsx new file mode 100644 index 000000000..6b3d651d2 --- /dev/null +++ b/app/charts/route-copy.jsx @@ -0,0 +1,207 @@ +/* eslint-disable react/jsx-one-expression-per-line */ +import React, { useContext } from 'react'; +import CommunicationsContext from '../context/OverviewContext'; +import '../stylesheets/routes.css'; + +const RouteLocations = (props) => { + const communicationsData = useContext(CommunicationsContext).overviewData; + // console.log('commData (from overviewContxt):', communicationsData); + + // initialize an empty object resObj. + // This object will store the microservice names as values and its corresponding correlatingId or correlatingid as keys. + // The microservice names will be stored in array within the order it was to the database. + const resObj = {}; + + if (communicationsData.length > 0 && communicationsData[0]._id) { + // Sort the communication array from OLDEST to NEWEST documents. + communicationsData.sort((a, b) => { + // Note that a newer date obj IS GREATER THAN an older date obj. + if (new Date(a.timeSent) > new Date(b.timeSent)) return 1; + if (new Date(a.timeSent) < new Date(b.timeSent)) return -1; + return 0; + }); + // console.log('commData (AFTER sorting):', communicationsData); + + // Iterate over sorted array to build up resObj. + for (let i = 0; i < communicationsData.length; i += 1) { + // declare a constant element and initialize it as the object at index i of the communicationsData array + const element = communicationsData[i]; + // Pushes the microservice name & timeSent into the resObj. + // Data objects w/ same corrId will be grouped in a same array. + if (resObj[element.correlatingId]) { + resObj[element.correlatingId].push({ + microservice_name: element.currentMicroservice, + timeSent: element.timeSent + }); + } else { + // The value that corresp. to the correlationId key is an array of obj containing name and time data. + // Each obj is a data point. + resObj[element.correlatingId] = [{ + microservice_name: element.currentMicroservice, + timeSent: element.timeSent + }]; + } + } + } else { + for (let i = communicationsData.length - 1; i >= 0; i--) { + const element = communicationsData[i]; + if (resObj[element.correlatingId]) { + resObj[element.correlatingId].push({ + microservice_name: element.currentMicroservice, + timeSent: element.timeSent + }); + } else { + // The value that corresp. to the correlationId key is an array of obj containing name and time data. + // Each obj is a data point. + resObj[element.correlatingId] = [{ + microservice_name: element.currentMicroservice, + timeSent: element.timeSent + }]; + } + // initializing the object with the first microservice + } + } + + // use Object.values to destructure locations + // Each elem in tracePoints is an array of arrays, which contain objects (each of which is a data point). + // Filter the array so that only subarrays w/ len > 1 are kept. + // (len == 1 means there's only one point in the route. There's no meaningful data to be gained from those.) + const tracePoints = Object.values(resObj).filter(subArray => subArray.length > 1); + // console.log('tracePoints arr:', tracePoints); + + + // Construct an obj that stores data necessary for calculating avg speed of requests btw 2 pts. + const avgDataObj = {}; + /****** Build the object here w/ nested loops ************/ + /****** WARNING: tracePoints arr can be very long (100+) ************/ + for (let i = 0; i < tracePoints.length; i += 1) { + let subArr = tracePoints[i]; + for (let j = 0; j < subArr.length; j += 1) { + let currDataObj = subArr[j]; + if (j < subArr.length - 1) { + let nextDataObj = subArr[j + 1]; + let routeName = `${currDataObj.microservice_name}-${nextDataObj.microservice_name}`; + // Key/value pair that keeps COUNT of two-point routes + if (!avgDataObj[`${routeName}Count`]) avgDataObj[`${routeName}Count`] = 1; + else avgDataObj[`${routeName}Count`] += 1; + + // Key/value that accumulates TOTAL TIME a req travels btw 2 certain points + let timeDiff = new Date(nextDataObj.timeSent) - new Date(currDataObj.timeSent); + if (!avgDataObj[`${routeName}TotalTime`]) { + avgDataObj[`${routeName}TotalTime`] = timeDiff; + } + else avgDataObj[`${routeName}TotalTime`] += timeDiff; + + // Key/value that calculates AVG TIME of travel (dividing the 2 values above) + let avgTime = avgDataObj[`${routeName}TotalTime`] / avgDataObj[`${routeName}Count`]; + avgDataObj[`${routeName}AvgTime`] = avgTime; + } + } + } + /** End of nested loops */ + console.log('avgDataObj:', avgDataObj); + /****************************************/ + + // Array of to be rendered. Each
contains route name and time difference. + const resArray = []; + + const position = communicationsData[0].correlatingid ? 0 : tracePoints.length - 1; + console.log('position for tracePoints:', position); + + // iterate over ONE elem in tracePoints, creating a
for every data obj. + for (let i = 0; i < tracePoints[position].length; i += 1) { + if (i !== tracePoints[position].length - 1) { + // Calc time difference (when not at the end of array): + // Convert time str to Date obj w/ new Date(), then get the time difference. + const timeDiff = new Date(tracePoints[position][i + 1].timeSent) - new Date(tracePoints[position][i].timeSent); + resArray.push( +
+ {/* Altering this

so it displays only microsvc_name */} +

+ Point {i + 1}: {tracePoints[position][i].microservice_name} +

+ {/* Adding another

that displays time difference btw curr obj and next obj */} +

+ {/* Time: {tracePoints[position][i].timeSent} */} + Time elapsed: {timeDiff} ms +

+
, + ); + } else { + // If at the end of array, don't push the timeDiff

to resArray (only push a

w/ name). + resArray.push( +

+

+ Point {i + 1}: {tracePoints[position][i].microservice_name} +

+
, + ); + } + } + // console.log('resArray: ', resArray); + + /**** Making a list of avg speed-related data. ********/ + // const avgData = []; + // Object.entries(avgDataObj).forEach((el, i) => { + // avgData.push( + // + // {el[0]}: {el[1]} + // + // ) + // }) + // console.log('avgData (array):', avgData); + + /**** Making CATEGORIZED lists of avg speed-related data. ********/ + const avgTime = [], totalTime = [], count = []; + let i = 0; // For unique keys for each // + + for (const key in avgDataObj) { + i += 1; + + if (key.endsWith('AvgTime')) { + avgTime.push( + + {key.slice(0, -7)}: {avgDataObj[key]} ms + + ) + } + if (key.endsWith('TotalTime')) { + totalTime.push( + + {key.slice(0, -9)}: {avgDataObj[key]} ms + + ) + } + if (key.endsWith('Count')) { + count.push( + + {key.slice(0, -5)}: {avgDataObj[key]} + + ) + } + } + // console.log('avgTime:', avgTime); + // console.log('totalTime:', totalTime); + // console.log('count:', count); + /****************/ + + return ( +
+ {/* Data on the lastest route */} + {resArray} + + {/* Rendering avg-speed related data */} +
+ {/* {avgData} */} + Average time between points: + {avgTime} + Total time between points: + {totalTime} + Number of trips between points: + {count} +
+
+ ); +}; + +export default RouteLocations; diff --git a/app/charts/route-trace.jsx b/app/charts/route-trace.jsx index 3f2fd6aeb..dea2fdee0 100644 --- a/app/charts/route-trace.jsx +++ b/app/charts/route-trace.jsx @@ -1,65 +1,88 @@ -/* eslint-disable react/jsx-one-expression-per-line */ -import React, { useContext } from 'react'; -import { Bar } from 'react-chartjs-2'; -import CommunicationsContext from '../context/OverviewContext'; +import * as d3 from 'd3'; +class RouteTrace { + containerEl; + props; + svg; + + // creating D3 container for the nodes + constructor(containerEl, props) { + this.containerEl = containerEl; + this.props = props; + const { width, height } = props; + this.svg = d3.select(containerEl) + .append('svg') + .style('background-color', 'white') + .attr('width', width) + .attr('height', height); + this.updateDatapoints(); + } -const RouteLocations = (props) => { - const communicationsData = useContext(CommunicationsContext).overviewData; + updateDatapoints = () => { + const { svg, props: { data, width, height } } = this; + console.log('data: ', data) + console.log('nodes: ', data[0]) + console.log('links: ', data[1]) + + const simulation = d3.forceSimulation(data[0]) + .force("link", d3.forceLink(data[1]).id(data => data.id)) + .force('charge', d3.forceManyBody().strength(-3000)) + .force('center', d3.forceCenter(width / 2 , height / 2)) - // initialize an empty object resObj. This object will store the microservice names as values and its corresponding correlatingId or correlatingid as keys. The microservice names will be stored in array within the order it was to the database. - const resObj = {}; + const link = svg.append("g") + .attr("class", "links") + .selectAll("line") + .data(data[1]) + .enter() + .append("line") + .style('stroke', 'black') - if (communicationsData.length > 0 && communicationsData[0]._id) { - // Sort the communication array from latest to earliest document - communicationsData.sort((a, b) => { - if (new Date(a.timeSent) > new Date(b.timeSent)) return 1; - if (new Date(a.timeSent) < new Date(b.timeSent)) return -1; - return 0; - }); + const node = svg.append("g") + .attr("class", "nodes") + .selectAll("g") + .data(data[0]) + .enter() + .append("g") - // Iterate over sorted communicationsData array from the end to the beginning - for (let i = 0; i < communicationsData.length; i += 1) { - // declare a constant element and initialize it as the object at index i of the communicationsData array - const element = communicationsData[i]; - // Pushes the microservice name into the object - if (resObj[element.correlatingId]) { - resObj[element.correlatingId].push(element.currentMicroservice); - } else resObj[element.correlatingId] = [element.currentMicroservice]; - } - } else { - for (let i = communicationsData.length - 1; i >= 0; i--) { - const element = communicationsData[i]; - if (resObj[element.correlatingid]) resObj[element.correlatingid].push(element.currentmicroservice); - else resObj[element.correlatingid] = [element.currentmicroservice]; - // initializing the object with the first microservice - } - } + const circles = node.append("circle") + .attr("r", 20) + .attr("fill", '#00eda0') + .on('mouseover', () => d3.select(this).text(d.id)) + .on('mouseup', (d, i, nodes) => this.setActiveDatapoint(d, nodes[i])) - // use object values to destructure locations - const tracePoints = Object.values(resObj); - const position = communicationsData[0].correlatingid ? 0 : tracePoints.length - 1; + const lables = node.append("text") + .text(d => d.id) + .attr('x', d => d.x) + .attr('y', d => d.y) - const resArray = []; + node.append("title") + .text(d => d.id) - // iterate over Trace Points - for (let i = 0; i < tracePoints[position].length; i += 1) { - // push into resulting array current tracepoint as div - resArray.push( -
-

Point {i + 1}: {tracePoints[position][i]}

-
, - ); - } + simulation + .nodes(data[0]) + .on('tick', ticked); - console.log('resArray: ', resArray); + simulation.force('link') + .links(data[1]); // error: cannot create property vx on books + function ticked() { + link + .attr("x1", d => d.source.x) + .attr("y1", d => d.source.y) + .attr("x2", d => d.target.x) + .attr("y2", d => d.target.y); + + node + .attr("transform", function(d) { + return "translate(" + d.x + "," + d.y + ")"; + }) + } + } + + setActiveDatapoint = (d, node) => { + d3.select(node).style('fill', 'yellow'); + this.props.onDatapointClick(d); + } - // return div with Trace Points data - return ( -
- {resArray} -
- ); -}; +} -export default RouteLocations; +export default RouteTrace; diff --git a/app/charts/speed-chart.jsx b/app/charts/speed-chart.jsx index 9a22dd34e..3753cf10f 100644 --- a/app/charts/speed-chart.jsx +++ b/app/charts/speed-chart.jsx @@ -1,5 +1,5 @@ import React, { useContext } from 'react'; -import { Line } from 'react-chartjs-2'; +import Plot from 'react-plotly.js'; import HealthContext from '../context/DetailsContext'; const SpeedChart = (props) => { @@ -24,24 +24,49 @@ const SpeedChart = (props) => { } } - const chartData = { - datasets: [ - { - label: `CPU Speed of ${props.service}`, - data: yAxis, - backgroundColor: [ - 'rgb(2, 210, 249)', - ], - }, - ], - options: { - }, - xAxisID: 'Speed', - yAxisID: 'Communicaton', - labels: xAxis, - }; - - return ; + return ( + + ) }; return
{createChart()}
; diff --git a/app/charts/temperature-chart.jsx b/app/charts/temperature-chart.jsx index 6aae7368a..e3855eabf 100644 --- a/app/charts/temperature-chart.jsx +++ b/app/charts/temperature-chart.jsx @@ -1,5 +1,5 @@ import React, { useContext } from 'react'; -import { Line } from 'react-chartjs-2'; +import Plot from 'react-plotly.js'; import HealthContext from '../context/DetailsContext'; const TemperatureChart = (props) => { @@ -8,6 +8,7 @@ const TemperatureChart = (props) => { const createChart = () => { const yAxis = []; const xAxis = []; + for (let i = 0; i < healthData.length; i += 1) { const element = healthData[i]; // If Mongo @@ -23,19 +24,40 @@ const TemperatureChart = (props) => { } } - const chartData = { - datasets: [ - { - label: 'Temperature Data', - data: xAxis, - backgroundColor: [ - 'rgb(2, 210, 249)', - ], - }, - ], - labels: yAxis, - }; - return ; + return ( + + ) }; return
{createChart()}
; diff --git a/app/components/AddService.jsx b/app/components/AddService.jsx index 611bb5983..5f36b9c88 100644 --- a/app/components/AddService.jsx +++ b/app/components/AddService.jsx @@ -1,7 +1,6 @@ import React, { useState, useContext, useEffect } from 'react'; -// import logo from 'app/assets/logo2.png'; import SetupContext from '../context/SetupContext'; -import ServicesDashboard from './ServicesDashboard.jsx'; +import '../stylesheets/AddService.css'; const { ipcRenderer } = window.require('electron'); @@ -34,15 +33,10 @@ const AddService = () => { return (
-

Enter Your Database Information - - ⓘ -
{tooltipWriteup}
-

-
+ Database Type: { - setChartTitle(event.path[0].alt); - setModalChart(event.path[0].id); - toggleModalDisplay(!modalDisplay); - }} - service={service} - type="image" - id={buttonProperties[i].id} - src={buttonProperties[i].src} - width="60px" - alt={buttonProperties[i].alt} - /> -
-
- {buttonProperties[i].id} -
-
-
, - ); - } - + const { service } = props; + + //Capitalize service name + const title = service[0].toUpperCase() + service.substring(1); return (
- {modalDisplay ? ( - { - toggleModalDisplay(!modalDisplay); - }} - /> - ) : null} - -

Microservice Health - {service}

-
- {healthInfoButtons} -
- +

{title}

+
); }; diff --git a/app/components/ServiceOverview.jsx b/app/components/ServiceOverview.jsx deleted file mode 100644 index 2fe2c767a..000000000 --- a/app/components/ServiceOverview.jsx +++ /dev/null @@ -1,226 +0,0 @@ -import React, { useState, useEffect, useContext } from 'react'; -import routeChart from '../assets/routeChart.png'; -import OverviewContext from '../context/OverviewContext'; -import HealthInformationContext from '../context/DetailsContext'; -import ServiceDetails from './ServiceDetails.jsx'; -import Modal from './Modal.jsx'; - - -const { ipcRenderer } = window.require('electron'); - -const ServiceOverview = (props) => { - // Overview state used to create service buttons - const [overviewState, setOverviewState] = useState([]); - - // Details state used to cause rerender on user selection. - const [detailsSelected, setDetails] = useState(); - - // Contexts have data added to them following successful IPC return. Data is later used to create charts. - const serviceComponents = useContext(OverviewContext); - const healthdata = useContext(HealthInformationContext); - - // Only happens when the component mounts. - useEffect(() => { - // IPC communication used to initiate query for information on microservices. - ipcRenderer.send('overviewRequest', props.index); - // IPC listener responsible for retrieving infomation from asynchronous main process message. - ipcRenderer.on('overviewResponse', (event, data) => { - // Adds to state and context. - console.log(JSON.parse(data)); - setOverviewState(Object.values(JSON.parse(data))); - serviceComponents.overviewData = JSON.parse(data); - }); - }, []); - - console.log('overviewstate: ', overviewState); - // Add routes to the display - // Hook used to toggle whether or not the Modal component renders - const [modalDisplay, toggleModalDisplay] = useState(false); - // Hook used to set the chart that the Modal displays. The - // modalDisplay state is drilled into the Modal component. - const [modalChart, setModalChart] = useState(); - // Hook used to set the Modal Component title. The "alt" attribute - // is grabbed from the onClick event via event.path[0].alt - const [chartTitle, setChartTitle] = useState(); - - // route button AND traffic button property - const routeButtonProperty = { - traffic: { id: 'Traffic', alt: 'Microservice Traffic', src: 'app/assets/chartModal.png' }, - routes: { id: 'routesImage', alt: 'Route Trace', src: routeChart }, - }; - - // declare routes array to display routes when modal is toggled - const routes = []; - // declare traffic array to display traffic when modal is toggled - const traffic = []; - - // push traffic component logic traffic - traffic.push( - -
- { - setChartTitle(event.path[0].alt); - setModalChart(event.path[0].id); - toggleModalDisplay(!modalDisplay); - }} - type="image" - id={routeButtonProperty.traffic.id} - src={routeButtonProperty.traffic.src} - width="60px" - alt={routeButtonProperty.traffic.alt} - /> -
-
- {routeButtonProperty.traffic.id} -
-
, - - ); - - // push routes component logic traffic - routes.push( - -
- { - setChartTitle(event.path[0].alt); - setModalChart(event.path[0].id); - toggleModalDisplay(!modalDisplay); - }} - type="image" - id={routeButtonProperty.routes.id} - src="app/assets/routeChart.png" - width="60px" - alt={routeButtonProperty.routes.alt} - /> -
-
- Routes -
-
, - - ); - - // Filters data received from IPC to the communications database to create a list of the services tracked in the provided database, - const serviceList = () => { - // Holds the buttons generated for unique services. - const componentButtons = []; - - // Tracks which services already have button created. - const serviceCache = {}; - - for (let i = 0; i < overviewState.length; i += 1) { - const element = overviewState[i]; - // If SQL - if (element.currentmicroservice) { - if (!(element.currentmicroservice in serviceCache)) { - const button = ( - - ); - componentButtons.push(button); - serviceCache[element.currentmicroservice] = true; - } - } else if (element.currentMicroservice) { - // If Mongo - if (element.currentMicroservice) { - if (!(element.currentMicroservice in serviceCache)) { - const button = ( - - ); - componentButtons.push(button); - serviceCache[element.currentMicroservice] = true; - } - } - } - } - // If there's no data, return 'No data present', else return microservices button - if (componentButtons.length === 0) { - return ( -

No data present

- ); - } - return componentButtons; - }; - - const tooltipWriteup = `Communications data - Routes and Traffic - is not specific to a single microservice, - but combines data from all microservices within a single application network.`; - - const tooltipWriteup2 = 'View and toggle between health data for individual services within your microservice network.'; - - return ( -
-

Microservices Overview

-

- Communications Data - - ⓘ -
{tooltipWriteup}
-
-

- {modalDisplay ? ( - { - toggleModalDisplay(!modalDisplay); - }} - /> - ) : null} -
- {routes} - {traffic} -
-
- {serviceList()} - - ⓘ -
{tooltipWriteup2}
-
-
-
- {detailsSelected || null} -
- ); -}; - -export default ServiceOverview; diff --git a/app/components/ServicesDashboard.jsx b/app/components/ServicesDashboard.jsx deleted file mode 100644 index 1f2afc554..000000000 --- a/app/components/ServicesDashboard.jsx +++ /dev/null @@ -1,100 +0,0 @@ -/* eslint-disable react/jsx-one-expression-per-line */ -import React, { useState, useContext } from 'react'; -import ServiceOverview from './ServiceOverview.jsx'; -import DashboardContext from '../context/DashboardContext'; -import SetupContext from '../context/SetupContext'; -import AddService from './AddService.jsx'; -import DeleteService from './DeleteService.jsx'; - -const ServicesDashboard = (props) => { - // Used to toggle setup required if user wants to add a new database. - const setup = useContext(SetupContext); - - // List of the databases saved by users to track microservices. - const serviceList = useContext(DashboardContext); - - // Used to hold the buttons created for each database found in context. - const [serviceSelected, setSelection] = useState(); - - // Creates button for each database in dashboard context. - const renderServiceList = (context) => { - const buttonStore = []; - for (let i = 0; i < context.length; i += 1) { - buttonStore.push( - , - ); - } - return buttonStore; - }; - - const tooltipWriteup = 'Please provide a different database to monitor additional microservice applications.'; - - return ( -
-
-
- -
-

- Your Databases - - ⓘ -
{tooltipWriteup}
-
-

- {renderServiceList(serviceList)} -
-
- - -
-
- -
- -
-
-
- {serviceSelected} -
-
- ); -}; - -export default ServicesDashboard; diff --git a/app/components/Splash.jsx b/app/components/Splash.jsx index 67b5b5f1b..f335bbc8d 100644 --- a/app/components/Splash.jsx +++ b/app/components/Splash.jsx @@ -1,4 +1,5 @@ import React, { useEffect } from 'react'; +import '../stylesheets/splash.css'; const { ipcRenderer } = window.require('electron'); diff --git a/app/containers/DashboardContainer.jsx b/app/containers/DashboardContainer.jsx new file mode 100644 index 000000000..113f24fc2 --- /dev/null +++ b/app/containers/DashboardContainer.jsx @@ -0,0 +1,17 @@ +import React, { useState } from 'react'; +import Monitoring from './MonitoringContainer.jsx'; +import SidebarContainer from './SideBarContainer.jsx'; +import '../stylesheets/dashboard.css'; + +const DashboardContainer = () => { + const [detailsSelected, setDetails] = useState(); + + return ( +
+ + +
+ ); +}; + +export default DashboardContainer; diff --git a/app/containers/GraphsContainer.jsx b/app/containers/GraphsContainer.jsx new file mode 100644 index 000000000..91f93fa9d --- /dev/null +++ b/app/containers/GraphsContainer.jsx @@ -0,0 +1,79 @@ +import React, { useState, useEffect, useRef } from 'react'; +import RequestTypesChart from '../charts/request-type-chart.jsx'; +import ResponseCodesChart from '../charts/response-code-chart.jsx'; +import SpeedChart from '../charts/speed-chart.jsx'; +import ProcessesChart from '../charts/processes-chart.jsx'; +import TemperatureChart from '../charts/temperature-chart.jsx'; +import LatencyChart from '../charts/latency-chart.jsx'; +import MemoryChart from '../charts/memory-chart.jsx'; +import RouteTrace from '../charts/route-trace.jsx'; +// import RouteLocations from '../charts/route-copy.jsx'; +import MicroServiceTraffic from '../charts/microservice-traffic.jsx'; +import DockerStatsChart from '../charts/docker-stats-chart.jsx'; +import '../stylesheets/graphs.css'; + +let vis; + +const GraphsContainer = (props) => { + const { service } = props; + const initialData = { + nodes: [ + { id: 'reverse-proxy' }, + { id: 'books' }, + { id: 'customers' }, + { id: 'orders' }, + ], + links: [ + { source: 'reverse-proxy', target: 'books' }, + { source: 'reverse-proxy', target: 'customers' }, + { source: 'reverse-proxy', target: 'orders' }, + { source: 'books', target: 'orders' }, + { source: 'customers', target: 'books' }, + { source: 'orders', target: 'customers' }, + ], + }; + + const [data, setData] = useState(null); + const [width, setWidth] = useState(400); + const [height, setHeight] = useState(400); + const [active, setActive] = useState(null); + const canvas = useRef(null); + + useEffect(fetchData, []); + useEffect(initVis, [ data ]); + + function fetchData() { + Promise.resolve().then(() => setData(Object.values(initialData))); + } + + function initVis() { + if (data && data.length) { + const d3Props = { + data, + width, + height, + onDatapointClick: setActive, + }; + vis = new RouteTrace(canvas.current, d3Props); + } + } + + return ( +
+
+
+
+ + + + + + + + + +
+ ); +}; + +export default GraphsContainer; diff --git a/app/containers/MonitoringContainer.jsx b/app/containers/MonitoringContainer.jsx new file mode 100644 index 000000000..dba3fa1be --- /dev/null +++ b/app/containers/MonitoringContainer.jsx @@ -0,0 +1,10 @@ +import React from 'react'; +import '../stylesheets/monitoring.css' + +const MonitoringContainer = (props) => { + const { detailsSelected } = props; + + return <>{detailsSelected || null}; +}; + +export default MonitoringContainer; diff --git a/app/containers/SideBarContainer.jsx b/app/containers/SideBarContainer.jsx new file mode 100644 index 000000000..475060743 --- /dev/null +++ b/app/containers/SideBarContainer.jsx @@ -0,0 +1,76 @@ +import React, { useState, useContext } from 'react'; +import Header from '../AComp/SidebarHeader.jsx'; +import Extras from '../AComp/Extras.jsx'; +import SetupContext from '../context/SetupContext'; +import DashboardContext from '../context/DashboardContext'; +import Microservices from '../AComp/Microservices.jsx'; +import ServicesList from '../AComp/ServicesList.jsx'; +import AddService from '../components/AddService.jsx'; +import DeleteService from '../components/DeleteService.jsx'; +import '../stylesheets/sidebar.css'; + +const SidebarContainer = (props) => { + const { setDetails } = props; + + // Used to toggle setup required if user wants to add a new database. + const setup = useContext(SetupContext); + + // List of the databases saved by users to track microservices. + const serviceList = useContext(DashboardContext); + + // Setting index of service selected + const [index, setIndex] = useState(); + + // Checking to see if service clicked to display Microservices + const [isclicked, setClicked] = useState('false'); + + // Helper function to check if Clicked toggles + const clickToggle = () => { + if (isclicked === 'true') setClicked('false'); + else setClicked('true'); + }; + // Click function for Services + const ServicesClick = (e) => { + clickToggle(e); + setIndex(e.target.id); + setDetails(null); + }; + // Click function for AddService + const AddClick = () => { + setup.setupRequired = setup.toggleSetup(false); + setDetails(); + }; + + // Click fn for Delete Service + const DeleteClick = () => { + setDetails(); + }; + + // Click fn for Refresh + const RefreshClick = () => { + location.reload(); + }; + + return ( +
+
+
+ + {isclicked === 'true' ? ( + + ) : null} + +
+
+ ); +}; + +export default SidebarContainer; diff --git a/app/index.css b/app/index.css deleted file mode 100644 index 292536b8d..000000000 --- a/app/index.css +++ /dev/null @@ -1,463 +0,0 @@ -@import url('https://fonts.googleapis.com/css?family=Baloo+Bhaijaan&display=swap'); - -body { - background-color: #333; - background: rgb(25,0,36); -} - -html { - /* prevent the gradient from repeating past the visible section*/ - min-height: 100% -} - -body { - /* background-color: #333; Original background*/ - background: linear-gradient(135deg, rgba(25,0,36,1) 0%, rgb(68, 68, 68) 0%, rgb(29, 29, 29) 100%); -} - -img { - margin-bottom: -75px; -} - -#splash { - display: flex; - flex-direction: column; - align-content: center; - align-items: center; - justify-content: center; - justify-items: center; - position: absolute; - top: 0; - left: 0; - width: 100%; - height: 100%; - opacity: 1; - animation-name: fadeInOpacity; - animation-iteration-count: 1; - animation-timing-function: ease-out; - animation-duration: 3s; -} - -@keyframes fadeInOpacity { - 0% { - opacity: 1; - } - 66% { - opacity: .66 - } - 100% { - opacity: 0; - } -} - -#splashLogo { -height: 300px; -margin-bottom: 0px; -margin-top: -100px; -} - -#splashText { - color: #FCE356; - font-size: 9vw; - letter-spacing: 100px; - margin-right: -100px; - font-family: Baloo Bhaijaan; - font-weight: lighter; -} - -#addServiceLogo { - margin-bottom: 0px; -} - -.tooltip { - font-size: 10px; - position: relative; - /* display: inline-block; */ - z-index: 0; -} - -.tooltiptext { - visibility: hidden; - font-size: 12px; - width: 180px; - background-color: black; - color: #fff; - text-align: center; - border-radius: 6px; - padding: 5px 0; - - /* Position the tooltip */ - position: absolute; - z-index: 2; - left: 105%; - top: -5px; -} - -.tooltip:hover .tooltiptext { - visibility: visible; -} - -.header { - color: white; - height: 50px; - font-family: Arial, Helvetica, sans-serif; - font-size: 40px; - padding-left: 5%; - padding-top: 30%; -} - -.mainContainer { - font-family: Arial, Helvetica, sans-serif; - display: flex; - flex-direction: column; - align-items: center; - color: white; -} - -.left-top { - display: flex; - flex-direction: column; - font-family: Arial, Helvetica, sans-serif; - font-size: 1.75vh; - flex-direction: column; - align-content: center; - width: 100%; - /* padding: 5% 10% 0 5%; */ - align-items: center; - justify-items: center; - justify-content: center; - /* height: 1000px; */ - max-height: 600px; - overflow-y: auto; - -} - -.servicesBtn { - background-color: rgb(255, 206, 9); - width: 13em; - word-wrap: break-word; - font-size: 13px; - text-align: center; - box-shadow: 0 4px 8px 0 rgba(0, 0, 0, 0.2), 0 6px 20px 0 rgba(0, 0, 0, 0.19); - border: none; - height: 50px; - transition: 0.2s; - margin: 0 8px 0 8px; -} - -.servicesBtn:hover { - background-color: rgb(250, 227, 136); - font-weight: 400; - box-shadow: #333; - cursor: pointer; -} - -.overviewTitle { - margin-top: 40px; -} - -.leftTopContainer { - display: flex; - flex-direction: column; - border-right: 2px solid rgb(163, 163, 163); - align-items: center; - justify-items: center; - align-content: center; - height: 1000px; -} - -.left-bottom { - max-height: 100px; - display: flex; - align-content: center; - grid-auto-columns: 100%; - align-items: center; - align-content: center; -} - -.left { - height: 1000px; - /* display: flex; */ -} - -#serviceDashLogo { - width: 200px; - margin-bottom: 0px; - margin-top: 75px; -} - -h3 { - color: white; - margin-left: 25px; - font-family: Arial, Helvetica, sans-serif; -} - -.servicesDashboardContainer { - display: grid; - grid-template-columns: 30% 70%; -} - -form { - display: grid; - justify-items: center; - align-items: center; -} - -.userInput { - width: 70%; - border: 2px solid rgb(255, 206, 9); - border-radius: 45px; - box-shadow: 0 4px 8px 0 rgba(0, 0, 0, 0.2), 0 6px 20px 0 rgba(0, 0, 0, 0.19); - text-align: center; - height: 50%; - font-size: 2vh; - padding-left: 0.25%; - margin: 15px auto 30px auto; -} - -::placeholder { - font-size: 1.5vh; - color: rgb(201, 201, 201); -} - -.submitBtn { - margin-top: 6%; - margin-bottom: 10%; - width: 50%; - height: 40px; - color: white; - font-size: 2vh; - border: 1px solid rgb(255, 206, 9); - background-color: #333; - transition: 0.25s; -} - -#databsaseList { - z-index: -1; -} - -.microserviceBtn { - width: 80%; - height: 50px; - background-color: rgb(249, 221, 111); - padding: 2%; - margin: 10px 0 10px 0; - font-size: 1.5vh; - border: none; - text-align: center; - color: #333; - transition: 0.1s; - box-shadow: 0 4px 8px 0 rgba(0, 0, 0, 0.2), 0 6px 20px 0 rgba(0, 0, 0, 0.19); -} - -.overviewSubmitBtn { - background-color: #333; - margin: 8% auto 0 auto; - height: 40px; - border: 1px solid white; - font-size: 1.55vh; - color: white; - transition: 0.25s; -} - -.overviewSubmitBtn:hover { - border: 1px solid rgb(255, 206, 9); - color: rgb(255, 206, 9); - cursor: pointer; -} - -.servicesList { - align-items: center; - justify-content: center; -} - -.microserviceBtn:hover { - /* font-weight: 900; */ - cursor: pointer; - border: 2px solid rgb(243, 239, 179); - background-color: rgb(255, 206, 9); -} - -.submitBtn:hover { - font-weight: 700; - cursor: pointer; - border-radius: 10px; - color: #333; - background-color: rgb(255, 206, 9); -} - -.servicesList { - margin-top: 10%; -} - -.signUpHeader { - text-align: center; - margin-top: 0%; - font-weight: 300; - font-size: 5.5vh; - margin-bottom: 8vh; -} - -.dashboardHeader { - color: white; - margin-top: 10%; - text-align: center; -} - -select { - display: block; - width: 30%; - font-size: 1.75vh; - font-weight: 400; - /* background-color: rgb(196, 196, 196); */ - border: 2px solid rgb(255, 206, 9); - margin-top: 1%; - margin-bottom: 2.5%; -} - -.backButton { - height: 25px; - background-color: #edd9a3; - transition: 0.2s; - margin-left: 0px; - margin-top: 5%; -} - -.backButton:hover { - cursor: pointer; - background-color: #fbcd4e; -} - -/* healthInfo visualizer css from 2.0 Team */ -#serviceDetailsContainer { - margin-top: 5%; - display: flex; - flex-direction: column; - align-items: center; - justify-content: center; -} - -/* #healthGrid { - display: grid; - grid-template-columns: 75px 75px 75px; - width: auto; - justify-items: center; - align-items: center; -} */ - -#healthGrid { - display: grid; - grid-template-columns: 1fr 1fr 1fr 1fr 1fr 1fr 1fr; - justify-items: center; - text-align: center; -} - -.healthName { - text-align: center; - margin-bottom: 1px; - } - -.healthChartContainer { - margin: 10px 10px 10px 10px; -} - -.healthChartContainer:hover { - opacity: 0.7; - } - - #routeAndTrafficDisplay { - display: inline-flex; - } - - -#microserviceHealthInfo { - text-align: center; - margin-top: 25px; - } - -#microserviceHealthTitle { - margin-left: 0px; - } - -#modalWindow { - position:fixed; - padding:0; - margin:0; - top:0; - left:0; - width: 100%; - height: 100%; - background:rgba(0,0,0,.5); - z-index: 1; -} - -#modalContent { - position: fixed; - top: 50%; - left: 50%; - margin-left: -500px; - margin-top: -380px; - z-index: 2; - width: 1000px; - height: 760px; - background: #333; - opacity: 1 !important; - align-content: center; -} - -#chartTitle { - text-align: center; - margin-bottom: 50px; - margin-left: 0px; - } - -#modalCloseButton { - position: absolute; - right: 0px; - top: 0px; - background-color: Transparent; - border: none; - color: white; - font-size: 28px; - padding: 0px; - margin-right: 10px; -} - - -.RouteCircle { - font-family: Arial, Helvetica, sans-serif; - color: white; - width: 4em; - height: 4em; - text-align: center; - border-radius: 50%; - display: block; - margin-left: auto; - margin-right: auto; - margin-top: 30px; - background: rgb(244, 222, 62); - background: linear-gradient(315deg, rgb(237, 225, 62) 57%, rgb(253, 229, 45) 100%); - box-shadow: 0 4px 8px 0 rgba(0, 0, 0, 0.2), 0 6px 20px 0 rgba(0, 0, 0, 0.19); -} - -#routeText { - color: rgb(97, 97, 97); - width: 11.3em; - margin-left: 6.8em; - padding-top: 1.8em; -} - -#routeText:hover { - color: white; - text-shadow: 0 4px 8px 0 rgba(0, 0, 0, 0.2), 0 6px 20px 0 rgba(0, 0, 0, 0.19); -} - -form { - width: 100%; - font-size: 2vh; - /* margin-top: -4%; */ -} -.deleteMicroservice{ - padding: 10px; - margin: 10px; -} \ No newline at end of file diff --git a/app/index.html b/app/index.html index a7f766a18..952d99e7f 100644 --- a/app/index.html +++ b/app/index.html @@ -5,6 +5,7 @@ Chronos +
diff --git a/app/index.js b/app/index.js index 5e000cca5..19c774ccf 100644 --- a/app/index.js +++ b/app/index.js @@ -1,10 +1,12 @@ import React, { useContext, useState } from 'react'; import ReactDOM from 'react-dom'; import SetupContext from './context/SetupContext.js'; -import './index.css'; import AddService from './components/AddService.jsx'; -import ServiceDashboard from './components/ServicesDashboard.jsx'; import Splash from './components/Splash.jsx'; +import DashboardContainer from './containers/DashboardContainer.jsx'; +import './stylesheets/index.css'; + + const App = () => { const chronosSetup = useContext(SetupContext); @@ -12,7 +14,7 @@ const App = () => { const [splash, toggleSplash] = useState(chronosSetup.splash); if (splash) return ; - return chronosSetup.setupRequired ? : ; + return chronosSetup.setupRequired ? : ; }; ReactDOM.render(, document.getElementById('app')); diff --git a/app/stylesheets/AddService.css b/app/stylesheets/AddService.css new file mode 100644 index 000000000..3a2d512c1 --- /dev/null +++ b/app/stylesheets/AddService.css @@ -0,0 +1,92 @@ +@import url('https://fonts.googleapis.com/css2?family=Josefin+Sans&family=Nunito:wght@200&family=Quicksand:wght@300&display=swap'); + +#addServiceLogo { + margin-bottom: 0px; +} + +form { + display: grid; + justify-items: center; + align-items: center; + width: 100%; + font-size: 2vh; +} + +.mainContainer { + font-family: 'Nunito', sans-serif; + display: grid; + justify-content: center; + align-items: center; + align-self: center; +} + +#addServiceLogo { + display: grid; + justify-items: center; + justify-content: center; +} + +.inputForm { + color: black; +} + +.dbType { + color: black; +} + +.userInput { + width: 70%; + border: 2px solid rgb(0, 0, 0); + border-radius: 45px; + box-shadow: 0 4px 8px 0 rgba(0, 0, 0, 0.2), 0 6px 20px 0 rgba(0, 0, 0, 0.19); + text-align: center; + height: 50%; + font-size: 2vh; + padding-left: 0.25%; + margin: 15px auto 30px auto; + font-family: 'Nunito', sans-serif; +} + +::placeholder { + font-size: 1.5vh; + color: rgb(201, 201, 201); +} + +.submitBtn { + margin-top: 6%; + margin-bottom: 10%; + width: 50%; + height: 40px; + color: white; + font-size: 2vh; + border: 1px solid rgb(0, 0, 0); + background-color: black; + transition: 0.25s; + border-radius: 10px +} + +.submitBtn:hover { + font-size: 2vh; + cursor: pointer; + border-radius: 10px; + color: white; + background-color: #494FEE; +} + +.signUpHeader { + text-align: center; + margin-top: 0%; + font-weight: 300; + font-size: 5.5vh; + margin-bottom: 8vh; +} + +select { + display: block; + width: 30%; + font-size: 1.75vh; + font-weight: 400; + border: 2px solid black; + margin-top: 1%; + margin-bottom: 2.5%; +} diff --git a/app/stylesheets/DeleteServices.css b/app/stylesheets/DeleteServices.css new file mode 100644 index 000000000..687662ff0 --- /dev/null +++ b/app/stylesheets/DeleteServices.css @@ -0,0 +1,46 @@ +.deleteMicroservice { + padding: 10px; + margin: 10px; +} + +.overviewTitle { + font-weight: 300; + font-size: 5.5vh; +} + +.deleteMainContainer { + display: grid; + justify-content: center; + align-items: center; + align-self: center; + font-family: 'Nunito', sans-serif; + width: 100%; +} + +.servicesList { + display: flex; + justify-content: center; + align-self: flex-start; + width: 100%; +} + +.deleteMicroservice { + margin-top: 6%; + margin-bottom: 10%; + width: 50%; + height: 40px; + color: white; + font-size: 2vh; + border: 1px solid rgb(0, 0, 0); + background-color: black; + transition: 0.25s; + border-radius: 10px +} + +.deleteMicroservice:hover { + font-size: 2vh; + cursor: pointer; + border-radius: 10px; + color: white; + background-color: #494FEE; +} diff --git a/app/stylesheets/Microservices.css b/app/stylesheets/Microservices.css new file mode 100644 index 000000000..3a507cbb2 --- /dev/null +++ b/app/stylesheets/Microservices.css @@ -0,0 +1,19 @@ +.microserviceBtn { + font-family: 'Nunito', sans-serif; + width: 80%; + height: 50px; + background-color: #24262F; + padding: 2%; + margin: 10px 0 10px 0; + font-size: 2.5vh; + border: none; + text-align: center; + color: white; + transition: 0.1s; + box-shadow: 0 4px 8px 0 rgba(0, 0, 0, 0.2), 0 6px 20px 0 rgba(0, 0, 0, 0.19); +} + +.microserviceBtn:hover { + cursor: pointer; + background-color: #494FEE; +} \ No newline at end of file diff --git a/app/stylesheets/Services.css b/app/stylesheets/Services.css new file mode 100644 index 000000000..034e10eaa --- /dev/null +++ b/app/stylesheets/Services.css @@ -0,0 +1,21 @@ +.servicesBtn { + font-family: 'Nunito', sans-serif; + background-color: #24262F; + width: 13em; + color: white; + word-wrap: break-word; + font-size: 15px; + text-align: center; + box-shadow: 0 4px 8px 0 rgba(0, 0, 0, 0.2), 0 6px 20px 0 rgba(0, 0, 0, 0.19); + border: none; + height: 50px; + transition: 0.2s; + margin: 0 8px 0 8px; +} + +.servicesBtn:hover { + background-color: #494FEE; + font-weight: 400; + box-shadow: #333; + cursor: pointer; +} \ No newline at end of file diff --git a/app/stylesheets/SidebarHeader.css b/app/stylesheets/SidebarHeader.css new file mode 100644 index 000000000..74bf4f52b --- /dev/null +++ b/app/stylesheets/SidebarHeader.css @@ -0,0 +1,21 @@ +.dashboardHeader { + color: white; + margin-top: 10%; + text-align: center; + font-family: 'Nunito', sans-serif; +} + +.left-top { + display: flex; + flex-direction: column; + font-family: 'Nunito', sans-serif; + font-size: 1.75vh; + flex-direction: column; + align-content: center; + width: 100%; + align-items: center; + justify-items: center; + justify-content: center; + max-height: 600px; + overflow-y: auto; +} \ No newline at end of file diff --git a/app/stylesheets/dashboard.css b/app/stylesheets/dashboard.css new file mode 100644 index 000000000..cfde4820a --- /dev/null +++ b/app/stylesheets/dashboard.css @@ -0,0 +1,9 @@ +.MainDashboardContainer { + display: grid; + grid-template-columns: 15% 85%; + position: relative; + background-color: #F7F8FC; +} + + + diff --git a/app/stylesheets/graphs.css b/app/stylesheets/graphs.css new file mode 100644 index 000000000..140c93f00 --- /dev/null +++ b/app/stylesheets/graphs.css @@ -0,0 +1,36 @@ +.graphsGrid { + display: grid; + grid-template-columns: auto auto auto; + grid-gap: 30px; /* align-items: center; */ + margin: auto; + padding-bottom: 25px; + border-radius: 2px; + box-shadow: 2px 2px 2px rgba(216, 210, 247, .5); +} + + +svg { + border-radius: 2px; + box-shadow: 2px 2px 2px rgba(216, 210, 247, .5); +} + +#docker-stats-chart { + position: relative; + display: flex; + flex-direction: column; + background-color: white; + border-radius: 3px; + box-shadow: 3px 3px 3px rgba(216, 210, 247, .5); + height: 380px; + width: 380px; + padding: 10px 10px 10px 10px; +} + +#docker-stats-chart-header { + align-self: center; + font-size: 1.5em; + padding-bottom: 25px; + height: 400; + width: 400; +} + diff --git a/app/stylesheets/index.css b/app/stylesheets/index.css new file mode 100644 index 000000000..cfc731cbb --- /dev/null +++ b/app/stylesheets/index.css @@ -0,0 +1,23 @@ +@import url('https://fonts.googleapis.com/css?family=Baloo+Bhaijaan&display=swap'); + +body { + background-color: #333; + background: rgb(25,0,36); +} + +html { + /* prevent the gradient from repeating past the visible section*/ + /* min-height: 100% */ + background-color: #24262f; + /* max-width: 1920; + max-height: 1080; */ +} + +body { + /* background-color: #333; Original background*/ + background-color: rgb(248, 249, 253); +} + +img { + margin-bottom: -75px; +} \ No newline at end of file diff --git a/app/stylesheets/monitoring.css b/app/stylesheets/monitoring.css new file mode 100644 index 000000000..c8aed0d4a --- /dev/null +++ b/app/stylesheets/monitoring.css @@ -0,0 +1,18 @@ +#serviceDetailsContainer { + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + box-shadow: black; + border-radius: 5px; + background-color: #F9FAFE; + font-family: 'Nunito', sans-serif; + font-size: 22 +} + +#microserviceHealthTitle{ + color: black; + font-size: 38px; + margin-top: 20px; + font-family: 'Quicksand', sans-serif; +} \ No newline at end of file diff --git a/app/stylesheets/overviewSubmit.css b/app/stylesheets/overviewSubmit.css new file mode 100644 index 000000000..108a12798 --- /dev/null +++ b/app/stylesheets/overviewSubmit.css @@ -0,0 +1,18 @@ +.overviewSubmitBtn { + font-family: 'Nunito', sans-serif; + background-color: #24262F; + margin: 8% auto 0 auto; + height: 40px; + width: 150px; + border: none; + font-size: 1.50vh; + color: whitesmoke; + transition: 0.25s; + box-shadow: 0 4px 8px 0 rgba(0, 0, 0, 0.2), 0 6px 20px 0 rgba(0, 0, 0, 0.19); +} + +.overviewSubmitBtn:hover { + background-color: #494FEE; + color: azure; + cursor: pointer; +} diff --git a/app/stylesheets/sidebar.css b/app/stylesheets/sidebar.css new file mode 100644 index 000000000..f7cd17ebc --- /dev/null +++ b/app/stylesheets/sidebar.css @@ -0,0 +1,26 @@ +@import url('https://fonts.googleapis.com/css2?family=Josefin+Sans&family=Nunito:wght@200&family=Quicksand:wght@300&display=swap'); + +.left { + height: 100%; + color:white; + font-family: 'Nunito', sans-serif; + background-color: #24262F; +} + +.leftTopContainer { + display: flex; + flex-direction: column; + align-items: center; + justify-items: center; + align-content: center; + height: 1000px; +} + +.left-bottom { + display: flex; + flex-direction: column; + align-content: center; + grid-auto-columns: 100%; + align-items: center; + align-content: center; +} \ No newline at end of file diff --git a/app/stylesheets/splash.css b/app/stylesheets/splash.css new file mode 100644 index 000000000..7fad28428 --- /dev/null +++ b/app/stylesheets/splash.css @@ -0,0 +1,46 @@ +#splash { + display: flex; + flex-direction: column; + align-content: center; + align-items: center; + justify-content: center; + justify-items: center; + position: absolute; + top: 0; + left: 0; + width: 100%; + height: 100%; + opacity: 1; + animation-name: fadeInOpacity; + animation-iteration-count: 1; + animation-timing-function: ease-out; + animation-duration: 3s; +} + +@keyframes fadeInOpacity { + 0% { + opacity: 1; + } + 66% { + opacity: 0.66; + } + 100% { + opacity: 0; + } +} + +#splashLogo { + height: 300px; + margin-bottom: 0px; + margin-top: -100px; +} + +#splashText { + color: #fce356; + font-size: 9vw; + letter-spacing: 100px; + margin-right: -100px; + font-family: Baloo Bhaijaan; + font-weight: lighter; +} + diff --git a/dist/index_bundle.js b/dist/index_bundle.js index 076cb103c..3b7511cf3 100644 --- a/dist/index_bundle.js +++ b/dist/index_bundle.js @@ -86,14 +86,51 @@ /************************************************************************/ /******/ ({ -/***/ "./app/assets/routeChart.png": -/*!***********************************!*\ - !*** ./app/assets/routeChart.png ***! - \***********************************/ -/*! no static exports found */ -/***/ (function(module, exports) { +/***/ "./app/AComp/Extras.jsx": +/*!******************************!*\ + !*** ./app/AComp/Extras.jsx ***! + \******************************/ +/*! exports provided: default */ +/***/ (function(module, __webpack_exports__, __webpack_require__) { + +"use strict"; +eval("__webpack_require__.r(__webpack_exports__);\n/* harmony import */ var react__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! react */ \"./node_modules/react/index.js\");\n/* harmony import */ var react__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(react__WEBPACK_IMPORTED_MODULE_0__);\n\n\nvar Extras = function Extras(props) {\n var AddClick = props.AddClick,\n DeleteClick = props.DeleteClick,\n RefreshClick = props.RefreshClick;\n return /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_0___default.a.createElement(react__WEBPACK_IMPORTED_MODULE_0___default.a.Fragment, null, /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_0___default.a.createElement(\"div\", {\n className: \"left-bottom\"\n }, /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_0___default.a.createElement(\"button\", {\n className: \"overviewSubmitBtn\",\n type: \"submit\",\n key: \"BackToStart\",\n onClick: AddClick\n }, \"Add Database\"), /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_0___default.a.createElement(\"button\", {\n className: \"overviewSubmitBtn\",\n type: \"submit\",\n key: \"goToDeletePage\",\n onClick: DeleteClick\n }, \"Delete Database\")), /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_0___default.a.createElement(\"div\", {\n className: \"left-bottom\"\n }, /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_0___default.a.createElement(\"button\", {\n className: \"overviewSubmitBtn\",\n type: \"submit\",\n onClick: RefreshClick\n }, \"Refresh overview\")));\n};\n\n/* harmony default export */ __webpack_exports__[\"default\"] = (Extras);\n\n//# sourceURL=webpack:///./app/AComp/Extras.jsx?"); + +/***/ }), + +/***/ "./app/AComp/Microservices.jsx": +/*!*************************************!*\ + !*** ./app/AComp/Microservices.jsx ***! + \*************************************/ +/*! exports provided: default */ +/***/ (function(module, __webpack_exports__, __webpack_require__) { + +"use strict"; +eval("__webpack_require__.r(__webpack_exports__);\n/* harmony import */ var react__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! react */ \"./node_modules/react/index.js\");\n/* harmony import */ var react__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(react__WEBPACK_IMPORTED_MODULE_0__);\n/* harmony import */ var _context_OverviewContext__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ../context/OverviewContext */ \"./app/context/OverviewContext.js\");\n/* harmony import */ var _context_DetailsContext__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! ../context/DetailsContext */ \"./app/context/DetailsContext.js\");\n/* harmony import */ var _components_ServiceDetails_jsx__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(/*! ../components/ServiceDetails.jsx */ \"./app/components/ServiceDetails.jsx\");\nfunction _slicedToArray(arr, i) { return _arrayWithHoles(arr) || _iterableToArrayLimit(arr, i) || _unsupportedIterableToArray(arr, i) || _nonIterableRest(); }\n\nfunction _nonIterableRest() { throw new TypeError(\"Invalid attempt to destructure non-iterable instance.\\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.\"); }\n\nfunction _unsupportedIterableToArray(o, minLen) { if (!o) return; if (typeof o === \"string\") return _arrayLikeToArray(o, minLen); var n = Object.prototype.toString.call(o).slice(8, -1); if (n === \"Object\" && o.constructor) n = o.constructor.name; if (n === \"Map\" || n === \"Set\") return Array.from(o); if (n === \"Arguments\" || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(n)) return _arrayLikeToArray(o, minLen); }\n\nfunction _arrayLikeToArray(arr, len) { if (len == null || len > arr.length) len = arr.length; for (var i = 0, arr2 = new Array(len); i < len; i++) { arr2[i] = arr[i]; } return arr2; }\n\nfunction _iterableToArrayLimit(arr, i) { if (typeof Symbol === \"undefined\" || !(Symbol.iterator in Object(arr))) return; var _arr = []; var _n = true; var _d = false; var _e = undefined; try { for (var _i = arr[Symbol.iterator](), _s; !(_n = (_s = _i.next()).done); _n = true) { _arr.push(_s.value); if (i && _arr.length === i) break; } } catch (err) { _d = true; _e = err; } finally { try { if (!_n && _i[\"return\"] != null) _i[\"return\"](); } finally { if (_d) throw _e; } } return _arr; }\n\nfunction _arrayWithHoles(arr) { if (Array.isArray(arr)) return arr; }\n\n\n\n\n\n\nvar _window$require = window.require('electron'),\n ipcRenderer = _window$require.ipcRenderer;\n\nvar Microservices = function Microservices(props) {\n var index = props.index,\n setDetails = props.setDetails; // Overview state used to create service buttons\n\n var _useState = Object(react__WEBPACK_IMPORTED_MODULE_0__[\"useState\"])([]),\n _useState2 = _slicedToArray(_useState, 2),\n overviewState = _useState2[0],\n setOverviewState = _useState2[1]; // Contexts have data added to them following successful IPC return. Data is later used to create charts.\n\n\n var serviceComponents = Object(react__WEBPACK_IMPORTED_MODULE_0__[\"useContext\"])(_context_OverviewContext__WEBPACK_IMPORTED_MODULE_1__[\"default\"]);\n Object(react__WEBPACK_IMPORTED_MODULE_0__[\"useEffect\"])(function () {\n // IPC communication used to initiate query for information on microservices.\n ipcRenderer.send('overviewRequest', index); // IPC listener responsible for retrieving infomation from asynchronous main process message.\n\n ipcRenderer.on('overviewResponse', function (event, data) {\n // Adds to state and context.\n // console.log(JSON.parse(data));\n setOverviewState(Object.values(JSON.parse(data)));\n serviceComponents.overviewData = JSON.parse(data);\n });\n }, []); // Holds the buttons generated for unique services.\n\n var componentButtons = []; // Tracks which services already have button created.\n\n var serviceCache = {}; // Contexts have data added to them following successful IPC return. Data is later used to create charts.\n\n var healthdata = Object(react__WEBPACK_IMPORTED_MODULE_0__[\"useContext\"])(_context_DetailsContext__WEBPACK_IMPORTED_MODULE_2__[\"default\"]);\n\n var _loop = function _loop(i) {\n var element = overviewState[i]; // If SQL\n\n if (element.currentmicroservice) {\n if (!(element.currentmicroservice in serviceCache)) {\n var button = /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_0___default.a.createElement(\"button\", {\n className: \"servicesBtn\",\n currentMicroservice: element.currentmicroservice,\n type: \"button\",\n key: \"serviceItem\".concat(index).concat(i),\n onClick: function onClick() {\n // IPC communication used to initiate query for information on microservice health information.\n ipcRenderer.send('detailsRequest', index); // IPC listener responsible for retrieving infomation from asynchronous main process message.\n\n ipcRenderer.on('detailsResponse', function (event, data) {\n // Adds returned data to context\n healthdata.detailData = Object.values(JSON.parse(data)); // Updates state. Triggers rerender.\n\n setDetails( /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_0___default.a.createElement(_components_ServiceDetails_jsx__WEBPACK_IMPORTED_MODULE_3__[\"default\"], {\n service: element.currentmicroservice\n }));\n });\n }\n }, element.currentmicroservice);\n componentButtons.push(button);\n serviceCache[element.currentmicroservice] = true;\n }\n } else if (element.currentMicroservice) {\n // If Mongo\n if (element.currentMicroservice) {\n if (!(element.currentMicroservice in serviceCache)) {\n var _button = /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_0___default.a.createElement(\"button\", {\n className: \"servicesBtn\",\n type: \"button\",\n key: \"serviceItem\".concat(index).concat(i),\n onClick: function onClick() {\n ipcRenderer.send('detailsRequest', index); // IPC listener responsible for retrieving infomation from asynchronous main process message.\n\n ipcRenderer.on('detailsResponse', function (event, data) {\n // Adds returned data to context.\n healthdata.detailData = Object.values(JSON.parse(data)); // Updates state. Triggers rerender.\n\n setDetails( /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_0___default.a.createElement(_components_ServiceDetails_jsx__WEBPACK_IMPORTED_MODULE_3__[\"default\"], {\n service: element.currentMicroservice\n }));\n });\n }\n }, element.currentMicroservice);\n\n componentButtons.push(_button);\n serviceCache[element.currentMicroservice] = true;\n }\n }\n }\n };\n\n for (var i = 0; i < overviewState.length; i += 1) {\n _loop(i);\n } // If there's no data, return 'No data present', else return microservices button\n\n\n if (componentButtons.length === 0) {\n return /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_0___default.a.createElement(\"p\", null, \"Loading\");\n }\n\n return componentButtons;\n};\n\n/* harmony default export */ __webpack_exports__[\"default\"] = (Microservices);\n\n//# sourceURL=webpack:///./app/AComp/Microservices.jsx?"); + +/***/ }), + +/***/ "./app/AComp/ServicesList.jsx": +/*!************************************!*\ + !*** ./app/AComp/ServicesList.jsx ***! + \************************************/ +/*! exports provided: default */ +/***/ (function(module, __webpack_exports__, __webpack_require__) { + +"use strict"; +eval("__webpack_require__.r(__webpack_exports__);\n/* harmony import */ var react__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! react */ \"./node_modules/react/index.js\");\n/* harmony import */ var react__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(react__WEBPACK_IMPORTED_MODULE_0__);\n\n\nvar ServicesList = function ServicesList(props) {\n var context = props.context,\n Click = props.Click,\n isclicked = props.isclicked;\n var buttonStore = [];\n\n for (var i = 0; i < context.length; i += 1) {\n buttonStore.push( /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_0___default.a.createElement(\"button\", {\n className: \"microserviceBtn\",\n id: i,\n type: \"button\",\n key: \"\".concat(i).concat(context[i]),\n onClick: Click,\n isclicked: isclicked\n }, context[i]));\n }\n\n return buttonStore;\n};\n\n/* harmony default export */ __webpack_exports__[\"default\"] = (ServicesList);\n\n//# sourceURL=webpack:///./app/AComp/ServicesList.jsx?"); + +/***/ }), + +/***/ "./app/AComp/SidebarHeader.jsx": +/*!*************************************!*\ + !*** ./app/AComp/SidebarHeader.jsx ***! + \*************************************/ +/*! exports provided: default */ +/***/ (function(module, __webpack_exports__, __webpack_require__) { -eval("module.exports = \"data:image/png;base64,bW9kdWxlLmV4cG9ydHMgPSAiZGF0YTppbWFnZS9wbmc7YmFzZTY0LGlWQk9SdzBLR2dvQUFBQU5TVWhFVWdBQUFTd0FBQUVzQ0FJQUFBRDJIeGtpQUFBQUNYQklXWE1BQUM0akFBQXVJd0Y0cFQ5MkFBQUhmR2xVV0hSWVRVdzZZMjl0TG1Ga2IySmxMbmh0Y0FBQUFBQUFQRDk0Y0dGamEyVjBJR0psWjJsdVBTTHZ1NzhpSUdsa1BTSlhOVTB3VFhCRFpXaHBTSHB5WlZONlRsUmplbXRqT1dRaVB6NGdQSGc2ZUcxd2JXVjBZU0I0Yld4dWN6cDRQU0poWkc5aVpUcHVjenB0WlhSaEx5SWdlRHA0YlhCMGF6MGlRV1J2WW1VZ1dFMVFJRU52Y21VZ05TNDJMV014TkRVZ056a3VNVFl6TkRrNUxDQXlNREU0THpBNEx6RXpMVEUyT2pRd09qSXlJQ0FnSUNBZ0lDQWlQaUE4Y21SbU9sSkVSaUI0Yld4dWN6cHlaR1k5SW1oMGRIQTZMeTkzZDNjdWR6TXViM0puTHpFNU9Ua3ZNREl2TWpJdGNtUm1MWE41Ym5SaGVDMXVjeU1pUGlBOGNtUm1Pa1JsYzJOeWFYQjBhVzl1SUhKa1pqcGhZbTkxZEQwaUlpQjRiV3h1Y3pwNGJYQTlJbWgwZEhBNkx5OXVjeTVoWkc5aVpTNWpiMjB2ZUdGd0x6RXVNQzhpSUhodGJHNXpPbVJqUFNKb2RIUndPaTh2Y0hWeWJDNXZjbWN2WkdNdlpXeGxiV1Z1ZEhNdk1TNHhMeUlnZUcxc2JuTTZlRzF3VFUwOUltaDBkSEE2THk5dWN5NWhaRzlpWlM1amIyMHZlR0Z3THpFdU1DOXRiUzhpSUhodGJHNXpPbk4wUlhaMFBTSm9kSFJ3T2k4dmJuTXVZV1J2WW1VdVkyOXRMM2hoY0M4eExqQXZjMVI1Y0dVdlVtVnpiM1Z5WTJWRmRtVnVkQ01pSUhodGJHNXpPbk4wVW1WbVBTSm9kSFJ3T2k4dmJuTXVZV1J2WW1VdVkyOXRMM2hoY0M4eExqQXZjMVI1Y0dVdlVtVnpiM1Z5WTJWU1pXWWpJaUI0Yld4dWN6cHdhRzkwYjNOb2IzQTlJbWgwZEhBNkx5OXVjeTVoWkc5aVpTNWpiMjB2Y0dodmRHOXphRzl3THpFdU1DOGlJSGh0Y0RwRGNtVmhkRzl5Vkc5dmJEMGlRV1J2WW1VZ1VHaHZkRzl6YUc5d0lFTkRJREl3TVRrZ0tFMWhZMmx1ZEc5emFDa2lJSGh0Y0RwRGNtVmhkR1ZFWVhSbFBTSXlNREl3TFRBeExUQXhWREl4T2pFNE9qTTRMVEExT2pBd0lpQjRiWEE2VFdWMFlXUmhkR0ZFWVhSbFBTSXlNREl3TFRBeExUQXhWREl4T2pJME9qUXlMVEExT2pBd0lpQjRiWEE2VFc5a2FXWjVSR0YwWlQwaU1qQXlNQzB3TVMwd01WUXlNVG95TkRvME1pMHdOVG93TUNJZ1pHTTZabTl5YldGMFBTSnBiV0ZuWlM5d2JtY2lJSGh0Y0UxTk9rbHVjM1JoYm1ObFNVUTlJbmh0Y0M1cGFXUTZaV1UxT1RGbVpHTXROV0UzWlMwME1ESTFMV0l3TXpndE1URTRaRGd3WXpBMk16aGhJaUI0YlhCTlRUcEViMk4xYldWdWRFbEVQU0o0YlhBdVpHbGtPbVZsTlRreFptUmpMVFZoTjJVdE5EQXlOUzFpTURNNExURXhPR1E0TUdNd05qTTRZU0lnZUcxd1RVMDZUM0pwWjJsdVlXeEViMk4xYldWdWRFbEVQU0o0YlhBdVpHbGtPak00TkdNM1pqa3pMV1l4T0RndE5EQTRaaTFpTVRNd0xXVTJaREExWkRFeVl6RXpOeUlnY0dodmRHOXphRzl3T2tOdmJHOXlUVzlrWlQwaU15SWdjR2h2ZEc5emFHOXdPa2xEUTFCeWIyWnBiR1U5SW5OU1IwSWdTVVZETmpFNU5qWXRNaTR4SWo0Z1BIaHRjRTFOT2tocGMzUnZjbmsrSUR4eVpHWTZVMlZ4UGlBOGNtUm1PbXhwSUhOMFJYWjBPbUZqZEdsdmJqMGlZM0psWVhSbFpDSWdjM1JGZG5RNmFXNXpkR0Z1WTJWSlJEMGllRzF3TG1scFpEb3pPRFJqTjJZNU15MW1NVGc0TFRRd09HWXRZakV6TUMxbE5tUXdOV1F4TW1NeE16Y2lJSE4wUlhaME9uZG9aVzQ5SWpJd01qQXRNREV0TURGVU1qRTZNVGc2TXpndE1EVTZNREFpSUhOMFJYWjBPbk52Wm5SM1lYSmxRV2RsYm5ROUlrRmtiMkpsSUZCb2IzUnZjMmh2Y0NCRFF5QXlNREU1SUNoTllXTnBiblJ2YzJncElpOCtJRHh5WkdZNmJHa2djM1JGZG5RNllXTjBhVzl1UFNKa1pYSnBkbVZrSWlCemRFVjJkRHB3WVhKaGJXVjBaWEp6UFNKamIyNTJaWEowWldRZ1puSnZiU0JoY0hCc2FXTmhkR2x2Ymk5MmJtUXVZV1J2WW1VdWNHaHZkRzl6YUc5d0lIUnZJR2x0WVdkbEwzQnVaeUl2UGlBOGNtUm1PbXhwSUhOMFJYWjBPbUZqZEdsdmJqMGljMkYyWldRaUlITjBSWFowT21sdWMzUmhibU5sU1VROUluaHRjQzVwYVdRNlpXVTFPVEZtWkdNdE5XRTNaUzAwTURJMUxXSXdNemd0TVRFNFpEZ3dZekEyTXpoaElpQnpkRVYyZERwM2FHVnVQU0l5TURJd0xUQXhMVEF4VkRJeE9qSTBPalF5TFRBMU9qQXdJaUJ6ZEVWMmREcHpiMlowZDJGeVpVRm5aVzUwUFNKQlpHOWlaU0JRYUc5MGIzTm9iM0FnUTBNZ01qQXhPU0FvVFdGamFXNTBiM05vS1NJZ2MzUkZkblE2WTJoaGJtZGxaRDBpTHlJdlBpQThMM0prWmpwVFpYRStJRHd2ZUcxd1RVMDZTR2x6ZEc5eWVUNGdQSGh0Y0UxTk9rUmxjbWwyWldSR2NtOXRJSE4wVW1WbU9tbHVjM1JoYm1ObFNVUTlJbmh0Y0M1cGFXUTZNemcwWXpkbU9UTXRaakU0T0MwME1EaG1MV0l4TXpBdFpUWmtNRFZrTVRKak1UTTNJaUJ6ZEZKbFpqcGtiMk4xYldWdWRFbEVQU0o0YlhBdVpHbGtPak00TkdNM1pqa3pMV1l4T0RndE5EQTRaaTFpTVRNd0xXVTJaREExWkRFeVl6RXpOeUlnYzNSU1pXWTZiM0pwWjJsdVlXeEViMk4xYldWdWRFbEVQU0o0YlhBdVpHbGtPak00TkdNM1pqa3pMV1l4T0RndE5EQTRaaTFpTVRNd0xXVTJaREExWkRFeVl6RXpOeUl2UGlBOEwzSmtaanBFWlhOamNtbHdkR2x2Ymo0Z1BDOXlaR1k2VWtSR1BpQThMM2c2ZUcxd2JXVjBZVDRnUEQ5NGNHRmphMlYwSUdWdVpEMGljaUkvUHBuak1ZQUFBVmVmU1VSQlZIaWM1UDEzMUcxYmRoY0cvdVpjYTRlVHZuRHp1KysrV085VnBGUkJVaW1BUWduUkNFbGdzRVNRR3JydGJuSndnOEMwUXdOdUMyaUQ3V0c3aldFTXdDWlloQUVJRkN3SkMrVlNLRldwY3IzSzlhcGV2WFRqbDA3YVlhMDUrNCsxdzlyN25Qc2s0ZUhSUFVidmV2WGRjL2JaZTRXNVpwNXp6VVZuOTE0R1NLRUVBQVFvK2l0OEpRQ0FhdmVkQ0xyN1dQY1o3ZGY0OCtqSjdxZFJqNk5uWHZzbjZrYTFmeVFFQmFoL0pQb3dtTUxlSGlrYVh2eTNlVGtlMVQ3bzdjQ2tlN3NmYnd5S0VUUjZ5RGVOOXdQdXVodEJCa05vUk8vdXVmWXM5SEJlZzVHMHpZNjZIcS9tY0MzR0hlbERBVEVDeUhoU2V3ZThNN1hkUlJ4Zzc4NW9FWTE1aEtqdGk4M1U0MDc2VldpZTM3blR2Ymt6dHRIZmZneFdWVUNrdW0rcU93dXZiYzhQSjBMOGFzc2ZIcEMyelJFR2g1L2lObVBReUdnaE94UVozbTkvVlFKVXdrOE5na2swOThGSGFrZlYzcE8yY2FVQjRCUWRFS0t4YWZQVGZycHQzdTJ4Y2ZlQklRQko0NlVWZ0hRODVSamEwV2k3bTdxTFFhL0o5YlFiUnd1S01DK0pNSlEwc0xVZUpoRUVDQUdOaHRRbFhiL2FqbDlKdXk4RFpHaldxSnY1THBQdEx4M2ZHQzFIdkdyZDM0NkhEaHFJVm5QWTR3Q0ZlNHlLeUl3aTdKT09pZWtZaDNkaGpoaWRHQ0RvUUpobzk0UnFoMTRVdjZyeGt4M1VLR3EzbzZ1dTdlN1h0b0Y5RTQxUVhLTTE3b2RML1dNRDdrWDlZK01KVXpzWGpkRkQ0K2M3WGpnWXhzN3dkQWVVTkpoMXM3WXgya1JyME0xc3p3UDliKzN0QVF1UGhqZGk1K05samppUmdyb1hDYlRMZ01memFoNGRQdGwxcmQyZk1jTU4vMm1yY2RBUVlvTkgyeWNIWC90Zmg2eEZCeXNldGRIM1F2M29PK0xxbjlQaHJCc2dVN1NPZzRFME4yajR2VU50NmdpMUhWdEgyQjNUNzVGMkYxTGR5UHViN1VlT3VHQzhrREVXeGhnUW8zNUVtVFJBamgwQ2lybW14aS8yVU9qSk5Tdzh4VDlGc3hqZmo3Nk9wenlnQityNEg4VWdiNEV4NU9EOW1PT1dlejdTZ21tSWp2b3dwT25hMmFXQkdBdHA1NEVkVGp5RTJPaW43bXU4bWdodzZPWGNBQUpSUiszTi9zbTR0V1o0WVRXcGI2b2JRTndtN1p0djNDeTE0Tk9kNldqYjE0QTRXdlJWUkJKMTJERkZMZXVnc1dpbzRjbUlzRHI2aWVmY0xIMGpuQU1NaGxyRmlJbjJVQms4RUhId01ZTUlzMi9HemtNeXdNNktVb1RLSTlHRUhnSkRQa2tnb3RqQUdQSE81dDVRRngvaFJ6Y0JHcXpjVUdnajduNEk3a0ZQZ2YxRm9ydDlOMXF3OGV2TnFMb0Y2MytsbUFEandjZVFHY0ZSby85b09PQ09zR00rRlRjU09tM0IwdkE3YlgraVlUdnhJR0t1T2hwTXg5cjNEYmFmY3Z4MWdQclJPN3J6RjZQeERObi9nTy9RWU9MeGxDUCtSWU9saVpZMUh2YVlvb2ZJUEtLUXJzRjRJWVlJRXg3cnpiRlJPOVQvRXluUjBZQ0dVTzg5S2hpd1BBQUF4eXlyNVNnVVQ0N2lIK01KTmg5MGRMK2hDdTFXZW84YzZOWXZwcWdXNmgyR0VXS2FHNk9TNm5CS0xiUFlaZk1Zcmw4TWpxYVhqcHFqaHdkUVJ0ejRnUHppd2NXekdaTlQyd3BGQStnK0VBWk1wMnMvZ21HcjdFWHNPbVpoSTRKQkE4aitUa2ZxM2E4OUJJYThhY1FaWXd0S1kyU05CaHJMSHgxT3NNZXF2cE1oWktodkpKb2NJcHh1aHpybXl6U1NQaFRQbXZvbkJ6ZzBvdmxvNHRpNTJYK204ZjBJZnpTNE5ocUdFWSswQSt4d3RtTmV6OUczdnZVaDNHSjhiUmRzTVAweGErL2gwQzNpQVBsaXpSRDlLa1M0U0lPaGQyaWc4YzBScnNYc2Jjd1lSamd3QUhpSDNERTIwNkNSbm8zdHJsL01wQ01nS08zWW1UMUVIb0lFSFk1UjlIdzhrTjNlb3lrT254c3VTai9VY2R2TnA4RHZScnhwOE80STlhSXVxT1ZUbzlXTTZYZTBJREdwRC9nakRiMG1PeWc0bk5USUdUd2kyVUdQc1NzOHB2YmhnSGZhaXU3RWZETjZyeVUzNmltd254ZTE2a2JmUjRSVkFJSjV4ZDJiSTRZMVpGbTc4K3BnUVhzbm9CMTVERzJNdGpVYUxubjNJOFgvUmRBYWtmZnVOUmhEekxkbytHdEVYa01jcGJhWGRtQ1JrYllYb2VQbmg4T2cwV050WTkyczQ1bU5NVzBnbytJSDl1SFdhSGlEenFMSG9pWG9tWkJpWlBESC90RkJoLzB6TkhpaWUxNEhMdW9PYmc5ZExZM2hPV1FaSTF5Syt4MDlNeHlrdHVNS2xsRC9QSTJXWk5qZ29DK04vc1ZBUVFqZmhzOXI5SGZjR2xyT3BoSHBkYWc0V0hFaTRpRkVCbUtqdzhSb0tBR1hhQWdDR2tFQ0xicTFYWGF3MkdVdzhjMVlxblNYRHZsbDdCWG9lY3lBVG1qd2N2Ulo5OTF1N21nVEF4aklqZDBHb3hiMjNJMDBvaGloS2U1ODJQZFlUQXp3ZUNpMXFHc0t0QU82ZUtBYXcrY2hVNDd3Z0dMSTBHRGlFWm0xc0I3d293RWZvVkh2bzZzYk16VHV2U2NQNmw0ZTJxdXZmWTBJWUtoc0RraXRZNy83ZUVQMzVDQldGMGtMamFIVVhRK2ZLU0Z5QSsrcVJSRlZLVGhNdUJkUVBVZU1vUlA4Z2EzVGVDZk0zVUtDMEhsODRvRjJDa3VNS0dPdElhYUJIVUNPNzQrbXBQM2c5OCsxR1pnKzVGMXRlUnFBZ2FBTWlESGd6ckhZQ1dBYkthNXh5NUVRYTJmUW1mc2RkOTlEVDIzTHc2WmlyOVEreXQwRG5MMjZ3eEMyT29CU3hEZDNYaDF6RE5vQjVjTldaelJZaldpNDY2NTVzNmVFZmxKQm9PeHR2SHNtWHYyWUE0NjY3cVlaczVqZE9VV1FEMWtNUk1UTlh4bzhNMkJWL1gvYWNxNmRFVFplaitZU1ViRVJZc2JvUlQySW11OFVoTVhEZVNvTmY0cWFHa1JjZFFjS2ZTWUF0ZXU4MDVGMmp3Mi9kb3doZW5JUWV1eHdQeGpRM1VnSENCZE5Yd2R2UnQ5MGZLZkhDUWFCcU1YM1B2TkVxWWY2UUtvcDBLNXRtRUdJZExlcEN4eGdSczFrZW5BcGd0ZTVUU0dJb0JhWnhCU3lMenBNb2E3SDRGcnRFMkJhQ214U2l4QjNHUytVdG84TnZyYlBkc3RBUXdLTGwyR3c2c0dQRVpwcDVCTURTc1NxR3J2VncrQ2JzVWV2ZHRTdi9ic1VEYWhIckI2VFZJbFlWTFJYd2lPTVZZMWJKQUJFbEtXd0RCQ2NVK2RaVkF4NUViV2NKS20xckY2a2NyNnVtNmsxUzROMmtKSDhiUFZTSWhDWWpESEV4bG8yQ1J0amQ4QzF3d25hK3hGaGpPZ3R2bWdFOEgwTUtVS3B2c3R4Y3RZdXVVYUI0MTdlZHZKdFNLSm91MmdKckdXMTJtZ0o0NGtNRVRObUFpUCtPZ0JSYUg4ckYxNXJJaUl5QUVFYW11OFJxa01jYlRQbXFDR3B5TXRMUGIxMkZOUU12NlBhRmo2aE5kSG1ZWTBDMDJNOUpTSTFHVTBXM1dEYUNYWGc3TmxBaS94b1pCTDExTk4wRXJKaXBPMm81NG9kUDRxNm9DaHRxWHM0OXFYSHFEZENnWjV2TjkrMVpTeVFEbm1pZ1hWRVRBb2xRMGxDcWFFVWdLb2dBbmVFUDBTR2thZllWdnFSei90UGZWRS8va1Y5NVo1ZWJNbUxUM2hUVlZ0cjFwZG45ZE0zRHI3aU4xeCs4NU9YSG4vVVY2VXI2bVpzR2tZVElBVml3NGFaTEJ0anJDVTJ4cVpNaGpoaXk2ZDNYOXhac3hqaGRwYXovVHJBMmdHTTQ0WHU3K3lUYjNIN2lHQ3hkd3h4aXdNbUc0MGtlbmEwSkFNQ2JpbXR4OG1kaWZTdng4OUVveVVEVllVblVLSHJwYnU3OFdlRkxxRkNJSUxaTVNIMmNxN0JUSWZvQStyVENYL1ZkdlpjMFhQaldRd1djcnk0LzlaWFRDSjd4ajN1OXRmUjRNTitldTEyeGxqRVlNdDV6b3ZjekNkMGxIQnFLRzBlVXRXUWR6Ykw5ZjZGLzVrUHlmZjloSHppZWF3THFKTGxZTGlSU25DamVPOHY2dXBMV3QyN1BKdDkrOWUrNC8veXU2Kzg2V2xYRmVxRXlEQWJrMWcyaVdIRE5pSG1JY2FHSGtWRTFkZmV1MTBpeEJnZFkzVnhnTmtkS1haTFNWMURyZFlRSVhMSDB6c2gwZkJQMGtHWHNYYTZ5d1FqS2FRTmc5VXg3OS9oOU4xTVJzdERyUXdaUGhJSjFUMHNSc2tDVEhKT3hFSXp3QkE4cXlyOFdzN1g3bVRsNzFleUFZR0o5K2RheCtwRkp3UTZ5ZE11VXdmYThidTdQQ3ErUStnRlk4dmMyeWFIWGZTZlJ5dndHcmk5aitIdWpHY1ArM2hvbXhyVStSWlR1cXhMN0lIR3VNRlk5dTUyRkN1bUFVc0lrR0NTRVl5bEpET0xuT1lwVDNPYXA5bE1zMVIrNEJmcXYvYVA1T1c3cEV4NUF1YXUrM2dVQkdLQ1VaU3UrdGpKdlU5TzhjaTNmOTIzL3JXL01IL2tXb3ZidzBtcXFIam5uSXIzcnZiZWlmY1FyMUJWcGRPN0w3V3ZkTUs3b2JZT21oMUkrdGdjRVVVcEF0SjNScXFRSnVsVVZVbFVGYVFnbFVZRDhFb0FSQUNDaUFwQVJDS2tLczNpRGFSWFk4WVFFVE1JeWlCaUpLd0VNQ05oTlF6VE9ZVkFxcXF4R0lxeWhYdktoTklvVXdFOWU5RW1kRU9DSnVWTFd2MVFBZXZ1cEp2M21mcXpwT3pTbXo1OXRzN2ZvbndJRllKblJZMXk0OC9PM2UxQ2xyV1c3ZWpRMDN6VDBpNFJka05Cd3lPNmtjVUpTTEdnSG9yL01hYnZZTU5BUkkwMDVJSHNqTXh6R3JVOGZHelVjanlGaUdHT1o0cmhIb1dlRys2b0R6M2dSOE1iZE5DWS9EU2VUdXVxMW5hbUlHS0dZUmhEaGltMWxDUTBtYzZ2NXZmaC9zTy9wVC8vTVNWQ21tQk05UHNwbllIRW1OdnJpMSs0OS9MNmpZOS81ei80Yng1OTE5dWc0cjMzcmhieDRtcnZ2VWl0QWxVL21Gc0xLenEvOTVKQ0JRU0JWeFVsQmNURFE3MlFnTVNyVXhJbEwzQUtyeEN2b3VSRVZkbUpLa2lFdkVKRUZlUWJZaU92R213UVVZSzJMQWdJdHJOb1ExeTloRkVsb2xoTmI4Mmc4Rk16ZVJDQ0c0UUpobUZaVTRQVVlKNWhtc3JFSWpYSWJFdVpCQ1pWZ0ltMFlUc2tBaStvQmFXanNrYnBxZkNvYS9JS0x3Q0JDWloxWW1tUnl5VEJQSlhNa2tKRlNOUlBMbjRvM2Y0eXdLQU1xRUZRdWxKUDNscG16M3I3SkNnaGVBWUFYMG01ZEhjdi9OMVNOZ3BQeEFTT0VMcVQ3alJBeC83M3lDK3pYd0wyNjdralNJY1VEb3pwUDhieW5zaDNxQUlqVXVrR2hzSE5jU1BVRWg3Nk9XSkVJVEVjNHNsMnoyc3JGM1lvYzZERk5CcVNxcUsxZWdrUWtDRmpZQTJzNFNUaGFVS1pKV3RwYWltMVNKZ01nUWtNVnJsL1VmNmgvMEkvL0RrNm5NY0pEVHU5anRTLzVvZUVDS0FmZWZYekwwem91LzdGMzN6ME43NnR1RmczWXFyelF3M2s0MERob24vOUs3ZTlxaGQ0SlJVNFZWVlcxWTVtQWtWcEMrSmVxalQrd1A2clJ1N0hYZ1Z0NlNpYVNBUjFRa3RzclhZeG5ubzBkKzFXckhtdTRYSkVLajFrakNFRFRhd3l5Qm9ZVWhBTXdTdFVJT0RhcTFjNER5ZU5UcXNBcVJKSFRzbEdDcXNoVEZNOW51cjFtUjVQZEphS3F1Zmk1YVI4dnkwL3pWb3FFaEJVU3lEejZlTjErcXpMdjh6YnkxQ3dlaUo0OVpXc3p0M3R0VHlvcElDQzJYUXNaaUFXeHFJdll2OWpTZElETTdvNUJPQUFmWWM3ZTJLUzdxa0kzWjNXZDRTKzkvYkhpRTRpTkVRMGl6M01aWS9DdWpPMm9YaU1RVEhBaDlaKzYyOHdnWWh0Z3NRZ3Nad2xsQ1djTTlLVU00T1V5VEl4UUF4dS9aVWlHdHhaU2phaGhJdHYvNC8wVTE5Q25vWHVoc0JvUjlGaGJRUk9SSThSMGY5eTc0dGY5S3ZmL2pmKzh6ZDkxN2ZWbSsxUUwrMkFRUDJONEUzOW4zLzJUdmlOZW9JWndLdFBKRzhwYmMvcWg1OGFvVC9vdExmd0VEMFdTYnZ1bFVFTEVkbFNLeWQzdWVlb3I3Ny9QZ2V2NGFoZFY5VDhUN3Z3UU1zeUdqTm1wUGNwZ2pvS0JlV0pQbm9nVDE3Q3djU0tnT292NWV2M212cHo3RStKTWdWQkhNaDdQdlRwNjZ2cGwzdjdpTkNNVWJPQ0NMV1dLL2RnNDA5V2N1YWxKQ0tDNmZsNjAyTkVZSHZFRVhWVzlaamtkZ0VSTDg5d3FRYlV1RXZlSTYwNHZvT0l6RHFoM1d1TWNVZWoxUnJLcmdGcTYrNDRHejJwSWJhbU5TYkRzQXhqT2JXVVcwcFR5aXpuaGxLTDFKSWxNSkhwQ0VnRGoyMnNFNlZBaXN4a0xETWJZem5KeUtRWGYrUjdxKy8vS1ZyTU92RDFVNCtjaERHakdFRWkzTEpFbGNyZmZmblRrdkdmL09TUDU1Y1BmVm1ydWc3ZmQ2UnBZQ2hLLytROWQxcDZESmpZbXJ1dE1xalFzQTJUeGhoQ0E5bUVQU2pSRTIyOEhQR1RJL3BzVnlMbTdJMk1qZWcvOElWeERrTk0xVzBYWFdBc0hrbUllYllDdXQyTU91SlRBMUhSeUNsVmRVS3BrV3NMZk5ralBqV0pWMFArZnI3NTVhVDRJTWtGd3lnWlFLRVZrSWk5VnVUdjhKUGZJSHlzWU5ZNndMT1M5Ym03dS9Hbmhhd0Vuc0ZFM0U4a3h0UVJpZ2Q5Z1VaUFloOWx4blN5cjZrZXl0SEQ4Wk5qSlJCREJJeEY0c2lpRzZxNEE1d2RQNnpORDlxSjNtQkRNQmxMQ2NPbW5GdktMV1VKcFpZemc5UWdhVVBtemV3VUFLUjNCU2lhQUFDellVdk14aGcyaVRHV21JMU4rakVwenYvanY3SDlXLzhFUjBmUVhsZ003SUNHODQwenFyQkRoQVFZb2xmcjdRKzgrdnpoVzU3K2QvNzVYODN5UlQ0L2FoNE00Mnd3alltWURUTmJZeXo5NDUrN1BXcElSNGdZaWFraE0rZ2w4OWliTXFLMzBSM3MrMG83ZnpGb2JXaGZVRStCbzM0SFFPcnZqeVJ3VE5pRG56QitQU2pNTWNFclVIczZtdWhYUGU0eUN5S2pTTmpmVDhyUHB0dGZNdTRPQVVvSkFNQkJuSmhqbjl5cXBsL2hrcWVWSm9EanhnWGxOM0oyNGU2djVMNlRDbENHaWRBVXdKZ2VCbDdIa1ViWHk1Tk8yMmsvL3lyeVp5aFI5emJiUTdzUHUwVWdSdFR5c0pGMjJjSUxBZ1ZCUllKdm5FQk16R1F0OG9UU2hGTkxlY0s1MGRSeVlpZ2xHQ1pHeXlqYk5LdmdMNU8ySzROQWJvYUpEYk1sYXkwYllpYTJ6TFN6cmdDZzRrVzh1M2R4OW8xL1VJb3E3RWp1QVRHa0t5YTQyb3NYRmdWQkRjT2FXRjJLdGFmTThNK2YzMy8veFoxdi9SLy8wbFB2L3BvMG54SVJpSm1ZVFlnV0pteXRZY3RzR3ZUcWlSQVBJWnRvUmVKYUZCMktSR2d4bW1nZzE2SCtpVEVON3lHVmVBeDdKV2Vza2UvK05DcWVNQnhQQnpYRTdvV3V6U0VyNzhjZk53S0FVQXV1elBDTlQ5V1ZKd0JLQnJBc2ExTy9tRzErM3RUUGt6cWxsRUNBcU5hQUVYdWpuSHlGVDUvMDlpWUlwSTRCZ0dyZGJ2M0ZtYnV6OVdjZWpva2JmVGxHY2UxV2U2K0VqSWh0OUFFWVUxRjN2MjljeDEwTVdIS2ttUXhrYVFUYy9uN3ptNFphSk0xSU9QaElHRW5DbWFYTWNwYlN4RkJxS1RGa0RSSVFVWnZjMExsWVFrZ2RqY0xDVElDeEJETEdFaHMyaG8xbE5rUnNyTmxMYlBFbDR1dHFXNWVGdUZxOHgySlMvdzgvNFArTDc2UHBKT1pkblFMQkJBTit0VmkvY0hhNlBralRHMGVMcTRjM0pEbTZ1elYzem5XV1kvQlNjekZvcWZYZmZmbHpiLzV0WC85ZFAvaTNYVm5hSkdGam1SbXh2dE8rNTUybmYveWUyeTBZZzFnWVFIanZ6YjdQSGQybkUxYnhUY1NaWkQxZWphWFoySlFZeWNQUnpVaWd0VFRkVWw5RXQ0TmV1c1ppQVJqM0UwdVI4QzIwR1kyY0FHMGxWZW53Kzk1V0Z5N21FNnlVRW1wYmZTRlovMEpTdjBpeUJLVm9kTlFhOEVxek9udW15ci9TSjdmVXpLRTFRVmhaSVlVc04vNzgzTit0WkNVUUJoRzRHM1JMQmczbmkyN3VFMFE5dUlaNndwaFJSdlRXVENKcU1HNHpQTlRvbVIzbjdUUkExVWFzTWNHbW5Cc1l3M21DTE9ITVltSTVJVmpMSmtoN0FpczY5MStndHNHT1NtWkRUTVRNeGpMYmtPYkZocG1UM1NoY08xSlZFUkV2VW92M0lxTGlreXkzTm1OajIyZkV1NnF1U2xkWGt0bk5yZDlGMDd4VlhzZktVS0h5eWRYNTg4WEZyZS82emJkK3o5ZE5ybC9PRmdkcE9zMjNLbi94NzhsUGZSRFRTZS9JaWVUaDFKaS9mL3Z6THhaMy91VFAvL2dUdi9ITG94R0tpdFIxNmV2Sys3b3V0OTQ1NytxWUNLTlYyNGZyZTVUQVNGbEZaMWdPbVFyUUQyNVhMWXpYdDVlY1E5L1BIcTExd0pyM0NjL2RsaC8ySVc0NUd2T0E2WGZPb2Fpanl1UEtETi8wdXJwMGNVL3R6NVFvMUxpNzJmcVhiZmtoMWpVb0JheENDY0ZpTkdJdVY1TzMxOWxieE41UUl0S2FvQXoyNGpaeWNTNnZGbTVaNlRyazN3ejB5V2hZZTV5S2V3ekZ2ZUt1QThVUW9DT3RvS0UzMVphWEJqWmd5REtZS1UwcE01UWtsRnZPRThvTkpaWVNicFE0UTcxdzlPMEg2ZmJBRXBNQ2hnMnhvZUFwTVpZTkUxazJocGtmSXR4VVZjVjdVUy9laWZNcTNvc1Q4ZkErakxNVCsyRmpFM09TcEJPYlpzRXNESzFVSC9qVS9hLzUvZWFSNjVGTHBMOFkrSW1UTzNmVzkzN3pQL3l2bi95T2Q4UDU0R0VoSWxqRGJNcy85TmZxbjN3ZnNteUl2MUFnSWZwaXNma25kei94MUR1KzRrOTg4RWMycTlPNktyMnJ2U3U4OCtJZDBQb0tpWWxnTy94cnRZaDJPTjJnd2pKUUs4OWlVdXlFUkNDYmxtSjdmRzJlYW8wY2l1N3ZVa3R6V3dla05YcW1CYTdFN3FVdzJENWVQMWFteHUzc2RCb0x6MzRjZllwME04ZUFxS0p3Z3FzemZlZWpybkt4RUk5SVhDdUF4RnpkSFAxT1U3L0xscC9PaWw5bWQwSXdTcFlvQjhEK1FiNzY4WHo5ODFYK0JwZSszbVZ2Rko2TGVtSmEwUEhjWG5KSnVYUW5GLzUyNFZlaWRmREZSOHJHV01LSFVZNVdidWVKN3JHWU83WU9TVWlqQ2lvQnlqQ2tiQ2t4WkF4bENVMFNUaTJsRm5uQ0djTXlHNFpoY0JoVDdJb1U4WUFIdEVtd1pnYklNTEZKaWEyeGh0bXdzVVRFeHRJZVBRMEFWRlFoNHAxNHB5bytaSng0RjJRZXRFOUNwYkMvZ1JOaUVFaFVWTHg0NTF5ZHBMbVNsTVdxMkY0UXNVMnlKTXZTYkZhOTk2TjBlQkRMam82L0o4eS9jbkY2ZDMzNnpqLzNSMS8vQjc2OVBsOFRKU1pQMkZockxiRWg0c24vK3orNisrUzNVSm9HTUdvRXpscjAwVHhmMktNWFAvUytWNy8wS2ZWZVZJSWZLYmlVT2g0ZndueTJ3N21ScTJ3c2k1ckZiU2l3dDd4aTlOV1d6TVpMcnoxaWRLKzBmOGVVcUMxS1VHK01CU1lxU3FvYXRrQmFic09NQlBIUUppVkFGV0JxZE1hd1Y1SmpaVGlTZ2ExdnJlY3kzZjJ1OXlqM1NrSHdRbDcxTU5kYmgvTE1GU0VpYVF5ZnpuWFd6NDJnZ0NOeFlxK1h5YVAxNU11VDhxUEoraGVNUHdVQVNrQUpZSUU2M1g0bzNYN1kyMnRWL2s2WFBDUHBUY2RNNmhqSnNYM2t5RjZ2WkxQMDk5Wnl1dlVYQW1VaVVtN1dySVBPWUhvN2pMMVRIcW56MHNYZWYySmlBMnNwTVd3dDVTbFBETktFTXN1cFJVcGtXdUhXTEVwSXhBZzdjUVMrYnlyNExka1lNbXlZeUJock80dUlUWnM4dEhPSmVCVUptU2FxSXQ2SmVQR2k2bFY5TjhIR284TnNqQ0ZLR3AzV2kzaFhsNlVydHE0cXltSlZiemQxVmRUbHhudG4wM3c2UDVwZnVqRTd1bVFvTGYyNkt0YmJvN3I2eEtjcFRTTE03ZkZrNDkxblZ1ZFBmZk0zL0thLyt1ZEpPVHVjakllclNsY08wei8rWGZYLzlLOHduVFNtYlBjamdSVXpObHZReFpkdUh6NStqWVFiN0NBd0dNUU5KYW1JY3kwUjdvWjI5cXB3N2RlZUFvZWN0MTNUb2RJWXh6TXdlR1hQZ3ZTaVJVUCtEVUNwMVVtS2c5UXZjczBOOGdTSmFRcElLQkF5RGVvYWhhZE5oVkt3TGFsd1ZJbDZ6eDVRMFpCa0UrdzdwbFpsb1ZHM0RWMzJzcndWZlNKa1NPZVpQbm5zSHp1U3pNTDVJSTBITGZUNllUdy9yVW1kOEtTYS9LWXFlNXV0djVDdmY5SFVYMUFWNWx4aGxKZ0E0eDVNVi85YWtManMyWHJ5dGpwOWc5TEV3NU1pNWVrVjg5UmxmV3dqeTZXN3MvUVBIQ29DU0RrZVBoSGEzV1lLb2k3SXBpMlhaNEJnRENWTXhsS2EwcFFweVNnemxGbktES1dHRElkUVc3dE1vWkYyNTF0SGJHaDRIYk1sUTZZUmE2M2xab2pNUXl5MzBKQ0hlT2Q5Y0ZHS2R5cGV2WmNtNFZCYXJadEFaSmpCU2JpanF1cWRkNVZ6bFN1S3V0NVdtM1ZWcmwxVlZNWFd1OHJYVmFQd3N6WEdKR2srblIyYU5EY21FVmRKN1V5YU5EZ3VIbmZQdTlUUXdUb1M3bGRsYnBLbmY5ZHY0Y1NHZmtXY2VPL3FvcTRxN3lwWGw5N1grRjN2eEQvL2NYakJrRDRJRUZCcTJDSXBIaXd2dmU0eDhSNGk0cjN6ZFYxdTYyTHJxbTIxM1pUbDJwVmJPOWJFUmlUM0VDSVpQYk5IaWtZcWJtdFg5R2daalhmb0hWRW9JRUlDR01Ja3dlV1pYSm5wcGFubUZwYUZpWVA2MElncERlb3ZnWlRRYU1LaVRWWmQ3VkE0MnRTOHJsSFcyTlJjMUZvTDFaNUVFY2k3NHlVVXhjYzY2bVJXeTNTUTZ1V3AzRmo0b3h5SlZTK29aY0NUT3I2eHgrUGZ0YThlOE9DOHp0L3FzamNseFNkcytmRzAvQ1MwQkJLUUFSbUZKVWhTZmpJcFArSHREWisrcnB5ODNkdkhsQWhhRTNSbWptYm0rTEpzdDNKeFVkOWQ2NG1JQ3pTalVMU3BpUVFpWmFiRWttV3lsck1NRThOcFFybWwxSEJxWUF5c0VsTWZhVkJwUktxcXVOaHgzYWg2eGpDMzlOWjVTcGlKelQ0c0FZSndDOWFiY3lyT1IxUVhNQnV0dTczeHV4TVRrVFp1RWdMVXUzcGJyRnk1ZFZWUlYwVmRiT3R5VTVkYjc1MnZLMVVoQXB1RTJaZzBueTZPazJ5YVpKTnNNclBCQWt4U1l4SmlabWJBZ0pRSXhxYkdKSnltWjh0dHR3bTBRNytBVElVb0cyT09KOHZ6dTY0c0E5a0gxYmg5VEFuQXBUa2RUZkZnRlJOUUo2Y01pR0F1N3IyYXZwUnRWeGUrTHFwaUk4NDVYd2ZiRldoY1FvMU5HT3QrRVU2TjZTcTIvZUlQWSs5RkZHellxMjFHM3hwU0NodXdERk5tOUdBbWh4TmNuZm1EVEhNTEluaUJncnlRYjFSRUpXWmo4eVROMlpwNnU2bXFiYVRuRUlDRWtXUTB5L1FLU1JpVXFIaUZFMVNlYXFlbHA5SlI1YlVXZGw2OVVzZ3BaMVlMc2thbkZyTk1Kd21taVZoRFVQVkN0ZC9Wb0dOellNQmdSbE1IQVBXa29rQTFlV3VWdmJXc3ZwaHUzNWRXSDRNV1FJckdXa2dBTXU2dTliZVQ3ZnZyOUEwK2U2YkszaUs4QUx5cXM1d2QwdlVEYzNVajY2MmVMdDA5TDVXaHpGS1NtRHhCYmltMWxDV1VNU1hjNWVVMGRxQUdnOCtya0lyRXV4YUN3UUxMbHRoWUppWnJqVW1ZbWNnUTA4TXROMUVWTDE2OUV3bWFwQlB2Zy95RU5KNVBiZW80a1RiWnZ3QTZiYVpUckZ1Q2JKWGVZbjF4Y2ZmbHplcFVYRVZzYkpJWm02YXpnelNicE5rMHlXZEpQclUyTlVscXJBV0ltUUVRRzZLZ3RTYk0xaGdtVHRoRTR3L0owUHR3VWdBTHFNcjY3TUhxNUk2aUl4RnFJRVJCZ1lkc0hVcUh5R00yWEc4bDhQMlhQK3NPMStxYkRNM3dQcXlsenFzUHNvaDFzOGhnRzdnNk8rMlJvdjNzUXpwc211bjYxOGEvMG1ob3ZaS0padVdsMlgyY0dKcFlPY2h4a09sQmpzUGM1d2tNSUFvQm5IUWFucW9vaUl5eHlXU2FaVk0yU1FNMTUxQnUrOWcwQWRwa2pROHBRWmtvTlpwYlJRWUtZaml3Lzk3MmJGOGhWV21jUGtwYys5MVNCVHU4cjhmbW5zY1BscmROcnlVQXZ1QWtTMlp2MWVNdjJ5dy9iWXFQcDVzUHNKd0JERW9BVmJJQUVTUXRQb2J5dzZsNWowK2ZyQ2J2OU1ualFsYlVNWFJpNWpNY0hKbWJxcDdKY3RqejFsYnlWSlVtUHhLOUF4Y0NEYWphNkkzRUhGeVJUY3d0L0lhSFhPSzlxb1ROQWRMRUE0TFhCSTBtMmNPbnBiTVFrK2FRUU8vUnBpRkVxUjlEeUVlWFRiTXJONSsrL01pVDVYWlZyVmNtU1pNMG95U3p4b0M0NlFBRWdLMDFiTURHR3NzMkJBOXBBUHgyN2NSNzUycHZJZE9zeVlnZWMxQy9TQkxuL2NYSHYvamtkN3k3M2xhR2d6Rk1VTytjRTNHdTJKWlMwbnMrZFhTMmdXV05DTEVqbWxwRW9ja3NaMk9WWk1pM0cwVWp6TUIyOTN2U2lsR3JaVnphSnErRlQxRnowVXlIcVFQU29EZUpBbTE0UEdHYUpqcXhtS2F5eUdXUllwcG9ubWphT3VGRnliZkdSODlkUkdGTU5wbWtrNm14U2NPU1ZaMnJ5dTJxcnJZUnRCc0JITkZBTDVmQzNIMWYyd0s5YzZNWk9nV1BoVTN6Sk04QmNuWHB5azFiRWF2clI0ZC9lNld1WTFyUmlyUUE3U3cyMWNuOEtKOGVoQjhxKzViVitjMXE4cTUwODc2a2VzN1V0MEdHWUJ0bGtETUFSazdONW01YWZOalp4NnZKTzF6MmxKZ3JVQkYxQkdLeUNnaGtFSWZvdVYvSEM1SGtlVDQ1SUdPWTZDRm8yb3l3b1RmdnZmY3FsWWlJaURieXphUGhlYzNFV3pIQm9DQXdnNkpIWGtTY2M2NnN5eTJEODhVUlc0dlExS0J5UjFpQ1p0UVVqY083R3FBMG4yZVRBNUFTdVBIN3NHRTJ4aWJHMk5kbUhBcXR0cHRpdS9TdUV1ZThxN3g0WFV6TTlUbEV5ZlRiV1p0VlU3NWtPV1h6L0QvNnNjZC8zMi9PYmg1dUx5N0tZaU5WV1c2WGRWMzZ1aFRuNmxueWh2Lyt4eUlCTmVpVWlkYmlCWFYrOVRBd3dpak5jeEEzMGtZZGJjYzZ5TStPWG1zcysraFFra1pVS29scXM4ZWlYZWFnYVJocWRoaWwxbWVHWnFsTVUrU0pUaFBORGF4dE90WjJsNldUUnBYdHFUZzBTcFFrV1pKTmJEb3hwakUveE5kMXNTM0tqVGpYWWtCOFVieVEvVnFNLzZMVmdzSjhRcDlpYkRLWkh5VnAxa2dzbDEyVTZ5SHQ5YjNFakdLNERob05aUENXcWt6bVIvbDAwYzdGYlpmM0dFNW9YaXgrYXlIZmtCWWZ6WXIzbWVwRkFrQUpHZzVod0FhQXFaK2YxczhyTDF6MmhtTHlsV0lmVWM0Z0RwMjQwM2gyQXdRamFKYlBiWkwybzFSUkZmR2lJaUxPdXpyRWsxc2lWSTAweVZieWdNZ0dKMlVndlFaMDRyeXJ2ZmQxVmRURnNpNkx1aXpxWWxYWGxTdld0Zk5FbWs0T0RpODlzcmg4UFo4ZGRGcFZxemkwTzBFNzdRL0VSR3dObThTd0pXdU1zV3dTWmdCN1NFNVZ4VG12enBVbEdGazJNelpzQ3lSampJcXZpcld2YXhDWURiem9XNTZtNy84Rm5VMTJsZzBBZmMzeHBaOTg5YVdmL2M0Ly84emYrdmVVUkxZMVdORnlMcG1uVjk3ejR1enV4czN6MGJzS01MRDJmdU9yMU03eXF3dTNMdnFXZS9jSnVxaVhqWDVvM1gzU0pvVm9LeThFdld3aHNBSUVCb3lCSVNSR0U0dU1mV3FSR1V4UzVFWlRpOHhvWW1CWm1YdkR0OWxoS0ZUSG80a1lLaHF0aG8xTmJacG0rY3pZQm1sVXBDcTNydDdXVmFraWFQaHR0eSs3aDhOUVBNZTV1UEUxb0I5VklhYko5RENiekx0NGJyRytLTGVyVGd5MmIrbVFBdVA3Y2N1amdRUndja3lCM3RlcjgvdmlIUkVUd2xacnJxYnZxaWR2czl2bjB1cGp0dndVcVJBbDJwcFBSQ2xBMEcyNmZYOVNmTlNacS9Ya0hYWCtGdVZMQ2lYVXFqSmlRUEhYemZJa1NTZEU1TDBMOFFWUkgrSnhQYmdhblRhd0tLWW1YYm94NTZDcTRwMTNVbGRWdFhYRnRpcTNkYm11aTAxZGJsMWRpNi9FUzlpaVRjWmFtOWgwT2ptWUpHbG0wa21XVHhXaWZSMHFiWmVlMlRUNkpMRXh4aHFiaENqaXpzSTF5Q0RpZkYyNXVuYSs5bFZSdTBxZDgrb2hvaUJyazN4Mk9GMGNKV2x1MC96b3lxUGliNVRiNVdaNVdoVmIzZGI0alcvbWk3WE9wOTFnT2gzQ1F5OGw2ZHN1WDN2dS9vUFAvK1VmdlBWSDNqMTkvSktJd0lzbkNOUEJCMjgvOHM4L1ZzMXpScTk4ZERpUnMvbUZpM3UxYkovK0hkL1l1cG4yeFd3Yk01SG9IL3pNYlFJQ1IyT0NJU0pxZHNRYWhqV3dKSWtoeTVvWXBJYXNrWlNSR0UwTkxKUGhmdnNzZGFKU2dTYUdGdmFreDRKaVYvdnZYNElxczdGWmxxU3pKTXM2YmNmVlZWMldWYmxXOGN5Sm9zMEFqbkp4SWxMc2pMd3VMcVk5U1VTUG9RM1dLSkJQWnRsa0VYZ25BRmRYbTlXWnI4dVdBbmZKK0dIVGlYNmxib21DUnM2em82dTI3Y0w3ZW5YMlFMeUw1SGpiRHJGU0N2VzJmajViLzVLdG55ZS9CcWNJM0JpdFlRNUFIY0Y3UHFpeXQwajJoaXA5R3B4REhhbnZseUptQkcyQm95NmsyU0ZRMVBzZ1UxVHFzcTRLVjFWMVZWVFZ4cFhiYXJ0eDVkYTVxcUUza2VBMXRVbG1raXhOODJReVQ3STh6V2MybXlSSmFwS1UyUFpCYWhFTktkZE4vbWRpRXNPVXNER3RGMkY4cVlqM3RYZTFxeXRYRmM3WHJxN1VlMUVIYWRhWG1WVTdhUTJGcXFoaGswMFgrZXh3TWxzMHlpYTBMcmJyaTVNcUovLzEzOE4zVDJIdFlEbGJjQ1JFWjk1OTlQN2RzMFdhZmVQckprOWN5NjdNRjQ0dWZlbml5bzkraGhUQ0ZEczdPbXhNbVA2N0Z6K1Z6Zk92L2R0L21DMnJTSVFEQTZPdnlVWDd4T2Rmc2t6TXNBUkRha3dJeW9waElpZ1RNUlRVNlcwVUJLVzBLa1JyWVNLRURydVowQ2dOYWpDN0NMZ3RhaENSVGRJa25TYVRDVk9yZG9xdnkwMVZibDFWQVdCcjZySlkzWC9sNHZTZVRXeEltVGVKWlE3cHZOYWFCTXhzRExObHd3Q3p0U0g3S1dSc0VKR0lvOVpRREdUZjZwK05YdUhGRmN1enVpN0hBclhUdTNyamNIZUh5OGdmMC95aUttenQvT0J5SjlXcmNyTlpudTZBaUxweWdLRkxjQW9vMS9leTRwZnM5cE5HVGdFTE1qcnFBS0phRUdWaUhpbW5YMTRuejZxOXJDRFNhZ2pwZ1FZWU5iQ1hTNFloY0Yyc2w2ZDN6dSsrdEZtZWlxdkpNSFBDU1pxbXFVMG0yWFJ1MDBtYVQ1TnNrcVNac1NrWjAyWmpOclVWaU5BbXBpV2h5Rjh3N0IrV0JScXVjcnNzaTYxNEY0Snk2bjJJZWFBdGNFaEVVU0xmZms2b3dUOUZuR1NUMmVJNHpSYzJhVVNyQjFZLytqUGIvL1AzMGlUYjViTGhxeVZXNkxLcXp0Y2JaWnBrNlNIWnhJdGtDVGh5alVRWVlJaytWeXgvNU9TbDEzLzMxei81Nzc3TFYzWGJZdU1VQ0M3TjFzNVRCV2g1L3lYMDNUZDJXUHRFaTZsN2hlbCtIVy92TTZPMWI4V1dLaEhJMkRTZHBQbTBWenRWNnFxcXk3V3JLL0UxZ2NPUVR1OTg0ZDZMbjYyS1ZlTXBRb08wZ2I4eUNNSC9TOHpFTUFZQUd6WnN3MFlYRUxGSmp4OTVjbkY4TlpTNk1teXkrV0dXVFZybnV4YmJkYms1RjFGanJIYzFNYmNpbHp0SUluWU9OM3hJV3g2ajJwWVBqY1V5R3pNL3VtWmF6YW9xTit2emt5RUd2b2ErUUVLR3lKSmJadVVIazgzN3JidW5ERVdLVHJLM3RZVklLMENFSm5YMnBqcC9lNTA5RFVwSlBiVGVwMGp2RWVPeHR0QW93R0hIamZqMThuUzdQRTJ6YVpKTmtpdzNKaUVPcGZ1QzY3clJQcGlZVEVKTTFpYk1JUjMwTlJKQm9lckZlUkhudlUvU3ZGTkd5dTM2L09UVnFsaUhqYmpVSkc3MlNzQStSSXRRT1N4V0x5UkVWZG1rczhYaFpINGNlSzdjUDd2L2YvaGpjdjk4RElYUVpJdTFUTVFOUVRjS1J1eUFoRGEyUDRYd0J0TVBQWGpsNWVyc1hmL3Qvelc3TkF2NDNBY3hFQnkzaVdIRG9TSmJrdERwM1pkNjRBOFdCanRyczIrdUErenBsY0tSWkJpMHJxcFFROGJta3pTYm1pVGxsZ2E4ZDFXeGNjWGFTWEQwQlR3Z0JkUzdpd2V2Qk5WU3BQTE9lMWVMZXFsckZhZWl6dFVpUGlReHFmcVFzWTRtUUtnQXBaUDg1clB2bkIxZUNYNmtkREtmekJiTURXSFU1WGE3V1VsZHNFMThYWi9jL3VMNXZSZU5UWUxXUk1ZU1cyTk13OFdOYWJaamNwUGRiMHhDUkdEYk1IZ04yWkpRbFRTZlRoYkg3UnhSRlp2MXhZTWRJYkFiMGhneUxCQ0lnWVIwbFc0L2twYlBjZmtGSWxYWU5sbTNrZFlBRkVKYWcweHRINVAwMlRyN01wZGNBNGhRRFptcFJnczRXa2VNOFNFVWoydlNJYVFwNWtYRXhHVFltTVMwc1lIWHlBVUZRbXBhWDNRczVGV3FkOXBVL09MSi9DaWJURHM0YkZabkZ5ZXZpbmROVmdiMmtVc3NpbHBac3d2S2dIb0tOY2FrK1R5Zkh1YXpnOVdmL2U4Mi8rQUhhRDZMRWtxR2t4OXRIUnZ5a2xiVmFTZ3pJZjdZOXZRbjdyMzR5TmU4NVozL3o5K25wVGMyTVVscWs1UnRZcFBNMnNTbU9kdkVOUHV3bUloRHljT1IwalVtcWgzVkpRTEQwTis2UzVPZCtkRW1LNUpOMHlUTjAyelNCL3BFWExrdHk3V3ZxOVluT3Bpc0JvV1NBaUoyRXFhRmtraWJoK2RGSEVSRDVxRjZIeEtGMU5YZXUrTWJUNWcwVisrU2JKTGxzeVNidEwzNzdlcThLallFQlp2VjZaMVhQLy9SY3JzQ2hiaTZFQ0xuTWhyT0VQSkkwT1RwYzhpTkpHT0ppRlJteDljZWVmcHQ0cXNzbjA4UExuVVQyYXdlbE50Tm05YTZpeVRoaTlKRGRBY0FTZ3pLV0N0VGZqWmJ2OGZVTHhCcVVQRGw3dWdkVW9ORWFWN25iNnJUTjdyc2RjSXowcnExR0Y5emNWdHBIcjQzSVEwMmhvMUpFc01KakRIR2NHUHM3YnRDaUVQQ1hnY25VbnZuVmJ5b3hMeWc4N2wyTE5vazJlemcyTFRvNFZ4MWR2K1Zhck5zNnUzcFBqTG9tMnRKWXZlbjZBRVJEMUE2blUyVCtmS2Ivd084K3FESkk0MEZTTnYrNE92UURST2VpTy84elpjK2JTeis0R2QrTU54bnNtQ2dpUkZUU0V4U1ZZWDBKYURhdXFNMGFHK3NIbU93UEwzQ01waVpEdFhXZGlkTmM4T1l4R1paa2syVHBObjlJU0pOT2xLNUVaRk9lclFyUTIxWG5RR2cxSVpvcWNsZkMvMUpBNTJoTW9KKzZ4K0JJT0xaMkd3eXovS0cwYXBJc1ZsV3hkcjdPa1MzQU4ydXp0WFhDdlhPZWVjMWJFN3p6cnRDUk1YWDNvVzlhazZiZEg1VjlTb1MrS3lLUDdoeTYrYXpielBHNXZsczBsS2dxbTVYWitWbVJmMWU3NUhBMmFPSVlqaWQrQ2VsbkxSazkycStmVzlTUEFjdGdBVEVRNkVhWHZUUVdoV1MzSFQ1YnlqenQ0czVCZ3loUWxBU091RVJ0citqNFMraExJUkpFbWJiS3Bhdm9WV0dYQmtSNzhSVklVbk5lNCtRTnRBeXNQYi8rOFJaMzVRU1VUNDd5cWZ6N2w2eFhwNmYzUGF1REpaSC96QmFiVEJhZSt3YlpVK1o3Vy9xaGFZNXZuaVBmc2Rmb0R6YjlYUHVJajBpT2h4MWxodno4eGYzZnVIa2hhLzRRNy8zNi83eW4zU3JyYmJNdXVYVWh0bVNDYkVXTmlabFptS21rN3N2RWlLZlNyTU5Od3J6TmtRWlI5NTJlRVFyQmp2VktOQkptQzJiSk0yblNaYkhXb3FJMU5WR2FnZUFERGNSM2hBRGFqS0QycFNMaHZBNlloeURWMFBhSTVyOFh6VG1icHRlMm54UWtLYlp0QnVEcXBhYmk3b3FJeDRzZ0JKeHoyb2I3MnJmZXNqUDlDSlFIeUxYRGVaSkxjNkpGKy9xNDBlZUlPTHAvREROZTdWcWZmR2dLdFlkcVVlUTNGVUNleFcwSDhkREZCT2xoRURHdlpKc2ZqR3BQbXY4bWNLQ2JMdVkvUW9CR3NwdGVIT2c5bFkxK2ZJNmUxWjVDblZRWjZLTjZteHN3QkxpL2VHQkFDdnZmU2lUMGl5b2Q2dXplODVWclQ4NVZKbWtJZHJFd29haUd3TzhDaE5UU0pJazJmeFMwZ1kydmFzdlRtNXZWbWVCa1QxRS91NWpZN3ZzdVdVR0trcXB4Wi83dS9SekgxZkREMnV6YTRYYVV3NDZGUlN0eGJYMDFiKzQ4OFdEMTkzNjNUL3l0eWVYRG9PZFphd2hNaUVuQ2Z0S2NRTm9pTERwb3ZFM0JVT01BV0lLL2pvQ0I1S2lKbmJVWk9nRldMUVpjVUZBQmVNQjZDS2JyKzBFKy8rZGE3REhKMUJjb3pocCs2ZFJlMXVmVlVPZmFPa1VnRUs5ci9OOG51YXowS3lJMnl4UHE3TGdnZDQxWW1Sb2Yyb0hNOXhwdnFPYmRJUWFjRGxSSnZabmR2dkJmUHZMN0U4SlZpZ1o2VTJOL0ZmUGNBcjQ1SlpMbitWTFgydW1UNUExRHpYalZBRVI3OFU1NzczNDJudnZwUTdJdURpNjBublV2SGZGK3F3cWl4YWZ1bUhUa0lrUFpyckRrcUtzWTFVaXptZUxiTExvc0tqWVhGeWMzcXVMTllma2pSMEZkYysxSTNlakJWQVFjSGVKNy9vcnFIeWZhdGsrTjdiUDJnOXhIRWVnRTdJL2NPL0ZMNVluZi9hTHYzSnc2OXByRDZjZmhvcXFVbDJWa2NNM29oKzhCcS81MzNSMUdCMVVtRzRvMnVwMXFocGNMSTN1cmhyeUZjTS9LaDVvYlRGakNCU3FqSUM0cVlsQXhHemFGQ3JESExoSFlCZ3hkL2pmNlJwb1F5S3lPcnZyWGQwNVlIZWxXVXRMaUZBek51MWFzQTJlR2VKVXc1Z3RLREZ5WXNyUDVwdjNzbnNKTUNBRGNEeXEzbUpVQi9WcVo4Z2ZwMHZmaElNdnAzYk1La0hycnIxejN0Y1FGZTk3bnpEUUJ1N0J6SlBGVVpwTnUyRnVWbWRsc1dvTGMyaE1XalFhUUZnWUppajVKblMwYTZOQ1ZXMlM1L05Ga2pSaEpCVi9jWHB2dXo3MXpnMFMxdllydVoySkMxQzNZN241alprVnhKY084SDAvNC8vMGYwTTNyc1dOeFNzYVB2WDZxZ0pvZ3JZWjJjOFd5eCs0Kzhsdi95dC82VGY5SjM5aTJIbkFiR2xMYjRpcVZ5K2lYZ1dpSHFwN2p4elpQdzBOVWRHVy9ZdjQ1bTlES2lMU2R3WWdHRXZOZlpGZzlUWGsxT2FWaXZnbW1xNGF0RUh0TnNoRzQ5Sm1seTFpVWRLTkxLWW5hUmEvRmN4QUZ5RWtNSmx3bzlQUldZTXFiSUpzNXladkF6REdBR0hUZDZEa0lPKzVGKy9OT1hXdlJjemkzZkwwbm9pTG5veElTYnRWN2VNQisxWi9xTVh0SkNqc0JKQ3NraVZza3VMVDZlWVhqWHVadEFibFhjczdRa2tobFlEazZ1K3A4aTl2RUtXSnlIVlhKeUV3eEVrUW9LTDUvSEF5TytqdWIxZG54ZWFDaUtONUVZSm1SYWFSSHVMRmUxK1hWVmt3OCt6b3FuZFY2MjFyRGNoMjNxcWVpTlBKWWpJNzZCaW9xOHF6K3krVnhTWmlxYTFpZ2haWUliT09tV0NORFNYWWpMVkpVMDJERXpZbXBMT1RUVTcrd0YvYy9xL3Y0VHlMWVROWUVMUmFLQ0lKVEhpNTNQN0UvVmNmZmZmYmY5Ky8rcnZLQ2xYbm5KTmFuWGRldks5ZDhBR0xROWhWMGk0REJhTnhkZlpBMGRCSklCaW9ldlVRRFdRYVVwQ0M4MUZWRkVKQ1VQVWFOamhJejJoVWgwTnRVS2JmdFE1dEhFVWRTclpHWkp5M01UWVI5bUw2aU9lMVg3cy8vYnZoVmlNcmVqMm4wK3Q3VE5lMklqaTY4YlFVVFkzNjNlWnpjU0JsRUhkN2ZOTEo3UGpLbzkySTZxcmNyaDVJdDEvbVY2R3gyRUNLeHo0b2tUVjZ0R3RxWkxGcjJOdE5LV2xwNnBmUzdYdVQ4Z3VFY2l3SmgyRDJmTGc4L2lPZzJOdlowNDkyU1lYak1RQ0FLckxwZkRvLzdJWlVWOXZOeGFtSWI3Z25FVlNLOWFxdU5uVzVyWXROdFYzWFplRmQ1VjBGTmxkdVBuMzUwZGNaYThYN25aWHZkbXVLU2JMcC9LaExmMVhWNWRuZHpmSU1RTGVsMkhEQ2hwa04yelN3VWRQV3JjRnJYbHE1ZTkvMUgxZnYrUlhrR2ZxQ3RHT3RLVmJ4R1NUQVA3djkrV1c5K2U3My9vdlpyV3ZsWnVORnZmY2lYcVMzWXpxRklPU2d4eXRJenovM3k5M3FoMjZwTCt2VTRhTzJmQVVqVTJXUUVvVStQQm9QdDF2Q1hkMDlScVUyeUwwSFNZZE43UkJuMU9uWXhxS1dkZTNCdWdGQUIyRFpWUTZDQmQrTG5EWlRTQUVpNyt2RjBkVkwxNS9va0tNdXQ4dnplOVM2bWlMWk5ScHhMRnVHL0JZdE9McWZZcjExWitJN0lqSDh4c0laa016UC9sNVNQTmM2Yk1hRFVZQlExOG5UbTZQZkgxaE80RTRQejdtTnVVVXpCVlhKSnZQSi9LaHpmWGxYcmM3dmkvaFdDOFg1dlpkZitld0hnNlVYeEZUZ1p3cjFycG9kWHJ2MWhuY21rNms2dCtPbDc5aUJpaUNmelNiem93NzN2SGNBelA2YWdxOXhhYU1xTmdMR2l5SDM4djM3WC9FSGlKaXpSSWZLNThCeHFnQkJnWlQ0bjk5NzZXVXF2dkd2L2JtbnYrM3Jpdk1WME9oSmJFSUlsZnJjSUZWVmNiVVhkTVhsRkNCNjRaTy9zZy9DN2U4anBJOEV6bGc2ZGVsekdqMlB2aWtNdi9ieHhmaG0vS0VaOXZpQjNhN2orM0VqRGE0MGl1NkExbnExTEJwejh6TmE3YmNyKzkxd29FNUJpN1VmSldCK2ZPM0t6U2U3Y1pYYjFXWjVzZ1BRL3NQSTJPdC8vVldDdFAzOVlRdmRyejA0bTlmTWxQeko5T0lIYmZsWm9wRWM2TnBSeU5hbFQyOFB2MHQ0SGpXMWw2cjdIM1luR1BhZ3pBK3ZSQ1VHZFhOeFVwWEJMUXhqMDdPN0w3NzBtUThobEg3U2dYOUR2VFBwNU5GbjMzWndmQzE0QnZZcFBLRlZNU2JKWjhILy9CcFhxQWNWL25yZkpheUhVaG9pMnFoK1FEZ0pTWlJtdWYvUTV6ZC8vTC9TMDFWVWJTSEM3UmJxQ3MySjM3czYvZW1MRjMvclgvOVBuLzZkM3lpYklrdlRKRW1zTmNaWUUvYUxOZHFUdHB0UXFLeXF1L2Z1ZHpvWG9BTWkzSXZsN2MxV3ArZzNaVVlQUkFRem9xV0g0ZFR1aTdGS1NjTjNScGpZRFgrWFU4ZFdxNEpzWXBOMGt1VFQ0QzhIZ1FaaHRJNFNlN2xEUFp2cnNLM2RKaXV4QkdoR1lLdzl1bkt6SDREcWRuM1dSYTBlNXJvYmF3eDlxc01JOElPdkxiU0E4U0lnZ2pSVWxkaXFFcC84bTJUelM4WTlBR2VSK0lwb1dHdmxwTXkvc3ByL1pxR2N0QjZBZWRmK0hBOS90Q3lBS2hrN083eHNiYmRoU2pmTDAzSzdEbTUzTm1hN1BuL3AweDhzMTJkc0VvVDRkUXR6RVlYNmd5dTNIbm42clRaSnBEL3VERU45blFKM3pTYXp5ZXlJSXQrTWQ5VjJ2WUpLeUpSUzljSGxFTUF5aEdkM3puYU16VXJ6cWZ2NWo2Mis4ei9Gd2F3VHJiMWEwbUpSU3ZTTHk1UDNubjNwcmYvKzcvN0c3LzNUVmlSTFUyTk55d1U3ZEF1b295SlNPMTlYMVhxejhkTHNJd25la1AxRWlKMzFIOTBKM3p2RGFuY3Q0dFlvZXFXWlNLZDU3a2pPM2RXT2ZvenVSNCtHZzhnaG9XYVl0VW1hTm9WR3Btblc1eUwrNzNudDBReit2M3k1YzczOXozRCtpMDBCbTRhWEQvWTZxMjVoTHEwUHY5T2xieURkcWtya2V1bnM5akhiYkQ5Z3hGcmoxQ2cyWmpvL1NpS1g2WFo5WG14V2pjL2RHRmR1WDNqdXZlWG1YR0c0T1ZlQ3c0bTJSS1FxMDhOTE41OStHdzIyNm5aQ3Z0TUNWRldOVGJMSlFUYnBSS0lXMi9WMmZhN2VVYk1qZnNCWW82WTArcWxyVmxWQmg3UHlILzc0NXMvL1RXcTJDN1lGSlJybENMbmg1ellYUDNqL3MyOTg5emQ4eXovOEt3ZWNHTk1kS0NLaWpRdlNpNGlYMnRWVjdaejM0bDJJQmdUS1owUFcyQ3hONllWUC9jcUllTENEVTd1SzRoNzJIcW1YRDlFUE8xNytFS29lOWhqRGZvK1NLUnJPTnpac2pNMXNtcWI1UE11bmFUNk5DN3orLytPbFh0YWYwbGUrajZwWHdMT0lpNFYvQWpnRlVsWHpkeGZUYjFDZVFzdjJzVDNLOHo1ZFY0ZlA3eWdrSUJFM1hWenFkazRDY0ZXNU9yOGZVSi9aZUZkdGw2Y2dZNEova2l3TUViSGg1cVJ4SmtaUVdZSFdPdWo3YTFnSkJTbW5lVDVONWtkUnBRaW4yMVZackFJZU5DcEVPR0VXWVh0NlAyWnFiT0NtYlNJVkFjOG5xLy9zNzVWLzU0ZldhWTZtbkgvemlpVjZvZHo4eU4zblgvZnQzL3cxLy9tZk5KT01GTVFteE11YTdlNUtqZE95RGV4d2syWExhWnBtYVdhdHpiTE1HTVBNTzVKd1pLb05SZHhlcnZnYW56RllvckdhT2NBT0FHM0pzMzJLYTVmMUFnSWJtOWcwVC9KWmxtWHBaRzZUbEhkcWZyV2xHV3BYVmVycmg1MzgrTnFYeG5EWVlWWEFydWZzWVEwTlUwZUdVOXd4OE9Lbk9xRzBvMjhNWG1LQWxDejhabkx4TCtuaWw0bHN1eXRmZ1poV2lMUVVuaGJ6YjZ1bVh3bXBxS2thMnFsNVF4b0x5TjdvNW1nMDFFaVJpYkdjZXBBMHNhOXNNcVhKY2I4UlhpdFpuWWl2bWdnUm1RQWFOSXErZHF3ODZHbGVRU0RmdW1SRklDQWZ6cUlONTlVcEtWQjZVWVZIWW1aSE5heW8xcDVCMkd3MjVYWmRDWW1pRk5TZUJGUjVGZEMyQm9DTkE0akttaFM2ZGFTZ3NsWmgyanBBY1dFbVYvN3BELzdmZit4dmJOSkpKM1FTcG5OZi9kTlhQdlBZYi9tNjcvamgvN0crV0VuZEptOUFnaXpxcEEwemM5aGlsNlRXbWpSSnJkbFRETkxHNkVYUjN3NFBZbnlKcFBhT3F2a2FPbG16WnJFdDJxTjFSR25Vc2cwQUZBeG5RSWs0N0xOT0pyTThueWJwTk1uekxyczM3aitVamhWWDEzWGxYZDBHdTZTTlZtczB4WDBrdFRQc0FZZUl6SktIWEx2dzZCcDYyR043bnhucEFmRUhBcGtvNUJPSTA2blVPVlpXNzJmblB3eC9CMG13QU12Mk5lMjBMb0lpL3pLZXZUdFBIcDNvSnFSRGRTSlBWQkhTc3RBZ1ZLaUU0QVZLOEVJSzlVSUtoTklYVHNpQlJOVjc4dERhQjVKUWdTbTlFbWhkYlpPTU5GMTRRU1VxUk1YMllMVmExZ292cUQyNWNHU3lOeDRvYS9KQTZWUkJXdzhWMm5pRjBzWWpuQkpkQzd5UUMyWHZCS0tvUFVQRktTbFFleVZzbkxKQ2F3RVVIcXlZZUFWQVRrbGF1VW5LQWErbFI3Y0dOaUZuRXdCVWliei9xdDk3Y0g3L2o3enYrMWVja0tvbFBGOXNmK2JlaTQ5KzQyLzgxci83MSt1elpTaTdxSzBGU016V1dHT010U1pORTJPc3RaYUhWTmVsWkZHN3ZTdUVLSHBkaFpoVWxkSG56WXpVLzRlaDJGaHdSWnBuRnlmb0VLZnorTVp2aGVSNlZRMFIxVFJKVFpxbCtTd0xaZTFDZ2EzUnBWTFh0ZmphdThxN1dyejM0cW5ydEdmUXU1NlBTQ01lY2Y5K05wMC9BSjNYWnVpNGZ4Zzg0dXlXMFpQdHpNY1JkbW9GWDJNZ3FSSVJOdzJwQUo2MEppM0piMG1XckJ1V0MvSm5MR3ZTbFpIbGx6WnBMZUpFbkNhMWtGTlRDM3VRRit1VkswbEpkZTFTbDF3L3o3NVJJVlh0bmFEeTZrQzFJeStvUEZXaUFtd2RBTm82S0hUclNZUXFyd3JhT2dBb1BZbWlsbERjbFR4VWZITTBldTJaeUR0aEFMV0NRRTVVQWU5VlFVM2RQR3BxazJuUUFOczA0VjdRQWdnckhVcXBoNHdMUXJ0M0ZHaHMyMjV0QkFDSmdpUm9nU0xLb1FLL2VDQ2NnUzRxamxTZ0FuWHFQTFNHS3NTSmR4QVA5ZXFjRjA4aUlqV0poQ3B5Sks0ay9yTWYvdW52K2VJbnlzbjB0dHYreUlNdkhUNzcxTy82WC81T01zdWxka3hzVGRnNmJsTmpqQTBaV2dRMGVXRFFLS1c1SVlOR3NVdXlmREkvSURLMFBMM3J4YXV2dmZQTzF5RWRXWDJ6SDYvWmxUZTZXZ08vZzFyakRJd3kyV05jSG1COUZBK0Fxb1NJUDVFaFlwdmFOTXNuODNReVRaTGNKTmx1Q2Ewd0h1K3F1cTZrcmtXZGQ3NnIyUnhIbVNNUFJNZGllc0xvVjdDQkNtaEFKOTF2ZytEUVBqTnBsOUwyUGtueHN3b1FNZFEwUlh1SW9BUjRRRWdxUWtsK1M3bzB1b0UvTis0Q3NtWXM0ZGNzVzhJVzRnbGVWZHRRTGxIbXZ1eC8rcjMzczllYm82dmlWWlM4UXNBcUVBUUpRQXIxUVFKSURUVDdHam8rME5BQ1pBaEd4SFJBM1ozb1M1T3N6WDJ5ZHBkOEdmc2RHK1liY0tuWjFCWU9LdzliZTBKaEdJR0tpb2NLUkNCT1ZkUTdpQThsdXRYWGdCZm5WTHhLcU52dDJnOWV4RU9jaWtBOHhBY2FBd1NpVUdrK05JU3ZVQkNqT1VTN1NiNGdTNkVDSkJrbUl1U0pUUzNiTlBQNTVIcy85NkUzZnVJVFAzWnkrNG5mOXB2ZTlSZi84T1JnUms2c3RXMzJGWG8zYjFEZndHQzAyUjh0MndXRm54U3E0bFZockprdUx1MUpXMnRDbU9LYnFMK3J2YStEaGRWdFVhbWRnemhWOWQ1VDQ3c2Z5clhRZGJ5Y2NkbHNoVEpabTlnMFM3TnBPcGxtMmRTbUdlOUwyeGZ2dksvRnU3cXF4TmZoMElLb2o1YXl4OUo2SUpvNnNveTB5bDB4UC9nN2ZMSnJjM0JuWC9aSjlEeVpJTnlpNm1CSzhOQ1N0Q1FwU1Zma1YrelBTRFpHenVHWHBHdVdrblJMNmdCUFFTdHNERElHY2V0Vm9QWStDS0JKZWZUWHZ2TmltODZ1WDAydlBRRk9JSFVUQTFRQ2d3S2hSQnd6c2cyaXNZdTJ3YkZnMmlqUTFhNVVWU0ZWRFhJakpDR0tWM1hxUFVLbW02OEQ1cWl2eEhzVmo1Wk9SSnlLcXEraEh1TGhQZFJCRk9xaEF2VVFKUklDaklxb2tLb3hJSUFOc2NJWU1neHJ5QkNsTm15c1FtTFlHSk5aeXRQRU1pV1djMnVONVNUaHpDYk1tS1ltVHhQTFNCTXp5NUxVc0RXY3BaeFpUcE0wVDAzQ0prMDRUMHhpMlRKbkNURnhZamt4eEV3MkhBZHFrM3h4cUpNcHYzcFBuWjlkdnlMc051ZG5Day9nSmhGNmhDb0VWNVhNM0NoM0V0eUlYc1FyUkp4UDgrbGtmaVMrbGxDdjdOZVdPenErV2tMdER1NW9rK3U5RSsvRTFlMUpBKzNXYVZXb2tMR0pUZE44bXVUVE5KdWsrY3pzcTZVVnp0T3BuUk5mKzdvU1g0djZVTzJVV2cxNVJ6M2VWUUwzMGxoTUtxTXJibkxIS3V1VFYxcDIxanhIWWVlSmhpT1RPaUdBR3VvSUZmeld5QkordzNyQi9wejhpckVpdnlMZGtGU0VtdFFwaEtBYWpvb0kxYklEMTlST2dxRHpyK3pqTlVUVy9aMFBQL05mdmZkTm4zbVFMWTZteVpWYlpuNVp4ZlVURUpITnFhZ1VOYXM2OHFWM2p1QkZ2RG9IYWZaQXE3cEdqSWlIT0ZJUEZSSkhVRklQRVVOQ1VDWUpwWENKSkRIRVJKWmdHWW1oUUF3Skl6R2NXVFljenBUaHhKbzA0TG94ZVdKU2F3S3BaQWxsaWNrVG15VmtqSm1uTmpOTVRIbGkwNFF0VTVvWXc1UVlreWNjY2o4VEUwcEVVY0pFVEphYktBY1RUSlBRQytvMjlmUkwySytyZ2pZWEozVzVKVGJlMWFyZWg0em1VTFhLZTFXRnVNdTMzbURTWEZ5dGhIeDZZUE41bjZOVFYrdlZtYStLcG41Q2d4TU44aER4NmQyWDdyLzQ2V2FmcWphbmJFQ0R1dXh0a2g1Y2Z1VDZVMjh4SmhWZjAyWjF5bXlKREljTkNlMEd4S0UzNzkvbUNrTFBleWZxeFRsVnRVbVNKTm11YVJmT3BuR3U4czQ1VjJvQVNTamROOWp4c0VlYXhXcG5SSVF0d1B2c3A5SE5IY1Z5WURScTFHUFFyN2lOaVpwVzhWYUNJR2lQc2lIWmtLeFp6dGx2U00vWkwwbFhrSUpsUS9BUVJ3UnR4VnBMYWQwRzJSN1VrV2dOQ2w3UGNLSWN0ajZ2b0gxU09hL3ZYRXovekU5OHhULzUrTTJFM2V6YURYdmxNVEpXUllnQWxmcnNsZW50WDNyejdLVUNoMGh2cEVaejQ1bHBtaGhqS0xXY0pqWlBqV0ZraWNrU2t6QlBVcHRZVG95WlpEYXpKaldVSnNhYUlEcU1JYVNKVFMyem9ZUTVDVFZUbUF4emM2d01FeEZzcTdBMWNUU2labnRjWkJ3RG5iMUVqVGNPSkEwS29SWEVqYTdWbFdkc0l2Q2dnTnc5Z0pxY084UVI2QjV6aUFCVTIrV2RMMzd5N082TEprbEQwOVQ0WktpRCsyUitlUFhXR3crdjMxVG5SVHpiWkw2NFpLSzAxVkNRQWVyYmdnODlmeVNtemNXRCt5OTkvdUxCcTN0TzdGRDF2cDRzTGwxLzhzMkx5emZvNU00TERXOG4xbVlyVTBoMjQrQkdCb2N3am1IbXBzeEl1ejJ4WmRQL1c4bFZ2QXMxbmJ6M29qN3NtUTI3TUlJTk8zbytObHFHVW5HdlpqaDJqY1Ezby9CQU1HdTVvWkNtRFNVVmFFVmFrNjdZRnlSTCtIUFdMZnN6a2lYSmtyVWkyVUpMUUJqYUxqWVRXQnN5NjFUSFBxcWdRR2NkVTJ1WXRvbFIvVkMxMHg0SG93K0REMmRzQ0VFYlg2WlJtT3EvZi84Ny84djN2dkhGODNTK3lMS2J6L0Jrb1NKUUQ1c2UwdXBQdmU0bi85Z2Jmakdkdm1rNytYYVlRMEpwbWhUT3R0ZFJ0S1NkVHlORFdyT25LWndJUlZ0TFZ0RUZ4bHBnTmp3cjVHMkZuWm5OR1UvVXRiN0hGdWlXRDd1R2ZjY2xXMlR2bWV5SU1YWHQ3MFExRVRCY2dUdGYvT1NEbHo4SFVKUGJyUkZHS0VMOTR4dVB2ZjdLWTY5blp1OGRRSlA1WVQ2ZGR3dmlYYlZlaHRLWThjb1NvR3lzRXArKy9MbTdMMzNHbFVWSUpRK3pEdzVWRVNIQzVVZGUxOVdZNlVSRWdITFhZR0EzaE80WUQwSW9rYUpFMXFTVCtZR3hnNHJPcnFySUdDSXdtUjEveWEvOTByRGpLWnpqNHlXVWI2ckZoMksxMmdUcis1MWhuYTZLZmV2YW1FK3RTOG0waEJFT0wxU0ZNaXJTa254QnVpRlpzcXpabjhHZnMyeEpsMFkyMElLMGhucUNiMmtqMkduVUJvRmpMRUZNV2VHK2pvdkg5R3NXYVp1N3FLWm9PSkVRUWtpS0JReE9oZWJLRXpYSDNoeXB1U3g4bUZXZlMveDc3cS9TLytBbnZ2cWZmUHl4UFBINThaWGsybE13UnIwVHNxVlB2K25LcC83Z1V6Lzk3VSs4NEdlL2MyUGZyRkFTaDI1QWc2SDEzSzM5b01NQngreU1BQ0l5Q2lZaVZVOWFrYXhabGxBdm5CUGx3am1RZ0t3U3R6TU9sQm5NVndrdGFaK3RGanZHZXJEdWdqRldoUVlrdVBPMWU1SkJaT3paM1MvZGZmSFQxV2JGSnVuZklFS1RIQ1BlKzhYeDlWdHZlR2VTNXQ3VnFtcVNkRFkvTmttVWw3YzZyNHExaXNRWUdOb3lOdDJ1ejI4Ly83SDErUU8weFZFN1pBVGdYQjEyMWc4V1BwN1ZFS2VCSnY5T21KTnNPayt5U1dmVXFVaFpyS3RpN1oxclZVZ0NNelhuajRmalI1aU1NZHpzNElySTV0ZDhOVm1oWVlPa2c2aUlVL0VpUVpVUHlyYzJ4NjRRSVJ3MDNTQ3hKM1hRbW5VREtZeGZraXhKejlpdldTN2dWNndsNlFaYWtYb0NBQytOY2RGNFJGb3hNUnJ6emlvM0hMVzM2cUlud3lRYTdqUlVQb08zVXhwWFhpZ0xCQWFsUWlsNEpqUlRleVRteVBNVk5UTXhoMHB6b1F4a0ZVMmRUOEFrNWNkbXhjLzQrdm52Ky9nYi85eFB2ZVBCMml3V1Nmcklzenc5VkswaDJMalVNcjd6MFYvKzNyZjg4TTFMaTAzMjd4YjJjZFlLNmdEMFRLTkhnVmdUaVNkSUlFTmdiY0t3TmZzbHlacjhnOFRkWnZlQTVZVDhPV3NCVlNXamxDaE4xTXlVcHNwek5YUGhRMFV1ZGdHZUNuTGxUQ2x0b1kyVzFnS0pOaVVPSWpnUHVOanUvZkZjeG1wUjg0T3hxYXMyTDMzbXc4dVRPOFMyeVNwdjgzUUNLWW5VMWs2dVB2SDZ5NDg4SmVJaENxYko5Q0NMOG9HOGQrdnpCOTVWd1NIWmRxbkJ4bFB3OHVTVlY1Ly9lRjFzdXdCTEcxOUVWL0p3akNoNzFEbFZoUktiYkRMTkp3ZDlhcGhxV2F6THpjcjVLbXpNYjE3VDhiYUFYbG9STVJtRVV3SFlXTk9lV3M2MksyWDVieUUvRlZCUjlaVzRsYmkxMWt0ZlBDQi9SbjdKc2lTNVlMOGtMVWtMU0FWeTFKNGlSQ0R0UFNJYzg5cE9tZWsySU82TGJZdys3eXFmUFU2MVR6VDBGdEJMQ1lCUlNrR1ptb1huUTI4T2xROWdMbmx6U1hnQ21paG5TalpzaHVDbUIvRytqSjI5Q29BeWdtU2JuNW5VUC9IY25kbC8rNzR2Ky9zZmZzeWtORG02Wks4K1FUWlY3MVN4ZEpQWHorLzlIeC8vdVQvNTdQdm5CODh1czkvcTZCTHBaZ2VvSFFJd2lEVlFDQWpxR0JXN0M1TDc1RTlzZmMvNHUrVE9TRGVRa2lna1NJVFRHMHlQVkJxRXViUUtLb0U0MUk5VEpNSTU4VXhvb1R3UlhvZzVBRTNGekpTblNya2lWVTVBQnRydDJVY0xRMjNqOEJnU0lYcDREMWhrcE1lMitIanZ4VS9mK2VJbnFVMzBSMXdraDFtOGgrTEtvMDlkZStMTndiWlZFWnROcHJPRDJFb3MxK2ZielZLYkdPY0FnR3lTdXRxY3ZQTEY1ZWx0WmtOazJKb2duM1pLSHU1WkE0VEVDV2JPSmdmcFpOcmxpS2xxVld6S1l1M3FrbG9HMEt2Z3ZiNCsvTnhpNGNqV2E0Y2UwbTV0a0p6R1d1S1UyREtib041U2cyNGVVa0pLZGVlb3orQlg1TTVRbjJoOVJyS0dPMWNwU1VxU1NodXAwb1VLQ0ExYTBFaW1EZFBIeG43bjd0ZFlmSTEwdG9qUmhzL2EybjBlQUlVSU1wR29BV1hDR2ZoUXpJSG5ZNWdETVplOE9SS2FFRStWRW9WcE9ieTJzUjZCd3Z1NkxqYXVLb3IxdVUzejQrdFBpSzliRjBVM2VsWk9UUDNLd2ViN3hYM3BIMy8wMmUvNTZYZmV1K0Q1MFNTNzloVFBqMElVdXhSYitQeXJqei8zWjU3NWtkLzIyQjE3K00xTDh3NGxrTlN0Ujhnb0dHQkFvRFhya3Z5RmNRL1kzVGJ1RHN1RjhVdGdpNlowQUdzYmt0a1ZUZnQweU5pVUNIQU5YbUpwU1NNb0FsWXBVY29WbWZCTTdWeG9CclB3UEFkTmxSZkNPWkFySjZBVXdRNlBaR1luUW9OL0I2TWxqNUNjalYyZTNDNDNTK0x3UHdhYjhDRWNVazlFSW40eU93Q2JWbHRXSXA3TURyTkpWeFVPcmlxMm01V3JOcEZydHRYMGlZblpPOGRNR2xuaVhjbkRibmhqckZKVk5weWswM3k2aVBlSmxkdFZYV3lkSzBQek8za2tFZnVKRFp6b2ZvVFdZWE02U1RBbkFJZ1FDY1FUS3ZJRjZacDF5N3BrWFJwc2pTekpYNmhia2xid215WW0xdXk2RFRZM0k4UU0rc291SFZXTlZNZmhVS1A3UTA4cytuYkdkOExud05kOWl3RyswVGdvVmNxVWNqR0hZZzZGajhVY0tGL3k5aENVSytXZ1JCdWlsVUMzelFJQlJPUzlrN3F1d3JuUTIxV3hXVmJGcWk0SzliV29rREdYcmoxMTQ1azNROXJOVzlFcUtDV2tMaTAvTnExKzZPT3ZKUCt2OTc3OW56MTNpNjNNcnQ0MFJ6ZGdFNmdRVVBxazlNbHZmK1FqMy91V2Yvbkc2MWZMOU92WHlWT3NEcjVnWGJLN2E5MDl5SWx4OTlpZnNXeUFtbFFidTVwTWExYzNPd3hHQW1kb3p3d2dQOVFwOXV3dkcxQnA0OVJwWS8zVW1SdUpVcUtVS3MzVVRJVG1hbVpDQ3pVSFFla1Z6c0c1YXFLY05EcE9vNTFGaGlnaHVFSkN5U0tnemVicUExRXRYd2p1UW5SSDdqUUVrdVN6eVd3UnBWTHFablZlYlpkRE4xdERpSjBxMmsxeGp5VHNKSUNxRW5FMm1XV1RSVndhb0M2M3hYcnBYSUh4UnYyaHhCOTIzS0FhZFJwL01DU0U0RWdxYUVHNlpyOGxPV1cvSWwyeFhKQnMyRy9RaE5TY3FrTXpBMjZYZ1NPK0c4ODJWcXJqSzVadkdLNzlMbG51YlVvYkxGRkZrR3hCMW9HVkVxR2NPQmMrRW5Qa3piR2FRODlIc0lkS21TSlRzaUFtaFNKb29TRTlKZEFxTVZId21MbHFXeGViN1hwWkZhdHlzNnlMdGF0cjlhNzFvM056MEFNSVVGZlh4NDg4Y2V2WnR6ZjVFakdpaHlOSktXVi91dGorSy9ZZi9mc2Zlc3QvOHJQdnVMM2s2U3hOcno5bEYxZTBpWlhyMW1mSHlmWjdudjNSNzM3aVk5Y1BiL3A2bys2Q2RVVytCRUllTlJGWjFlNEFpZEZhandDSUVaL0ZtS2tOSER3dGZNY3diK3labHJmc1dDaWRJQTM1NHI3N0NwRENFb3h5cWhTc3pZV1l1ZkJNZVNGbW9UUlRuaWhObFRNZ1ViSUVia3NuaEdaOThOaFJwTUU5WkxJQkIyaXlPTTdhRW5zSUluRzlyS3Z0M3JNVFk0OWRzQWxINmdHMFBSMHBueXhpNTJkZGJvck4wdFUxbXBDR1JpVFlGRitCVWtqV1VaaWdrN01LNENFbGFjVzZocXlObkpOZmt6OWp2eVJzMksraEJhUmdDaUcxc0RZYzZFMmJmSitZMGdaNlRyUit1Mmt1SXlrWElVaU1DbzIrMmhCa0pLS0ZWRU9nSW9UVUFSSllVQ0k4VVhPZ2RDRDJVT2hZa2t2Q1U2VkQ1VlFwQTJ5TFk3NzErMm1MUjgyWkdXSFBqSGQxWFczcmNydGRuWmViaTdyWVZ1VkdYQzNpb00zWnUwSC9WeDJPR21nS28zbDNjUG42NDIvNjZoaDlJK3h0MUFGYmZtNWUvY2p6ZDRzLzlxKy82aGUrZEt3a3llVkhrK05Ia2RpQVJsNU40ZFBYemU3OXBUZjl3SGM4OW42Q1VaZTNKOGpIT3M0SXFrUFVHa0FQclVJbzBGQnNoZ0ZXb3ZaOFBYU0dkN3lJUTBUdmwzS295eUI2ZUxCcXc1ODZqNDYwSXdLSUZBWmtsVkxsS1NnWG1vbGRnQlppWmtvTDRWeDVyalFCcFVxMmQrYzJNbE1qbDdWMmZCbUtOSjlORjBkUmZWM2RyaS9LN1ZKbHZIdDRzSmFST2hvbUtrU2NaTlBKOUlDdDdlNDZWeGJycGF0TEJRaXM2R0xON1Y0V2NpcWU0RWdMbGpYSkJySTAvZ0wrM01nYWVtSDhHa0czaEF2V2tTcmFSZ3lvRlpVREJrblVyMnV6TUoxaU0xQmdvaHBOOGYzZGhSdzJNaEtNSVFQWWF5dmNRRWFRS2svVkhDZ2ZlWE1nNXJLWVkrV0o4b0Z5QnFUYWQrcEQrR2VBdFlIYVF2NEQ0TDBYVjFmRnV0Z3U2KzJxMkt6S1l1M0xUYWltQlFTemdVRGMyckdqd1NJVTNpSUZNU2ZaSk1rbWsvbFJObHNjWG5tMGl6TjJ6akJGRTlGTldBd2g5MS9DNWwrVzY3UC80U052L3N2dit3cTFVMTVjTnBONVUwaUNRSXBLVENucGR6LzIvdTk1L1UrKzVmZ1ZLUzMxdXl4R0JCZkRPY0o3OVFSUmVJQUVLY3hFK0pLYVkraUcvWkowRFY4d2FxaEhyNzZHQ3FJbTZxaW41eDNtK05EVjNLSEdNY1pqc0x0WEczV3YwY2lnellZYmdJd2lCYVhLbWFjWnpEeTRpOVFjQ0MrVUptcW1TaE5GQnJJZzB4cWlVUFhNbk04V2VkNGZIdXFxY3JNODliNGUwV0UvdGtDRVRUU1FLTWxtMldRV0grWmFPU25YamZRRFFIQ2tGYlFrdnliZHNGK1JyRmpPV1pia0wxZ0w2SnFsUW9CeUE4VDJXTkZtRUh0MzNJNkZFZ2FBYThZOVZCMUhYREJHaUYwZE1ucExtM3ptbG8ycGdvbFNvVlI1cm53Zzlsak1RdWlLMkNPaENjeENLVlZZZ0JYQ1FHT2ZxTVFBamVSQ0tKTElxdXBkN2V1aUxyZkZabFZzTG9yMTBsZUZxMG9ScCtna0lnK0cxemdsT3EwazVOQVRBWlFrYVRiSjUwZVR5VHlmSGFXenViRVpHNGFTK0JEcmF3NXpUOGluVkZvOUp6bXJpdk9YbDh2YlMvZXhzNE1QbjF6NXhNWFZDemQvdFpnSkdSVnBqckVQNDJhanhFUlkxOWxFdDMvMDhSLzd5Ky80TitLVEZvdEhnZTh3YWRIR3grdkJWaWxYbm91OTV2bXkyR3RpTDNzK1ZMTlFwSUJqZGRBdCtUWHJtdVNVL1RuN0pjczUrUXZvMWtnRkZCQnA0UmlxampGYXpObFJibnVDSExteTQ1dlI4Mk1OS1A2cGZRVm92blo0b3MxYUV5aWNrQVVPdmlKUXJqenh0RkM3VUpvS0g2aVpDMmJDV1RxNW5COWNKV3FxWFFoUWJhdnQrZ3dxalpyWTJLSUtnRTd1dlVKZ0FkSnNsazluZlkwTUtkV3R5L1VEdDc1RGNzSFlHSDhCdVdDL2hxNU44RVNyS0J4cGMyUm5GN3lPN2JSQktBeDlmdENRZm5iTmpEMXNiMGhZc2Y3WlBUUmNwemJ5UnFyYWFTTmdjQzZVS2kyRUQ5VmM4bVl1NXBLYVkrRk1lYUdVTk1wa3dEQUlOUlpnWnkzMDNjWE11QnNURWEwdkhxelBUMXl4M200dVhGbUlkNkc0YlNQb1FKMjhvdTRnT3FBaFAxRUpPYzBndHRZbTAzU1NUK2VYc3NrOG14OG02Y1FrU2FQK05GVnZESUVNUzRveTFRdVYrMUtkMzEydnYzQmVmZkprOXBtTCtVZk9yMzVoZWZWZVBTOThUc1NHdlNWbktaeGNTV1JhTEJjbjFVYkx0YXp2eWNXZGFuMnVWVmw4N3dmRnhYWS9xVHFDQUI1S1lLdTA4THdRZTFuc0k4NWVGM09vNWxBcFJ6Qkc0SVBtUm8xL01oeVZRU0hmSUNTUUVEbEkxVVQyL1JuN0M5SWwrek9qUzRNTjNBcCtvMUpSY0tXUVVRQXdTaUNZZGkwb3dobWdHMjJMQzFFdXhFaUxIbVFxUnROc2FUTFNzTHFyY2N5b2h0d0poSXJzRk5DUGdWVEpDbExPRHRnc1lCZXdoMGd1a1YxVW10ZWxPcVNnaVpJRkVtVUd5SkpmSjF3bENWSjlDV2NuV2oxUWR3RjNCcmNVdDA1bG15T2s4M2MwZzFaNVlCQVRNbEJJUHRhT28xQS9xZUhnMFZGZ045OUduNlkySjZPRFkyejlhQjhwNzV4S1Vhc05xSnVRRVFWZklUSEJDR1hncWREQzg0SGFTMkl1S1Irb1BmQ1lnS2VOWjVLNGlZOURTWlhVdCtlL0V3WThzdU95WGZjRGJhZjNNUkN2eis3ZmVlRlRITHh0UkVSc1RCck10SmJxMElBcUlHZklFRkpsWXBOaytXUTZtUituMC9sMGZwVGtzM0NrWnBpN0JwOHhtRWx6MmlTMEl2L0FWdy91cmRhdnJ1dFBuMmEvOHVEeVo1ZlhubDlkdTEwZXJTUW5KVGFha2pNc0MxTTJnVHMyUUFMMWNFNVdaN285OCt2N2ZuVlhpM090dGxYbG5SREVUUEpnZEVRMnFKTFk2Mm9XM2x4VEUyVGRKYUdwVWdyaVJ2ZFJUL0FFMXlZUGUrY3FKbElpUXliUVBFV3BWMUFDSlVxSjhvRzNOenVaU3l5THd3UFNFbTRaZ2s5K2UxdTJkMGlYTEd2U0RhUW0zVFpPUDRMMi92QW1rdFdWc1lnWTlJQUNXMXdkck9aQXBlcFhPaFlKRFRJR0ZhK1QyNkVqVldHVXJJVnV6enJYblFJZ1RpaE5tQVVwYUNxY0tVL0JCNTduZG5INk4xbExTSzNxdEVsRUpDSURZdE9vQVJaSWRFeFVHdEVaaGZCck84WmQ4dXRtMUVXcmUrVmg5OUdva1k2ZTQ3bHFsMVlTQXI0S1E3RENFN1V6cFFQaGhkakxhbzZGRjhvSDNzd1FQSk90UFVEd0lXcEVXamM3aTFzZ3RvUXhrTlh0YXZXTWNrY3JhK2JXa0t2STVadXZ1L1BDSjVsdEsvREMvd01EQmFDdHVGTWxNc2JhSk05bkI1UEZVVDVacE5PRE5NdUpiYXM3cXlnRFpJQ1V0aGxma0x2ci9jVnFmZmF4OC9xbEMvdWgwOHNmT2J2K3VkV05CK1hpekUwVnpLd0pTY0orWVVvb2dRaHNDQW1ScXErMXVKQnk1VmYzWkhuYmI4NjFYSHRYT1E4UkErTEVtbXNIZVBPTitrM1gxbDkrcXd6SG5mZHJUYzduNzl6T3Y0RmdGSFdiYkNDa1pZelM0bXR4SXVLOHJ3R29hbkJjK2k1QnRUbTJJQnhhWUlMTlRPVFFKV21vcXNmeS9IeHhkSTN6UThwdkJlQmVuSjc2ZW12SVE3Y3NXNUl6ZHVlTjVQUVhrQ1ZKd2JwVnJSRUZFZ2dtZUlQaXFTQnNJYUlCMWJXclBDYkl6dFUwWXY4amR0dytTRXBoeFpKdUgyR3I2aXFqaEJUc203eW9oSlRrdVQrTUpxVzk0M2tEVnJGalh6VS9SYTZxa1dYY0l5NGlGaExKL2NBeit1YzdTUkozSFFoR0lhVFMyTTBLWWlPd1FsT1l1ZktoOE1MYnk4ckhZbVpOZ2dWbElCTXlqOUJ3UG1sM2tlb1FpTEhtTWhoenhHTEcyc3Y0eVZiK0RSVlVHSnUrL0xtUFBIajVzemJKV21BaEhMTUJLTEZKa3R5bTJXUnhPSmtmNTdORG0rVTJ5VUpxcXlpMDhRejdsSXBNejFqTzYvcE92VjE5WVZsLzlzeCs5T1Q0TTZ2alQxMWNmN200Y3VaeTFjU1F0eXlHeGNBM2kwSUdvWHFDaXRhRkZHZTZPWmZWUGIrKzV6Zm42aXRYZStjdGxObnFMSE5QSFZWdmUzVDdqbHZ5bGh2Vlc2NFdOdzdGcEI0S0xSckhXOHMzaGVCZDhreXgrQlpuYmhLNUZyWlFsWEI4UlVqM2JaMWJNVHJ0WUZXNy9rRTdvR2EvQlRHYkpKMkVOcE0wbng5ZDdkNzEzbDJjM29XaVBUeUx1endRZ29QV0xDVmt5WEpCc21KL1p2d3B5WkprVGJKaExkRHMwbXl5dDBKYVQydHdkcFEyeWlpSVI3MDdqMlo1OTJtdHVnL1pZdEpvdFR6LzNCK2xJUTUxMnY4UUtTbHF1bGZQb3FHTmNUcjJPKzk3SzU1bmJMazEza1doQkVpVVoySVd5Z2RpRHNWY0VYT29GTzVNRkdtN0JncDQwbERnQU0yV2dnRVFZNDdRS29FUmJqMEU0Z09RTmZmNzFKOFJ2NkNldmJRTlAvK1JueXMzeStEMk5EWk5zbnd5TzB5bmk4bjhLSjh0ak0yWWJXTXRLUVBLVUlNeXg3bVJCNjU2dFN6WEx5NnI1eDRrWDF3ZmZPRGsrdlByU3k5dnI1N1dzMXF0SVRYc1V4WkcyS2xFU3Vnek9jU2hYTXZtUkpaM2ZYRXU2d2UrWEluenRTTVZCbkdlK1p2ejhtMDN5emRjcTcvaVNYN3p0Zno2MWFQamd5dFUvRExxRTBnT1lkVjRRVHVxQ1RIZlVwRFgweStycHQvc3pSRzBJb2lxbE1WV2ZOVkxNelI4ZEE4aVIvZDNtSndDbEdjelRoS29LalRMNTlQRmNZZk80dDN5N0Y1emRtLzhjbEJoUW01ZFg1OUZWRDJyZzY3WkwwbFdKRXZqVCtIUGpLeEpscXhyK0lwUkkzQS9oRkJFbDFrVlg0MGdRSGM2ZVlRcVVWeCtqNW01SThlR3lDUFAvZEVJRjhmSzJZNEkyUFdqOU9nYnV6U0hBNG1YczZlM2JpK1BJbEdld015RTVtS092VGtHSDRnNVVwNkxtU3RsaXJSTnBrVVRlWU8wTzJMR05ER2ErU2o2cE5CTzRkbWh0SWdVQTQ4YWVwV0dUOU9naDRGckJvQVFKZWYzWHp5NzgySytPTW9uODN4K21HUlRZOVBndUlJR3BWY3N0am5Pakp5Nyt2WjJlL3BnVTMvcWpEOTVkdnlCMDBlK3VENStjWDNsZ1R1b1lRekJrTGZrTFVuYkQ0Rk1PSXVPeEd1MTBYSWxtL3YrNG81c3ovMzJ3bGRGSFN3TXRvbnhpOXk5NFVyMXRwdkZPNS9RTjExTG4zM2s0UEtsbXphOTRwUHJ0Unc2elowbTdCN1k3WHZ6elhzSUVubW5kbEdLQ0FLdGhJK0t4YmRXK1plQlFGSUQ2cjF6VlNIaTR6VUpwRFZ5Yk1ZRTJTTnBkQ2VmTHBvc1RkWFo0ZVgrNENmQTFkVm1kUXBBdkcrTFVYVkxBTVRVMmJvSkZXZzNxUVhMU1VnOFVKQUV0ZmFDL1RuN1U1WVY1TUxvQm43SlVpcHFBdG80R1RXVTJVeWxSd3hFa2ZlOUVtdnd0UmVFTFVMS2MzOHN6TE9OVnNmNVJ4MEVSL1pydlBXbUk3blJhRUtYb3MyT0JHbnpOaFBocVlUWWk3bnMrUkxNM0RYSnlsT2xWTlJTS0RJSmJjT0pTb1JPN1lrbnZ5T0tkZlJCd3lxTTF2aGh5OTR4bVk2OWpSdmY5ZG5HalkyN2FSSmNRcTZmc2dBTU1WcWt1RWprRHR6cHVyaC9zaTZlTzAxZVdNMC9kSExqb3hlUDNDNk9IMVNIaFNZTVNWaE1TM1VOV0ltSmpZSUpxcTdVNGx5MjU3SzY3MWYzZFhQcTY5Szd1bllFR0NKTWMvL0VVZkhNSmZmbGo1VmYrVlR5K3F1VEcxZXZUT2MzS2JsUjQ3RFN1Wk5FR2xYZkI1bERaQlNKclQ0N1hmNGcxN2RCV1NzTlJoeXJQU1FBTlZSZDhrU3grQzB1ZlgxSTNTYlZxcXBjWGJRcVNRUFJQZkp3MzlVSlEyWTdtYzVWRWFwdVRBOEcrU2dJbU5Gc2hCZnh6bnVuNHNXTHFoY2Z0Z1EzaFQ4YnR0dG9RZDJ5TmM0VmdCcW5hM1B1UWMxYVE5WkdWdkJiMWxQMjUrUlBXZFlzSy9ScXJiUlQ2WFhhZG1ZOUViWmZla2EyNjFBSWtoQXhMUTBmM2FYcFVVTWQzQVJ3VGIvRUFDdG5TaFBoaFpxNU4xZkVISU1XM2h5b1dTZ200S1RKUDFUdGQ4cDFCc1pnb0RFekh2VWF6eEE3VDdaeXI2V21CaWpkblY2Q3RTOFBFODMzWVl2dURpbUdXNk9STmhCZ2hscmRKbGdtK29EY25jMzIvbXBiZlByY2ZPejA2UE1YbHo1eWNmT0xtMnNQcXNOQ2pBSUppeVZ2U0FuUytwRTVuQ1VHSWhXUGFxUGJNN2U2cjlzSHNqcVI0c0s1Mmp2MW5nRmpyRnlkMTIrNVhyenhXdlhPeCtYdHQ2WlAzYmcwbTE5Szg1dkMxMHVkTzgyOGtxcHZ0WWt3UXdvbVdRaXZLNVJveW5LYXJuOHAzZjRTdENES2hnZ1FxMHRCcHloQVdURjlWemw5dDVvRlNRbFNGZkYxV2RYbExnVDNBblQ0VllNK3lkWm0yYXdUdndmSDEzNHQ5ZFJWdzFsaUl0NnJkMTY4aU1EWG9mZzh3azdVNktJK2xnR0V6eG9pYmMyZTdCRFpVL0drQldORHZpQTVaN2tnZjJwa1JlNmM1UUpha0phdGF6MGdnd0V4bEZ1eGlVNENqa1JhSXdtSDBCZ0kxdWdkYmVRU0pEZ1lXd2FYQ0dmS005REMyNFh3WmVGTFloWnFGc3B6b1J5VU5KbjRUU08rRGJ0aGhPNHRDY1NxM1M1SEdLbU91MVF4b3NsWWorcGJpWWlRZHQ2S0Z1aGhYK084TUpDR0xmbEVERmlVQ2M1U3VRdi9vQ3p1bmE3V0wyL014MDRYejUxZi84alpveThWUi9lS3kwdWZLaUVsc2V3YXFrT3I1QkFSaDFReWtucUw0bHkycDdLOEo5c3p2ejF6NWNiVklrSVF3d2xtbVh2aXFIenJqZnBkajVmUFhFL2YrdWppK3RYcmVYNU5rMnNWTGxVNjgwb3FJYXduRkFTQlVuZnN1NmozV3RXNlpVcHpYb2pXclRKamdjeTY1eWZuUDJUcUw0R3lPRll4WURvZHg5U3ROemVxNmRkVWszY3BNYWtqSXU5cjV5cFhsU0QwTkJ3cG8rMyszUUdNdGMyV2hxak5Ka21XQjRIR2JCYkgxL2VtWXY1YUxsVWZpdlVGZ2VuRkJSSnRxZ3IyaC94RmVuSWY4Vzdnb2tSUmRSS0ZLcWtqRkpDS1E2VUZmMnAwcmZXSmtRdlNOZmtOYTRVbVN3d2FuTlhvNm5lU0FoMFJEZ0hhVVloNjZ2STFRS0RNY3dhYWh6MEJhZzdWWFBKWWlKMkQ1a0laa1dsclhRZXgxa1oxZTVuVFRyQkpGcUZPUXdqbmgwSUViSWFxNW9DUWRyNE9GN0FGZVBSMTU2MWVaVWFrWG1MWVJid2NyVW9lVDRHTXdoQVlKQlpGcXFlSm5wRjcxZFZuNTV1ekY1Zm1JeWNIenk4dmZmVGkxaWRYMTArcmcxVTk4VVNXeExJWXFrTzBQcVJ1dHhGekF3akVTYkdTN1prdTc4ajIzSzFQcEZ6NXVxNUQ0VFUybWRVYkIrVlR4K1dYMzZyZTlTUS9lMlB5MU5YanhlRU5rejNxNmFDVTR4cFdoVFRzWUFaQ3RDeFFuVUlWVXNuV1MxbnFaaXNYdFJRZVJTMDFrNzJTUEg2YzNBSlkxV216S2lscG1hNStNaTArd0g0RHppS0RCUzBPOVJtRnBBNHFkZnIwOXVEYmZQSTRhUTExSUlpcnFxb1E2V0lHRGFSYisyVzhocjB2UndGQ09wbFpUZ1FLbFNTYnpROHZ4NC9YVlNIZUdadXdNZFFlNVA3cnVFSTBNeFFyRXllcTR1cW1mTHVxZW1rMnhFUURIdzQ1Zkc1eVZGclJGL0pzYXBLS2RjdCtCVGxqWFpNN1pWMnpQeWQvUVkza2RGQlA4dHdmQWFCTm5uR29uTXZLQkdSS3VacUZtQ1BQUnhxMjRkZ2o1Vm5ZRXdBeXJlWUF0SWtSR0I5bnhTQWxZaEFJM0hEaHRreWJjNlVyUzFlVmRibXV5bTFWYkp3cjAzUis2NDN2MkJGUUk2b2JCa1QyM0IvcGt5T1J1RXZHSFFONnlQUE5ka1FUb3VRR0x0R1RSTzZ4ZTlXNzA5UE4yWXZueWVmVytjZE9IdjNjNnNvblZqZGZMWTVYZnVLVUxmbUVuR0ZsdEU2bzVyelJKcjlNdmRONmcycnRsL2RsZWRkdlQ3Vll1cXJ3WHJ4bktGbExpNngrNWtyeHpKWDZxeDZYTDN2Q3Z1V1JTNGVMbzJ6K3FQS05Db3NhVXkvVVdIZmFuYnJPQkJLSXFITlNsYnF1ZEZQS3BwWk5wUnNmY3MyYTVOU0cyUXY4d2x5NWxEdzJ0Y2V0WndXQVVVN1kzWjVjL0ZCU2ZVNHBJWmd1dkJaRnRDSndhU2s4cTdPM1Z2TnY5T1lxU2RGb3A2NnNxMHJWRC9hOURaZHdSSkF0amRKa3VnaE1Tb0UwbTh3V2w2S1lwRjlmUEhCMUdVNU5KOE1VenJjd3hyQmxZNG1aLzYwS2w0VWpYVlNkZUJFSlZRV2RldTlWb1FKUmplVjRaSGRHMkVoQXE5YUcvRkpTRWdjdFdRcVNGY3VTNUlUOHgvOFFPUGVVd3h3SUhZazlWblBnK1ZqTlliT2pPVlFjQ0RTR2JqK0JqTlUrWW9LR3cyd1J0QjBGVkVSQ3lrVHBxbTFkRlZXeHFjdXRLemJPMWE2dXhGZGRPWXJRQnJONTVwM3ZOa2thY2RnOTlscy96MllBSThtSjF5QzJIZG1JU1BGdW5sUUtCL29Zd0JERW9rcjBQTUVEOWkvNTh0NnFYTjFlNHJuenhZZlBIdm5DOHVySGxvKytXbHpldUt3R0VVbEtQdUdRWTZXdDZzWWdRbHQwQjY3VWNpbmJwVi9ka2ZWOTJaeEtWVlIxN1IxQkRJaG11WC9zdUhqcVV2V3V4K3F2Zk5JK2VXWDY1UFdyMmV5eVRSNXhmTG5RdVZjckVoVDdvR1J5Q3luMVdqc3RLOTJXZnJXVnBaZXkxa3FvYnRJMmlLRkVjYXAzTCtFaGNJYnNKZnY0cGVRSkJndDh5OUlzRWZMVlQ2V2I5NUJXR0ZpSlkwT2dTVXlScmRpcjVmVHJxdnp0YXVha0JSU3FVbGVGZCtVT3VVV0loRWdZdGt0blRKSk81azBCSlBINTdHQXlQNHJmdTNod1I4UzFyTHRmMzFBMzBCRERHTU9XbUNlenc0ZDAvbXU2RkFwcERseFM3N3g2Y1U0YW5kYXJOSWRxeENaMFhIZ0dDTjRmYWhHTVFVVExsOThyWmlFMEFlY0syNloyZWdCUjRZQWg0alpGeWpuWUx3Ri9SY1Q3MnZ2YVZVVmRiT3U2ZE1XbUt0ZXVMQU94ZWQvdXZnc0RHOWF3b05iZjVzVWZYWDcwaVRlL3E2NnJqb1QyaWNVQkJ4aVNXZnhXVEhYUnF1NFJpVTE2a0lJQk1haFRQVXYxTHZ0N3FHOWZGT2QzbC9UOGV2S0IweHNmTzN2c2hjMlZMMnl1clB5MGxKQ0s0aXg3aGpTbVRJQS9FWWdKREdJU0wvVkd0dWV5dXFQYk03OTY0TGZudnE1cnIrb1p4TWJxdFZuOStxdkZzNWZycjNwUzMvcFkvb1libDJhTFM4bmtwdWZyRmVhMTVGNlVFTExxdERYcVNOUUxxa3FyeWk4cmJFdS9xV1RsMVFsOHFMQkl5cTBwRVpOY3BKYkgzSTBJS2g1K1lhOWN0VS9sZkNCTjVjRUF5OVQ0ZS9ueXg1TGk0K0MwY1hmdjBTQTZnQXRKV2FlUEZmTnZjK25yQUFrYWNsQ0NmRjMxU1ZiRHRSeC9CQlJzMktUVFdidTdUNmZ6Uytta2Q1WjZWNjNPN2t1emhXWFBZQUpkS0dSK2REWE5KdDJMNVhaVFZ4dWJwR3dzczJuRWFjanYvZlZkVGVFalZTZml4Zm1tK3BHWHJ1NXVQTEUydUUrQTBzbTlPMDBLZkpNUGdXaWQybWM1VUF5SGRGWHgzcnZhdWRxVlJWMXU2cXFveTIxZGJseFplRjk3VjBOVm0yS01RVGRxaWEwMXhwb1lSQS91eGkwWnhKbXJ5MXR2ZU9lbEcwK0s5NjJNMnBNanYyTVQ3cmtaSVJwMlNKSGp3SzZoTXBGMXFuZU12SXJxMWFKYXZib3NQM054OU9uVjhVZFBILy9VOHBFdmJLK3MzTFR5aGhpV1hNTENrTUZ5S3pXN2tNZ0FYdXNTMWNhdjc4dnludCtleXVaTXlvM3p6bnRXTmNTWUpmTFljZkhtNitYWFB1R2Z2Y0Z2ZXZUd3Nhdlgwc2xsU203V09DNTA2b1ZWbGVDcE9SV01GYXJ3dGRSZWkwTFhoYXhxMlpTNjlhR0tSek8za0VKSkE4Vmg5SFdQampCNEp0UXh2Snc4ZmlWNWtvaFZtd0p6SUF0SXV2bmxmUFhqcEFVb0h5a3FJeVZlQTljZ0tpZGZXVTIrV3V3Tm9Bb0N6Ym5TVllXSTdxWWFkL0l3TXQwSjBDeWIybEFtRkJDVitjSGxOQXBhMUdXeE9yMUxvWEQzT0d6Ykx4SXp6NDl2ZE40ZFZWbWRQNmpLYmRpdFRNenRLV1ltbkxISEpqR2hSaGwzSnhEK09xNFFSZ25lSUM4ZTN2dWcxZ2FqVEFXcW9kcGFReW5vRkp2QTBGWEVPMWVYdmlycnVxeUxiVldzNnFwd1ZlSHEwdGUxcW0vcWVBTUkzS05sSWRRZE5OMEs0WDZwKzlvQnUwS3BhY3dZZnQwN2Z6TnpiSGJ1UmFDZUV3KzFvMTFqTW5BQm8yQ1FnU3JCV1YxTzlCWDI5OG05dEMwdUhoVHVoZlA4dytmWFAzejIyQXZyYTU5Wlh6K3JGNlczSUVuWUpTUk03VmFNbG8xUjBQVkRIVHR4cU5kK3U5TDFIYisrcjhzVHFkZXVxcHlIZUFZb1NmWFJSZm5vVWZXVmo5VmYreVNldUpZOWMrUHEwZUUxVG04S0h4WjZVR21tRXB6UDJwU2dWVlY0cDFXbG0wcTJoYTRydjY1MTY5V0hqZVFFQUR6TWloeHVodXJUYjBkMXQxb1p1RGRnMDBSbWZVNEhqeVRQNXZZb2VHc2Fuc2lwcVY3T05qK2ZiajhFdGdxRC9WeXlXMllsTFpVWDVmUWJ5dGxYSzZVa0ZRQkE2cXFxNnpLY0JQWGFvaWVNTEo4c21BMG9IS2FCdzBzM09DcmlYbTdYbTlYcDBPSWNKVVZCVmROOE5qdTRGRFd0RnlldlNuTzJBcldTdjhNMEJLZEdPT1pzZG5BbDN1RGY1Q0grV3hLbkZ4RVJUeWQzWDJSaVJkajVWdFpWMFhoS3R0dTYzSHBYaFFRSUVXbHFPWGVhSk1LZmJtR2JCTUNXNUZxRHZVa2kxS0dBN2RZclZvMGFNQVVDZnZRTjcxeGN1Z25wTmpRQWc2WWZaaC8yOUJ5MnpDaFpOSnM4U3V0WDhPZld2OHIxZmV0ZjNSYm5INzI0L1B6MjB2dnZQZmJjOHRZcjI4Tlh5K05LRW9VWWtwUzlDVlRYQlE5RFFKYWFlbkJRUWIzVmN1MVc5MlI1VjRwejNaNzVxcXhxTDhKUUpvT2ozRDE5dVh6cWN2blZUOG83YmlXdnYzVjg5ZUE0bXo2aTlwRWFpMUltWGhraDZ0T21EM2s0SjBXaDYxcUx3cThyM1hndFBlcUFkb0hlR3FycnhRLzJBR1JzK0EyVjlFN214WkpvRklNQUJONG9IOWdiVjlPbkRSSVAxM1JHQ1lIdDlnT1QxWSt5TEVGNTI4QWdsMnpBR2RWREs1YytYY3kvMmFkUEtVRHFRQ1Fpcml5Y0srbFhJVU1BSUxiNVpOYU9XNG50d2ZIVnVMNTF1VjJ0ejAvWmNNZlRoOXB5Z0pwTUQ2Nm1XYi92dGlvM20vTUhVVVdpc2FiUXprNG44OE44ZXRDOTZIMjlQTHRuVFJLS2VqSmIwNVlPL0xWN2crajAza3ZyOC91M24vKzRxdnE2Rmw4RkRicXg5amprRTRRMXBZNElZaXN4WnJWNzBhTGJ2RFBLMld3amJkMHoydnFMQmFwSlBuM21IZDlFaGp1OGFYMVJYWXJtamtJVnJCUTJvcUVvZzJOWkdYZVA2enNzOTIzMU1zc1pkTU5hS1ppTS91am5IdisySC9vZDZaVXJkSEExTVdSUUplUUhtTnFDQWdCUlFrVGlLNVJMWHk3MTRxNXM3cm5WaWRabFhWZmVoMm9EWkkwK2RsZytlNlY4MTJQMW0yN3dXeDZmUG4zdDJtSjJTYk5IUFYwdVplNWdWRUJOaXF3QTZyUjBVbGU2S1dSWnliYlNqZFBLaDdPN0tGUTY3M0lBaHJJTHc3WG82SzFiajEyUjJBbW5pSGxHdEljaFNJT2lJYXFTbXRuTjlBMVRjK3podW9WUW1oci9VcmI2NmJUNGVNZ3IwRUZlWlUrRVBhL1ZHcEE2Zit0Mi9xMWlqcUZWU0NyenZxcXJTcVFlNnBCN01KWU01NU41NTZDMTJXUWVPVXNCckM5T3EyTFZIaGdWdFJhRWNyUGhDUFBqcTBtU2RUOXVsbWVoMUZyM2FQUnlwRXlwVGc4dnhUbDB4V2E1V1oyMDhLVkdtNFF4eGhBUkdXdU5CUnRtWXd5RFRIQ214SmNsa0hmMTV2d2t5ZktRRmRXS09MUnJpVVlKMFJIWERDTlRqWjhlWlAwMmhOc3A5ZzMxdERRWjBvNmdLcUxNQkdLYnBHaytUYVlIV1Q1Tjg1bkFtLzZRMTBpcEdDU0JHdzNuWmhLeDFLUkxyaTZ5K2lXdVgyVTVJWGVmZFVOU2d3Q0VlRG9MVFFnQXU0MGtXSjVidmMzTGFYSnduV2VIbE04VjJtdzJiNGNMZ0ZUck84L3B4YXR1ZlYrcnJhOUxKK285UXd4WnZUNlZaNjl0bjdsY2Y4M1QrdnFyOXBsYng5ZVBuamI1STJxT0toeFZmbmFxWU4vV3E5ZktvNnBsVzB1MWxmTktONVZzUEp5RW5mNEVVaVlpaGdIdktJbzYxTm9HTEtoYmhIaEJJdld5STdsbTNZWTNCMnZkTnRod1BDSXlsV3krVkg3MHdGeS9rajZSSW5kd0JDTGRpTG0rT2ZyOWRmSGh5ZkxIeUo4UkpYc1RKcWxERGtvQVRZcVBtZXFMNWZ5YnF2enRpaWxRR3B1eVNWMWR1TG9VRVhEbld1NHhNWXhTbkt2TElzbW1ZYVpWc1Y2SnhEc3Rwb3NqOFpXcjZ5aWp1bkV0ZEdBaTB1M3lOTGwwSTNycnNLNEtsVHBrQmU2emtrSTd1anAvY0hRbDZ3cC81cE41WFc2OUsxdUZLenpvZmUyQ2tsMjF3Q1FRdDVXdnlUQ3paV1BaTUozZWUybDFkdmRMSDMrdlNWSjl5REwwY0tBK2hYejRjS3dLb3FHMDhFTzdqeldndElpRVFDRVRzMGxzbXRwMGtzOE84dWxoTnBrbDJjU0VBOUxDM2dMdk5Pek1qQVlRTnI1cU9QMU5QZXNGdVFmR245ajZGWGEzeVorUUZxUmJnTnE4Qk5PWmp1MUFDUUN4LzhqZHk5L3hyNzcyNVhWZVZNaE1uVmcyQjFlU28rdVlIZ0RVYU1LTk9GSlpuL256RjkzZHo4am1kSjc0Nnd0NTltcjU1VSs0MzNEZHZQbld3YTJyMS9QSlpjMGU4WHBRNnR5SkVWVUdDQ0phZTNVMXRxWGZiR1ZaeTdiU3JXZ3RqVkVYelBFbU4zU3NLSTRwYXBqMjhEQjFkSmVvZXU0WldlTmpJaHhTK0ZBOEJpcHlxQ2M4djVhK2ZzNlhGS0xVWnBsd3h2NXNjdkhEU2ZscGtDaVNrVm15bS9PazhFQWw1dEZpOFZ2cjlBMGh5QW1DZXVkY1hkZFZkQ3hjOUZvanpaQmxFNU5rb1JOVm5jeU84bGxmRDF1OFcxK2N1UEVSRVMxV043Y2ttY3htaTk0NFZQSHQvb3pRUyt6VkN5dlNTSkprR0t2MHJsNmUzV3NMYy9US3lYREtMZmdIeGhlQlFHZjNYbHFkUC9qQ1IzL2VKbWtUbm9pZ04vZ2FyV2pUVmorclBXcDBlRHhzWHdWQURHUFNKTXZUeVR5ZExMTHBJcC9PYlRveEppSERCSWhvMkJzeEhrRW9RVXVzQ3RhS2RNUCt4TlMzMmIzTS9zVDQreXhyVWdmVlVCQWVUZGFmUG1UNTBka0paR1JaSng5NDlkTGYrY2pyL3ZYelYwKzJxU0dmSldwbmgyWit4UjVlVnJLa3ZwMnBKY3RhVi9uNlM3L3ZpWS85aWE4OGYvTEdJMGh1aWg0V2VsUkxJb0lRdUFzSmVnSy9jZy9XL242cEc2ZTF3RFdsbklDMmtCeDZhN05WMXZiUVFFK0t2VWJVQWo1MnRFVGNjS0MxdGs5R2k3THpLVkowRWIzYkQ2OTdrRUo2MTNIeTZLWDBWa3E1VjJsRm5GSGxwUDVjdnZ4aDQrNkIwZzRsaGxzQ2VrK0pLZ2hlNGFySlYxWFRiL0xKRWJRT3JtRHZuS3NMNTZyZTV6SENMTlY4RWdyaEtnQlJYUnhkQ2JzUW0wdmsvUFNPaUI4UmY4L0ZpS0E2UDc1bW8zcUNyaTR2VG05emMxckpMb1ozbmV2MDhGS1d4VEdTK3VMMFRweW9ITCsrNHpVY1hIUjYvK1h0K2NuekgzMlBzV2tVT1dnMXpyNEN5bWgxMFU5bU1MaU9pcFRZMmpSTHNrbWF6U2FMdzJ5eVNMS3B6WEpqTEJHSEdEM1V0NnNjalRqVTlvRUZsRkhEcjFudXMzdGc2cGVNdTIxa0NiOGsxQzFqdENIdWlmMlhvaWZGYUp6dGFoQXByRmZnMC9lT2Z2SkwxNy92WTA5OThNNUJWU08xZFRxZEpZZlh6ZXlJOGhsVVZSeENuSUFUcjNqbVFMNzFTZnllMXhkUExsektzcW03SFRYbytMZlRZdTNQdHY2azBGVWxSZUFMMUJUUEcxbGk2RC92b1lSOUJOYk5ML3d6aU1Yc0UzVEFEc1h1NjZzREQ0WmtqSGpNSXBDRUoxZVQxeDJhYXdyZjZjcmdoSDB4V2Yxd3N2MWdxTVVhQ2VWQkRuQ25tQ2lVdFJiS3krblhGN092STdMUU9uUVY4azVWM1k1UzFveG5PajNReGc4UEFoMWN1aEVYeUhWVnVicTRQN1o0YVRCQklsNGNYWTFkck1YbW9saWZoOERWWGc3ZTNDYzZ1dkpvUEp6TjZyemFYblFLNzdBK1dVZEJ3NzBQQVFpbmQxOHExdWVmLy9EUE5oR1lSaGoycTBYVWl1Rit6UUJTMGtCejBwQlFTQlpLOG13eXo2YUxOSjltMDRNMG45a2taZWF3ZFJVS1ZUK1ExVTFiRE5oRzEwVk4vcHo5MHJoWGpiOW5nb2JwbDJGYkU4RTJHWHJEVWdVSzdHeTIzVU43dzV1UnZGV2x4TUhVWldGKzdzVnJmKytqei96U3F6ZS9lRitzMFRSTGs4T3I5dkFxVHc1QWFNcVRnU3FIMHRQVlhML2lodnlCTjdsMzMzS0hxWmFDMG5lSjNTQXdzUWs1c1JzOTIvalR0VCt0WlN2d0FGRnppc3NBcXdhcjArVHdqRkd2cFZVMGs0N2xmVTg1ZXpqdW5wOTJPZmRJZm81NmovaXZRajM4NWVTeHk4bmpLWExYcEM0U3lDakkxbC9JbHo5cXF4ZkFlVGlVQWp0OEVMSENSaDVTZS92NDl1QmJuSDJhSUlBalloRnhybkIxMVoxNXBCMmNGRXdtbTg0N2xncml4ZEdWdUZKdVZXeFc1L2VIT2Q4RHlLZ3FtK1R3MHJYNDJNenQrcnhZTDZONlNQR3FvR1dFYXUxa2ZuaUorcENqcms3dmhvb2V3NzRlOXJlRjZjbmRsOHJ0eGZNZi9ObXdrVGxpdHhncXBScTZhV0wvQkNMREprbnpTWkpPc3RraW54Mm1rMWwzMXJ3aTBLZ01EekFMZkNDRUltMDdDc2QreGY0ZTEzZXR2OGZ1VlpZVDhsdEdoU2F2cHpuanJsKzFrWG9jVFhnSXN6M01NM285U093cVBDUTBVVFBqNUFaUHI2bDk3SVhWSS8vOEkrVS8vY0NEVDd4U0ZHV2Q1VW0yT0xhTEs3eTRUSWFEL3hZRUo2ZzhNZkQycS9vTmo5YS85NDN1bVVOTldMY09YcnI1aGtnT0ErUzFybld6Y21kcmVWRDZqU0FVa21ScUVnc2piWE1VWUVDN0lpTkZjUnlIMkhsM01QdnVyUmd3TkFZanhYMUZLczlPZ3dvQ1BKTzluanh6bUR6UytKWUFFQ2xsSkp0cy9RdnA5cGVNYkpTU0hwK2lSaU1CRjFha1ZpVDE1UFhsN0xkNmU0MmtPV1JCZk8ycXl2bWFoa3VzMERTZDJqUUx0MVhGMlBUZzB2VjRrTVg2b3RoY1JIWFRoajBDQ3VTVHhXUitHTS9zL09TdURzaXBBV0NVWlVLcWtrL25rL2x4OTd0MzFkbUQyNGI1SWRpSVdKeDJuK24wN2t0VnVmN2NCMzZhclIwdHBDb1F0bmlJZ0lpTVRkSXNTU2ZwWkQ2WkhhVFRSWkpOYkpxSGZBSUFLaUt0dklzSDNteHRKaHZtd0tqSnI0dy9JLyt5cWU4WWYycjhQWklWR2grbTFhYTZmcWNQN0ZlbU5STDVFZWNZa3RsQStVQnpQSWg2a0FKV2VPN3RWVFZYWFhMTDI1dHFGc0l6SWdQMXFmR1RCQ2NiK2RqTDIvLzUvU2YvNnljdVhyNjNOWmJ5eFdGNmZJMW5SeWFiTnZ0ZkFGVXFuVlpDMXlmNnRtdnlmM3FULy9wSEphVWF3S0RlYTlnNW9nUWloYStsV3Z1VHRaeHUvYWxYM3hiTDRzRk1ZN0xjdzNkRytpUWVxcGZ1ZWFZbGdRaWFZMzIxSjhKaHYwTnBxYXFBTE96VmErbnJFMHA5dDFtUldERXg3c1hKeFEvWStvc0VLMlJHV2xxM2p2MElWQWhPekxTY2ZuTTVmUmVSaFpUaGlDVG5uS3VLdG9wdVAvSWttOW9rYTBhcFlyUEo0dkJ5alAvbGRsa1hoWmVRcHhKNmIrcjh0ckR4ODZOcnNVa3A0aTlPWGgxdWExVU1OT3BHTEIxY3ZtNU1MM3VMN1hLN09uOVlLTHRUMlRxVVZDaWQzbjNKbFp2UGZ1aW5tMEpQNFpSN1ZXSzJ4cHBza21iVGJIYVlUYVpadmtnblUyTlRZZ01pcUZlRmR1cG9EOVZ3c2JKcDVxazEreFhMZmE3dnMzdlYrTnZzVGtnS2FEakxLWnptRTBndXNFTkVUcTFCbHZBK0hSMzdOTS91U1ZYMVVOZFVScVdabUFPZlBDTG1pazhlOC9hSzBxR1NBVUR3YU5MM09uaXBaY29UTWt5ZnYxZDkvNGZQZi9KVDV6LzMyVlZWMTFtZTVaZHUySVBMRkV6enRsdm5VWHBpTXYvT2paZi8wWGZmV20yMnJxNjBPZnR5RkZ1Z2tNNEhSYVhiVWxZci8yRGp6Mm90ZW1yY0s5QW96bTdlWWF3RC9qdWtLTVFrSFpOWisxaWZIVGlpMjcwYWJDUnZBU2dFTHVISkpmdllzWDBVSk5LRmxEaUYxTm42WjlMdHI3Q2NnN0xoZXNVcUVyb1FNRlFJZFcwZnErYmZVR2R2QzdXa2lhQ3F2cTdxdXBCZU8xVUNOY2VsTlBvYXBvdWorS1NrY0ltS05sbWR0WE8xaXZmT0tWUjlTTXJqb3lzM1JuSC96ZkswalRmMjEwZ1VzekVIeHpmaXBKbnQ2cXpjTE5HOTJBZG9CZzZWamlmUzZkMlhYRlY4K3YwL2JxeGxrNlQ1TEVrbjJXdyttUjhsMlRSSk0wN1N4cGZRa2lpaWxlemFWQmgwR1dGYVFyZkduUmozS3RldkdubkE3ajdMR25BRVZSaTBWQmN0UUJPTVI4K2ZSNFE5MGp4SE1PbjVQRlNoVlZPRXh1U2FYRlp6dWFLcnp0NlM1SWFhdWZJa1FJVGcwV2xRK3hHM1FZN1UwalExNjFKKzZmbjE5NzN2OU9jK3YzengvaWJMMHNucjNrYkpSTnZjWEdJRFNqZWYrNWwzSHB6ODNGLzliZ0RlTzFkdFhWMjd1aFR2Z0JEd1FOUUxBY1RFVVBJb0N0bXUvWU9ObkpaK0xmQlFFSmxHajRvbE9tSWROUlppUXlMcEYycGZ3R08vK1JkenVVN2JvTUdkQnJmUWtHdmJiRWd4blp0TE43STNKRFRSVUhvMGFJQ1VzYitiWC94d1VuMGFzR2lLeDNURGlTTW52V0JVclVCVTUrOHNaOS9zelRGcEJTZ1JpVWhkRitwcUw3NUp0MlNUVDJmUUprQ3ZvcFBGUVp6YXN2ZFNGZlhlaTRpdm5Lc0FuczZQWW01WkZxdk4rVWxyOVVXeWEyalU1ZE41UHV1MVdZV2UzMzlGVmRxc3NSSHZpMWttR2lJVThlZDNYOHltaXlTZkpWbkdaRU9ZV0JWTnpBQTZJSUVHck56a1lRSkF4WDdGN3NUNCsxeS9hUDA5OHVjc1MyZ05WV29pQjkxTUlpaU1IU2VESllrWSsyamNHRTVHb1JLcXpTb3M3RlRUUjJqeU9MTHc5MUZ3WHRkU2J0YXVXa09GYUZTdUJnMmt4bUFDWWxZUE1QRTBnUkpPMS9YZi9NWHp2L0hjQWMyUElXM2xKV0tVbStxRjl4VzNQL0cxYjM3eXAvL3FkOFV6RmZHdUx1cXk4SzcyM29XK1dpdWxuemlUQWVEVmxiSXFaTGx5RHdwZGVxMEJERE96STByQXJpa1k2NUFSeFE0bXRHOUhSVXlydmZpTnhPYm94WTc0bytlOStwUW54OG1qeDhtanBCd3l5eFVOR3VTYjk2VHJuMlFwRkZrWEVRc3NPRnJrZ1I1RXV2Vjh1WnErczV4K0hUaUhsTTBiNHF1eUVOL3MwckpKbHVXelR1OFE4YlBGcFYxNStPdTkxc3VUdXR5cWlvS28yUzZMU0R5R29jcGtjVG5MbzFKVXJscWUzbzF3dUhkcDd0cEp6ZEZveGxvb1JId2IveHN1QUJFMGJMRXpDZ01TVWtmK25QMDV1OXZHM2JmK052c1QrSXYySEV5RzJ2YXdseDZ6Vy9zdEhrV3p3S09odFpPTFVTbGVHS0Nwd0s4QXFjbVY1dDVlcGVucjdjSHJLTDhPZTJXWG5nQzR1cXJMVFZsdTFIc2dOdGhHTEtxN0l1N2NUdUVvMHp0YisrLzltK2tIN25CenhEUkFOcEhpb3Z6NGo4ajJiRlBoMjkvMTlBLy9oZS9ZdTZpcTZ1clNWVnRYVitFTTlHZ2thS0hCQ0lxNitrcTNXMyt4OFNkYnZhaWxrS1pvQ1ExOU9ZZ0lxUnZ2VUkxRXpKTGJyeU9hYkljWW9kYytmMC8zekVPdFRYV29GK2JxdGZSMU9TOVVwU1ZGSXM3WTNjOVcveVlwUGdES0FETU02dzgrdDRJWVVFZnFYZkpvZWZEYjZ2UU5wQTVhaFNRS1Y5VzFMOVY3UUpOMDBqbHBna1prYkdwTVlveGhreG9ic2xYNmFNU3Y1VkpWRmVlOUYvSGlhdTlyOFY3RmkwQWhyWEZBRURtNGV0TkVvWTdOOHJUY0xxay91em9La1BabzJkaUVMNkpId0ZnajZQYVNoeHo1bXZ5YTVTNjdlOWJkWVhlYjNRbnBoc1FSTlJwbWZIaGQxMkNjV2Q5bUM4U1lQWmp2enMySU1GUUIzNFRPS1JFejkrYUsycXUxZlZ6U1I0UVd5ak5pYXd3bmFaYmwrV3RrQTZ2NHVpckxZdVhxQ2lvMEVORWphVHpXSCthSi9OQVhrci93aTlrTFM1cTAvbDJ5cVh2MTQrVUw3MU5YYmd2NW1qZGUvL3YvdDI5NS9XUFg4WnFYS3J5cjZycHc1Y1o3VVFsOGdkc2ZHd1FuTWdIWm5kU2xucS84NmRxZE9DMDlISUo0N0toeElKU29qM0QwOEtZZXpjZVdDU0tQQ2RwMldvRHM5Y0cyejZGTGtCcVFOeFNlUUVmMjFyWDBTWUtSL2ppUUJFVEo5bjM1NnQrUW5BSFRVY1hPS0lRWVl4R1JWa0syU3Q5U3piL2VwNCtUTHdFWDV1bGNXWmRiRmNtbWMyUFNqcEJWVk1NbVkxQTRiWmVOWVpOYVk5Z1lzcWt4QmtRMFJzVmZaZW5FZXhIdlhSMytldTlWdkxYSjdMRGZZNkdxNjRzSGRWbkVuckl4V3d3RGpZNUc0OWFiRWhMNlMvWWJJeWRjMytINkZTdjMyVDJBcmxsclJleE42UVZVSUxEZ0MyN3hZUUJUOU1rVGcyR01oSFFuNHFIYW1KRmtoQktZUzg3Y0ZIdkZwYmZVWFBWMEFESWdVRGpwRW1IN0tRQmxZOUo4bXFUVCtIaXBNU0NodnFyTGN1bXFXbndWa2VKSXYycGxOZEZCcXYvbzArWlAvL1MwaHFZbUhMeE9iTkx5cFE5VVgvaGxJWFpPM3YzbUcvL29UMzNENFN5dG5MSk4wMnhpMCt4WDNlcmlmZTJxc2lxMzRweXFheUhTQVlvVXlxQkFrRjZxQ3NWYVR0YjFnMUxYclRlU1dqYUtnVnpxbGpxV2tydHljdGY5ZzlaREU1TVdkbVRnU0p1Tld5TUtoNlZrZlBCSStrek9oNG91L1lpVU11TnVwNXYzWkpzUGdFTnVCc0lLdGxwNk4vU0J3VUpTZURPcnBsOVg1ZThVZTB4U0JpNGdYdXE2RUZkbm9XQnBPOGVoQ2FjQkNZTytSZ0F6ZzQwMWxtM0NuSVM2R0wvdWNoaE5VUnBuazJ5RTNtY1BYZ2xxVnd2NE9LMjhaZUluOSs0Z2xEeEFSWDdGL3I3MUQxQy9iTjF0OHFkR0M5SlNRVkFDSndBTmVkV2dkbjNuWEl0R0VaTldkNmNmZTZRMmhXZUNmOUlEQkVvOXpjUmVsZVNtVDI0NWMxbjVrdktrV1VGMTNjbGtrYkxhOXhXbzNhWjVscytTTE4rUnV0RWd4QmZiVGJrOUg1ZklhTVc1QWducEpNSGYrWGp5WjM0Mm55UmltdGlmQVZDOThMN3lTeDl3c0s3eWYvMFBmT1dmK20xdjhpSzFhOG9jZ01Cc2pVM1NiR3JUUHV2M05RWlQxMlZkYnV1NlV0K0gzV0xMbVZwMVZPQ2RMOVo2dG5Zbkd6bjNvV29RMlVpcGVVZ3k2bDYrMXpsZGRhalpRc2VrT0ZCS2Q5bldtQXVJZWtObVlXNWN5NTQyTU5LNG9FbkpBQ1lwUGpKWi9TaTVCOFNUOXZCdGRHZ1dTOExCRXV0R3pMWHQ0bHZxN0swQVNFTjhpOFRYcnE2YXZmT05RQTVaZGJ0cEQ3SDkwOUlETTBETWhxMjFObUUyYkJOamJHUXYvUG91VjVkVnVWSHZWYnp6SGlLOXZ0Rm1HTkRGSys4MTlhdkczMlYzMTdnVDBwVnEyRE1XU3ByR0N2UkFNUnU1bU52NzhYTURPTGFmUnpQeENCWUVDV0NVSjk1ZTllYW0yQ3ZlM2hKektHWUJTaHBCMTV3K0dhQWR4RUtJa1VqUU5nWjRFMHBlcVRLRkV2VFRMSi9TdzJsZ2RYYlBSUVUxbWxhSW9KSWJxaFYvOUtleUgzcytTYXcyZVVXY2FMa3BQdm1qc3JwZktqOStQUGwvZk9mYi8vMnZmK1ppVzJ0Zjdhcm5DQUNJVFpKa0prMlRkQkliRDNzdkVSRmZWOFhHMVpYM1ZVU01zY3doQW9pTVFwMXN0N0xldVBzclBYZFNoTm9XRE9wVGkyTHJFVU94Tm1aaHV4WTZIazVzKzI3R0xWTXpmNFZrUEwyZXZuNXVMb242c0hrZkFQR0UzWjFzOWROSitWR29JR0lpUGNMMS9xWllWM0lRWDAvZVVVeSt5bWRQazFiaDRORTJpdzN4VXU1Y3ZXSk91OUJwRjQxSVFaWU5NWm1XTWxNMmxnenp1RWorcjM2Sjk2cmVPeGNNU3dsL1ZVaWUrME5RQjJqcmRPR2hQdEFyQS91MmlvM21PWDRsZml5Nkd3SURRaUJQT2N6Y20ycytlZHduTjd5NXJPWklrQkFJOE5RY1FCbXJoUVRBR0t2cVYrY1BqRW55K1dIWWxDemlCMUNOcjNDUEtNMG0rZlNBZHdsQWRYbDJ6N3NxSG4xQXJ0VElSY1YvL0tmekgvbThPY2hETFhDUVRYUjF2L2owVDduTmc2TENtMjRkZmYvM2ZPUHJIemxZYnV0bXFFTVU2bkZKQVNpeE1VbHFiSkpsVTJQdGE3TllWZlcrZHVXMnJtdnZxbUE2TnRUWERSTWdZZ2Fyd3FFc2RiTjJKMnQvVXNwYVNacVNoMENIeUR0R1hmUnhKTnp3R3ZybmNGMWlrN0tYbGdOZXJmQkVmR2dldVp3OGtYQVdGR2tGaUt4U1lzdFBUaTkrZ1AwcEtPMWFISHByZ0c1YlRvZCtXZ0sybVAybWF2cDFZdVlrVlZ2aHRnVSs5WW1FdS9jSFUraWxTL05rRkcyT2tJcVltUXduWkkxaHk3YmZNZmdhNjdqM0V2SGlmWE1XUlRPc2dlN1NTN09kWDBlRkREUjZlS1R2VW1NV3F5ZDRnUklsUWdzeGw3eTk3cE5iM2o2aTVrQjRHZ3JhTmRWdW9uMzNRNDJLMkZxb1g1M2VmL0RxODh1VE84eDJlbkM0T0hva1B6eWFUby9ZSnFwZU9oVThIcUVxa2NsbmkxMTVxTkR0NnJ6Y0xpTzl1akZ1RnludUZmUjdmbVR5b2JzMHRlMnltOHhmM0M0LythTytLb3ZDZmUyYmJ2eXJQL3Z1dzBtNmNiN2Q5ZHlCSmRBS0ViT0dnNHJRQmtSYkc5cmF4S2E1VFRJYjRyR3Z2V2JPMVhWUmxWdnhMaHpORzRTakFwRWF3RXdFWlNGWCtJdXRyTmJ1UWFGTEp4V0JtTGtOcHJYcjFUcjRodnVrZDJYSVhxa1M4NnVoV2RpU0RHTDBCUUhxMVdVOHU1NDl1ekNYcFhHY0VxQktHY25GZFBtdmJmRVJJZ1dTQVcyMGVXZVIxZE1CV1NDbEpEZTM4OTlTcDgrQ2szQXdSb1NjWTFIL2tOalk3dXdlT21YdENKc1E2cDB5TTlzazFGazAxakFudjhZaXFERVJkbGVzU2FEbFE3R1BKd0wyMkl6dVpoV3NPd0ZJZVNKOEpQYUdUNjU3ZTlQYjYwS3pzTGtUN2NsbmFIbm1Ub1F3RElYWnNIZnU0djRyNS9kZVdaL2ZoV3FnSlZFUFVXSXptUi9PajY1TUR5N25zNk0ybFo0UTlGV2lMSi9tMHlNMlk2QlV4YnJjcmx4ZHhvNlpzR3l6Ukgvc0Jmc1hmajc5L0pJblNhUGhrRW5MRjk1ZnYvUWg1OFU1K1RQZjl0Yi84SGU4WlphYjJra01ycTRwZ0ZUOWRuaytYUncxT3piRlMzU3VSaGZYTXNZbTZjUW02YS9Ga1NQaVhWWFVWZUZkS0dNSHFJS1pCdUtYUXpBRGtFSTNwYXlXL3Y3V1h6Z3RBRFJSUjBTWUhLOTh2K0J0Z3lPM3pZalp4b0tsM3hDSGh3bkpRSGlYazF1SHlhTXA1Ukx0aHdKWlUzNWhldkZENUY4bHBLMkRaQ1NqMnNYdFNaRkpLOEM3OUpudHdXLzM1aWExQjdiM29ONGo0c2JIUGZSajNsbkhtQ0NITHE5V1ZuY0NtMEFBc3lWbVk2d3hscE1rZk4wckxlTUszUEhFNG1FTnJQaVJpUnh4VHFGUWZVaWhuQ2pOeFY3enljM2FQcXIyaXBwTFFxbFNxSWdVemdxSzJXU2svMFI0b0FBVEV4dGZWNnZUTy9kZS9seXhQb2VDalduMldJV0RBd0RvLzRlMS80NjNMYm51QXZHMXFtcW5rMjk2cWJ0ZjUyN0phcWtsdFdRRksxa1NzZzFqSTR5TnNSa3dHRWV3amJFeDlwQis4Qm1DbVIralljQmdHQU0yR0FORzRDUmpLeUFyV0ZscWhjNzl1dnZsY1BNOWNhY0thLzdZcVhhNHI1K3MyZjArdDgvWlorOEtxMWI0MXFwVnEwaXJGSkd0bjc1bjgvYjdTcWNvRVRsZUwrZ1B1S2dTR1dTWDFqSmF6ZElvUkpZWkU3Ung3OWlEOXo0bmZ2VDMvWVNNbC9tdEFJRnhlZjR6OHNhWE5EbEU5SSsrNTlVLythMFByVmFwMURiK3NZbEp6UEd1blh2MDZNWWx2ei9zRFRlSDYxdHViK1Q2UGNhWU1TWmJoTXJaZ2dpeWJObUNPNDRuM0o1d25CZEhPRVF5end3VVp6bThJRnRDckNPWExBOGNFRWlJUXoyTDlDdzBzNVJXUkJxQXNUSmd0VDNINjFnOGJDQlNTNHlid0JXc29ocERqQUNnUWJub25YVHZING90QTJYOFkyWVNZMy8xUVMvOGRMWWZDcHJlQktwVDIyb1ZTVUl2NmI5VkJvOW9Oa1pLYk42dWxBdFlMVy8wNnhqT1J5UmV4RXBVWGFUczBLSXUvVnVzdVdjS0ViUERGeG5uM0JGQ0lPZWN1NHd4Wkt5MGhHMGMwZzBJcmZqQ0RHUm1kb3dUNjJtMnBzVUo3Wncxem1uREpvWVBDcCtLdHN4ZFRjM1dvR3hkSmpQN0x0Tmt1blBsY1B0Q0dvY0FVREFsV3RpSGpERkFhckIyNnNUWmwvUW5HMXFwREN3d0pvTEIyTFhpR0xMTEdCMUhxeVNhNS9PUitxOE1vZS9BTHowbC9zcEhmVTlRbGpFSU9RY2xrd3VmVWplZWlnMTNPZnVWSDN2enUxOXpkaEdsTjdGYVRJaWpHeGV2UHZjbHprVytvUVJRT0g0d0dBL1hUd2FETmE4LzRGbUM5eEtzNWpxVkFCQVpkeDFYZUlGd0hNWmY1Q0FVeWtJaGswaktSRXRGcEtHYXdkZm96SUFEa2pZcU52UEl6RmZtTUV1YUNBQ3NYSE5xcjh1WDQxYmRLVm11WEg2a0pqbWI2L2gxUGljQU1BYjBSTnkyNWQ0dDBLdjJZWUFBNUZ4ZThCY2ZGUEo1d3FBdU1SV2ZVUDByQUFKb01pbng5V2o0VGNwL21BQ1FWRlZyVy9YYmpiTkNkNnhmd1dVUWFUeGN3U3BseWdBZ3VJeDhCd0tIK2c2NGdoaWl5UTU0dFJWVFRqY29oN1hJUXBoM0Foa1hqSGVlUmRHMnYyVkJwdkNwZ0dFK3NhSGhwN1RZVXM1dFJwelVmQXpvQWpFQVhRaGV3NmFYK2dlNnFzZ1poVEVPakNXcnhlTHd4dUdOUzBtODRnd0JXYm5EdU1vY0JhUzA2Zy9YSjZmT3JwMDhpNGhhNnd5cEJyMkI0L2NibG9USXBIRVVoM090SmFzdERPWWM1akxTQkgvbG8vNnZQKzg0UEkrSEFPNUFkQmc5OFh1VXppUEZYbi92NWovODdsZTk1YVduWm1GNnpMd0NBUWc1RDJlSEZ4Ly9CREplRkordlVCbWpNKytNM3gvMlJwdkR0Wk51YitDNEFSbEZGbjhUWWFhNUVCbDNQVWU0d2dzS2QvbXhGd0dRVmpLTlpSb3BLY0ZvS3g0ZUxKb2pBa2RFVFVxWktLVFpTaDFFWnE0cEpRS0dkV21FdHJMS20xajkxT20vc1d0c0xKQlVOdzBCY2Vadk9XZlgrRzA2Vit1SUFKcDVqRkovK1QvZDhGTklNdHV0bjV1WFZpUllDWHl4WUEwRWxYb1BSY052SWJFQlJsV254ellkVk1lQnovd3ZaL0RjUHJzOFpjc0VUQmtRbXY5RWdVTWpENGVlT1RHa2tVOHV5MkZObGw2QmFtNmhwcVhOL3FzSlllVVBLVVNGUURNeVdZcExRRzV3WlBpYWRrNXA1NndXcDRrTkRlc0JNQUxEcWkwSUhkSlY5TmhlUnVnQWI1d0xBeFRPRHVjSDEyZTcxNlJNT01zU3lHTko5NUpOdFpiQzhiZHVmMkQ5OUoxY3VEcUxJU1J5dktBM25MU2prMlNhUk1zanJleU5NRmoybFFoY1JrdUZQL29SL3plZTUycys1WExESFQyOW5wejdzRXJESkRGLzVPRXovL0ZIM3pvSnhDb3Q5ckJtVEllQVZMRUM0enljSFZ4NTlndGF5YXpST1d4R2dDSXJJQmtEUU5sZVZlSDZYbTk0Mi8ydkxvLytzclVoZ2NuV1c0QXhJVnpoZXNMeEhOZTFndUM3TDZPVlZqSk5RNVdtcEJXQm5TRXpIeU1FUkVCQWptQVNFOGRtR1pxRGxUNlNKczQ0aERGbURacU4yUzI5WTg4aktuMWJsK0gyd2tiNUZRQklHNkF4UDNuQ3ZjOUJUNE85TmRGM2tuUGU4dU5DbmdOMHJFUk1RSVhaeXVZbHRod1czdG1VMEpYK0k5SHdYY1FDTUdrbHVSWHY1ZTBnS0EvU3E1NWdDTHRMOXJHTHd1UEVBV3JoS0FnRWtPWHZOVUNNNGNDaGpiNForVFFKcUM5SWNPUU1zRnlveEhMMUNpbXphVm5tRnh1T0VnQ0NBY2h5cldzQ0pPd1p2cWFjMHlTMk5MOWRPeWNNNndFNlFJQ2dpYkl6MG16YWQyemZid0JjcUQyWi81akJyV2h4dEhmdHVjWGhEbW5Gc2cxVGdOYVE1VkpvdEdMQ1dUOTVkbkx5VG4rd1puU2E0VS91ZXIzK1dEanQ2WjlLdzFVVUxSQUpnVFZEOWdFSVlPRFFOSUh2K0IvOVIzZXg1K1RqZ054VkJ4ZVRjeDlTU3FYU2ZQZWI3djNsSDM2ak5DQlZ3d0ZiYVZjQ1lvekxPSHp1aTc4UGtKK0ZZRnNBVzQrVWRrT3JkT1AyKzA3ZjlUS3F1dG9rVjhIYXVTVmczSEY4M3hGZXRvc2Fibm9abzQxU2FScXBOTkZLWnJ2cGNvOWE1cTNOV0FPeUF4SkFReHFiTU5UWk9rZEVvQUNBSWMvRnRzRys3VWxnK2JVOWI4eTdWd2V1eGJzR3RJUEJtblA3dW5NN0FGVXJydWdES1MvNnVMdjZDRGNKb1Y5bnR0SS9iT3Y2Y29nSlRLS2QwL0hnWGNwN2dFQWdwUlp0YkdQUU1VdDBPRDE2VFZ5ZFlwYTJxUGFnUFZRRWdHZ01HVUpDNEFpK0lGZVFMOEFUNUhJVWpEZ1M1OGlCQkFlUm9Wa0JMamVaSmRSZ2RIWlFKS0ZMYktENXBuSHUwTTVKelU4WXZrN01BMkQ1RExBMGQ5VXU0NXBOczIxNVd5YWh1b2taanlKalFEUS8zRDY4Y1NsY0hHZ3B1UkNGMzZVa2FRNUJpWlF4TUZvL3NYWDJ3ZjU0azR6T1RyQml5UHpCMkcrRnpCTlJFaTNpNVR6WCtTWGRiVzBOMkhmb2c1ZkYzLzZrOTh3TSs3a2psQUZqNmZsUEpOZWVVb1ljd2YvWjk3N3VlOTUwZDNiU0pOUkhvVzc1d1pDNS9NU25vOVVjTXh1Q05rZFdUU3Y3cUpWY1AzblhiUSsra2xTZXJNVkNUWFZvWk4wcHhaVnpJUnhQZUlIanVEZUpSaWdwSW1Xc2xaUnBvbVZpaFkrRGhWTXcyK2NKQUlaMFloYVJXU3oxWVd6bW1oVGtVOGVpMTVra04rWS9OYjBEVmQ4clRxbHpmS1hCa01BWU1BTytlZHE3ejhXK3prUGtEUUEzekhmU0YvekYrN204aFBtR3VMeGV5dmpScXJpdUxKQklJWkQwN2tvRzM2TEZXWUswMkJMUm1GVkNZL25ONGZURmErTHlVYVhvQ3BCWWRLMksxclR6ZTZJaG9FTGZGdkc3K1NQWlJsTEJ3SE5nczBkb0h2OCs0a1BOeHNZNXFaMDd0RGlqK1pqWUVGQVFFQ09kbjVwV0Uvc2F6OVdvMy9ySlJnajFOeGpqWE1zMG5CMGNYRCsvbXU4Yll6Z1g1ZGdVb2xmb2Z6SkVPaGl1cjUrNmUzTGlkZ1EwUmhFWlJPNzFCcTdmYjhlZ3lEU09Wek1sMDY1SlZEVkNZdy8rK3d2T1gvcXdseGp5T0JJUU1JSEdKT2QrUDkxL1RobXgwZmYrMVErOC90MnZ2MnUraURYWmMrWjJseG5qN09LVG4xa2ViRE1oR2xKbmZhVXlsWS9XOHVTZFg3ZDErLzBtMzM3V2dBNGQ0QUthRGNpSG1qSE9IZGYxZXR4eFh6UWlCN0lZQUpuS05GWXl5WTRUWXRaWjcwVVYyVnljRVppWWxvbFpMdFZoUkRPWjU2M2llRnhFRGtCZGlSUmwxdncwWFU4aUFvRW02ZkhldW5QSG1KOUc1SlNMSWhJNkJPaEhuL01Xdjhzb051Z1d3S1pUM1Zma3ltV0JGS0NUOXQ0VUQ5NUNFQURGOWM0MldCY1JnU05kT3VLUFh1V3VJRXNaWWwwSTY5cXlNZWEyd3JHZ1FFWXBwUUVYVno5aXhDbkRob0F1SVFOU0NBWXNud3ExbEJpMG01RXBsWUtzQmRPWEhTUm90QUdabHVuaVlQdHc1Mks4bWdFQTQ2SmFqb1VLbHVmd1UyckhjemR2ZjJEOTlEMWNNSzFONW04UXJ0c2JyTFdQVU5aYVJjdXBUQ01nYVBucnk0RUJqdEJ6NlZlZUVULzY0Y0IxUUtBaHlqWWxMZVFMbjVENzV5UEp6bXdFdi9GVDczajEzZXZ6U0JhN2NZL1ZPSWg4ZWJSejdia3ZHbU95eGZRTWptYmhacm5ZV2RNbm85WDY2YnR1ZStEVldxV3RZV3pvOG9yNmFOVm5zWGRHc3d5cU1zZHhIZGQzWEoveEY0bklBUUNqdFZLcFRDTWxFOUpWVUVIRDJtTysrQUdTNHBXZUpucStNdFBVckF3WUJneXl2RlcxU1NCMFdjVkMwZ0NzQjVvOUF5QUEwcUFHWXZPVWM3L0wrcVhqbEFBQmZhNzMzUEFQdk5XbmdMa0VkcnE5UEt5dzVXSXMveHFneFBCVFNmK05TZkQ2TEtOVXkwTlRVUjRCRGRGdlBPbjREcUpsLzFxYU1MOVpXeFcxSHJDa3NiNWRoUUFQOTNlQlRMWVAwRTVpVXh2NytpbEtaZUVGbU9IQzlWelhSODZObG1rY0dTVU5aVUhZYU9GVlN4UVozNy84N082VlowaWJMSWwvV1ZGaDZmUDJHaVdaNDZ4dG5WMC9mWmZmSHhzdHM0TTd1SENDd1VTNFhpTVlsY2lrY1JpdXBtQ29MbjdOYkRRdUp3TDg4WS81Ly8yYzRJd1lBeUJDNGV2WnRmaXAzeU9WeENuOHFUZmU5Yi8vNlZmZnZURllKc3BXOE1Va3FpUkdOU1prTkFISWVCVXVaekphaHN1cGpKWlpNb1djSUlpTU1TTFNTcTJkdk9PMisxK1ZSMWEyT0tibHgycjhhdmU4ZVljSUFBeGpuSEVoM01EMWdsczU3VDA3MzA0bGtVeGpvN001UDFqQmltWHgyVEdVekpDTXpEVFU4NVdaSm5wcFFBR0JGVlJaYjNQSmpnWE9xWUhIRHF1UlBXNDRPcHZ1M1dOK2lnSG1xZEVCQVFRaGQrSkhnK1VIbUQ1QzlBaFliYjlPcHNRcmVGa1hNSklJT3ZWZUZnL2VxWjNid0VoRVUxTVdGc3dXSEo3WjVVL3Zjc0VxWTlna0NUVEhzR0V0YmZHcmtubGtCTWcyOWRiMUVOV0xyMVZJQUFpR0NKQnhMb1RyOXh6SGIwUmphaVdWakdXYTZpeW5RMmJmRUdzVklhVnh1TmkvdnBnZFJJdEQwaHFRTWNZS0hReEdTME13V2p1MWRlZUQvZEVHR1cyeXhUUmtRWC9vQjhPMjB6eE53bWc1TlZvamxzcTI1RTZiVFZFd2loWCsyRWY5OTU3amt5QjNYakhoeVoxbjAvTWZsMUttcWZuQlAvTEFQLzF6WDA5RWlhSkNiemRzb01WZWxpck4xbVFCRVFHMWxqcE5aQktHeTFteW1vZkxxVTRUclZJeXBqL1p1T3VoYnlBRU1LYkpJdTJCTFdZZGxxeFIvY2ttaUMyUUtoQVI0NHd4N25xK2NBTXVuRnM1UlVoTEtWVWs0MWhMU2ZsS0NWb0dMYXVKWmJOSERTclZxNlUrakdrZTZia3lLU0V4NEFpbFo5dDJUWlVoMWhZRWhmck9xWnJsMUFZb1lPT1Q3djBCSHhuTENVL000MnJYV1gzYWp6NURDQUNPSlhVVnExbkVMR2tGQUFTVUFnc1MvNVZKLysyR2pSQ1MydVMySEZZRUR2anBLM3gvaVlWbkZrdEwwUmlvY2dTd1NKR05WUUw2cG56bUtEby9HcTA1NnRYWFNqa1UxT0hDOGJ6QThZSVhYVUUyV2l1WnBETFJhWnhsV0xGZDVNZ1lNRVpheDh2cGJPL2E0bWhIeHBFeG1uRm1OUFhIYTJzbjcxNDdlVHNCa0ZGWkw5eGc0QVdEdGxJM1drYkxlWnFFQUpCNWV1cS8xd3o3MEtXamhIM1g3L3FmMmVaOWtZWG1JaktVdStma0N4OVBGREZrUC9XL3ZPd2ZmdGVyNTNGcVRGczJTaHpRd0tKMlJxQUNEaFhHTHpNblNxVTZUZU53cWRKd2N1Sk94aWdUUUNKb3JXUTNCdGIrb1JuQVJRQWROdE95SjRCSUpnK1c4THhldjM3cyswMHZVa3BwbGNvMDFrbHNha21yYW9vak00QUVKRTIwTWtlaG5zVm1ucG9vSjI0V3NHb3YwTlVXNjFyUm9IbmxWVkFyZ1VZUVcrNWRhK0lPQkRDZ2l4STRnWERTSi8zRjd3cTFRK2puU2NPcXROOVlsQVV0Z0FvQW1rZ1MzNG9HNzVUK3F3RTFtdXBVaWZKNWhoU20rSUhuaE11ejVaQUNtSGFxeE5JVzE1VkJCOXlCREk3bVF0Z0dPVlhUc3orY080N25DODhYb3JsejhVVXZZMHgrN3BwTXNnd3I5cnlSY1E2QVdpYmg4bkM2Y3ptTnd2R0oyOWRQM2MyNTBGcGw3TTRkTnhpTUhjZHZseHl0WmlvSmpURmQwNytHOEVEZmdROWY1WC83ay83VFJ4ZzRCRVRJQkFBbDV6NHM5NTVQSko0Y0I3LzY0MjkrMDB0T3JXTFYybnRWeWdNeDVHN1FFOEluTWtvbFdpbGpKR2xkc0QzbVk1VzNJYWNyWnV6S0VBQ00wWmF5YUFpZS9RR2dLV0RRK3RwTXhkZGNpeXZrRWdHRzZ5Y2J5TVVZZlNzN0FMUldXcVpLSmpKWHFWazlkaCt6RzVqbE5aYVV4bVllNnRsS0g2YTAwbVFRa1JHcmJHUGV6aHF2TmJ0Z0dYOENJdEErRzUxdzd4bndUUU5aRkM0aEFMRUE5Vkd3L0pBYlB3NGtvWGt1VFVVSzJ6eGE4MFlGUk5KN1NkSjdrM0lmQkVvUlpJUCtEb2RMUi9pRnE4SmgwRUFrR2RLa3NwNkc5eDBxcTlnaGhGRGJXUTlVQndHWnFtZWNPNDduZUlIakJOaktHZjNWWG5uS3VpU1cyWUhKbVZIT2tDb0NaOXdBa2RMY2NUSlpKVVBNRVVGdjdIaEJXL0psRW9Xckk2TlV0cmV3WlRacWZNd1Erdzc5emdYeHd4LzJWaHA5Um9TQTNDR1pKcy84bmp5Nm5oajh1alBqWC9xUk56MXl6OFlpa3ZVMVcyc1VpUkRZY1AxRXd5QVRHYU9sMWxwSmFiVE1qekluWXpHVnZaK3JhbDdESjM0TXNMU1hraHVpZU15N1RWVk1BT0FHdzE0dHl5MFlvMjljZW9aejNodXNCWU9KNHpqd1ltQ1Z5RWlaNkRTV2FXSzBJbU9nOER6WmJTc3lVekZOTXFGRlpCWkxkWlRvbVFiRjBFcHkyM2FybGhpMTJWa0VJQU9hSVovd00ydk9IUjd6TmFuOEllU0Fya2hlOE9lL0x2UXVZZWt2cUF3TTVBSUMxdjJxVzBBeG9TK0QxOFhCYTBtY0pvaXdZaWNBQU03WlY2N2grVVB1c09KbUNTK3BsRVdxVldzVkQ2WE95ZlZEem1KNHRIdTErV0FaS0NFY3p3OGN0M2NUTlptbWtVb1NvNldUelRkZTFETmVWVVZLcFRLSnRVeVVTb21Bd0pTaFpBUUVaQUM1SC9UOS9oQmJHeWkxVE1MVlRLVVN3QlNqWHkwQVFFNmVvaVpBanVSeCtCdWY4di9ORTV3eEZHZ0FHQXJYekhmU0M1OVE4KzBvZ1ZmZnMvbnJQL1cyVTVOZW1NcjZPa1JSSGdFQmNTNzZ3dzN1dkhnd0oybXRWS3FWMUVvYkxZMVJaTE5hdFYrK0llb1ZSTFBsSjc5VDN4eGtLUnVzZDd4RG1KR3owZHFwaGk0NzJyOTJ0SE9aTVE0RWpIUEhDL3orcUQvY2NEMi9QZXR1OTFLbXFaSnhGZ09RbjNoVG0vem5YV1BBTXdpWG1NV041Tm5ZTEhQL1RhZjFLeWxleTQwRTFRTUltcFRIZXFmY2w1UmJoUE1HTVpmcDBGOTlTRVJmWkpRQ1ZwRWJEYzljaDQ3TFpKa1N3d1pSLzV2UzNxc0JPT2ErMC94SmhuQnh5aDY3TGhDQjVTYmtHRURUK0hvTWFnV29IRE1aZ3hFaU1pNWNMM0Q4SGo5K3ltZVVUT0tWbExHV2xkc1FrZkVzak1NTjJJdUZPTnBYZGdTUFRPSnMwMm8yR3E3Zjg0SitPL3lGeUVTclJSck5pUnBZNkRqd2hpNDNnUGpqSC9YK3l6T083MlFiY3drZFgrKy9FRC85SVUxR3BmVFgzLzJLbi82MmgzeUhKZEtVMEs3dTVzNU4xcWgrUlBPdFhsbGdnWlpHS2FYU3drNW1UUzJQdDYza3gxcjc2cFN1QWtyVmNFOUQ4ZFplTkZvTjEydEpwZ0hBYUhucHVTOWp2Z2NmaXRCV3c0UVFqaGYweDczQnhBdjZ0N0l2VGlzcDAxakpWTWtrTzNIRTZsUmxoUmd5UTJZM2ZmNVFYZVhvMURGekMydzNERmhwMHZLd0l3S0VkZWYyTlg2Ync0SmlEUU1Ca05EaDZtb3cvMDBocndCNGdKWHFzdWxaZWkvck53RkJnMUhLZnpBSjNpQzlseUtvSXEwZUFvQXI2UElSUHI3Tkk0bUNWYWRJUWpFT2JiOW85M3dpWnpMRW81MHIyWllJeGwzWDgxMHZZTWQ3ejhnWW1jWnBFdW8wTWZtdW1WclpCRUJrT09QSWhlUDZyaHR3eDZreng4MHVvN1ZLbzFRbW50ZHp2S0R4S3dFbDBUSUpWMGFyS25DL3JqQWJBMG1BUFVIVEJQL2FIL2ovOVp3WStnYUpnSEVtZkhubDBlVFNaOVBVRU1FLy9qT1AvTVFmZldrWUs1VkRpVGJOa01nZ3N1SDZscDN6L0E5OUVSbWpsZFphSzZsVmFyUXlXbE9lMndwcndMV0cxaHJ3RmNyQjc0ckx0VDBlSk54Z01ONjAyMkMwMnI3MHJFekRiSW12ZWhrem4ycnVzbmU4d0F0NnZjRzYzeC9jU3I1QW83Vk1JNW1tUm1XblJ5RFduRTRJQUF6NXJqeC9tRjRHQUFadHg3L1ZqbHAwRzlSUUhRQUFHVEFPZWlmZCs0YmlSSlo5R0NBVEpRZEkrcXVQZXN1UEFnTUFCNnlaYzMxOXY3M0hOek9KS1FHa3dldVN3ZHMwMjBTSW9kaDNKamdzRW5qaUJ0OWJvVFlnZU0zV1ZVSm82Y2JLb1FyUXFBb1A5NjU2YnVCNHdVMUNFSWxJNnpTTlk1bUVSc3NDUnpWdGJkbXhrbDZaWFhWY1gzaUJJMTU4c25HVFM4azBXazZ0WXg5dHhWaGlqTEk5K2Y4bUhueDVuMy8zNzNyWGxpeHdLbmVtdXY1WWV1bnpzYVROWWZBUHZ2dlYzLytPQjZhcnBCNVUwRkpmQ0lQUnBuQnJuaUdaUkl2WnZ1ZjNYYS9IaFBncTBIampJdEphR1MyVmxsb3FvNlhSaXFEWWRGNVA5SFNNYW0yQ1Q1c09RamlEeVZaRHQwNzNyeDN0WE9WY0VGUnJCTkFvQW9GTUZyeUJqdWM3UWIvWEh3WDl5YTJzT3BJeFNrbVZ4bEUwc3lMZHNoWXhSRHlTVi9lVGl3WjEzcnZHZ243ZWxMcDdwaHB3ZTlKb0ROQkVuRDdoM3N2UnNVMGlNRmVrRjl6VlI1ejRTV0E5U3l1QjFaNmNxcTBFTG9CQVNJbm02OUhnWGRwN3FVRWZLY2wrelhaaEhrVHMzQjdiV1RKdHlNbVZaOVdEdWdlcXZsQmgxWUZheVp1QUsyTjBtb1F5anZJa1NEazNWTVhZS3FUVnQ2d1hsT2s1TG9Sd1hOZnRjZmZGOHpnMExxM1NhRG1YTWlxWWtyWHNGVFM1azdEdm1qKzRMbjdvdy83MkNnT0hpQWk1Q3lxSm4vNkFtbDJOVW56NDdOcC8vK2x2dkhOanVJamJtNUlzSmlaaW5BMG1KeHN5SnRQNCtvVW5qRllBakhPV1FRbmgrbTdRZDcxQUNNOCtLKytydkVoclpaVFdLbFVxeWJKY1ptc01SWThaRnV4b0RVRnRHbGIyeFdnOTNEamwxSUU5R2JwMDdnc01XZG5kaGtQQk1zWEZTa0YyRWg2UUVLN1hIL2xCUCtoUEhLL3ByMjVmTW8zRDJhR2hZdlplNEcyT0lqYUxhL0VUa3BKQ3JXY00yN0xMamNBd3JGZzdONDhJaG96RHZIVnhkbDNjVG1BSVRPRUdjUWwwc1BxNHUvd29Ra1FZRkpDMmJuaWJ5c3ZDL0tEQVNPMmVqWWJmcXR5N2dCUVdTWjg1UnlDenUrSTM1bXg3Z1ZHS2tNbG5kbllnTk1xekI2QzZnNDNGcnZKU01vbkRoWmFwS1RJTDFRczVUcDFVaExOcmdWd1dEUUl5TG9UanVhN1BieUVGWU5WbUFxMVRtVVJhU1pVbW1kZlJNc2cxOGlIU3dNWDNYMkkvOENFLzBpQnlMU3pNOUdwNjhWTjZkUkFsK0UydnZPMWYvK0FiVG83OFNCcUV0aS9PS2haaE1ONXN6RTYxU3JjdlB5dVRoQ0ZDZ2QveWZnSWlCODQ5eDNVZHYrY0ZROGR4aGV2L0lSSUJGWDAzeHVqU3phT2tOR0FnTzU2dDZIRGVabnNTQmZrODMvSDdnOUY2bzhqckY1K1Z0ZnpRQUoyQzE4V2JCRVFtTytxREI0T1I3dytDUWVhK1BsYTNhcG1HR1pCaDJWRzhaWkZjUTNvdGZqdzBNNTRsbFdtRTBkVDYxWmpXV1MzS1FzclFFSm14T0hYQ3VVZXd3SUNzZGhlQ0w5THpmdmh4a1R3RjZBQlVpN29Xa3FxcWFNM0pFU2dsNExMM3hyai9Xc05PRm52MmlRQUVBd2F3VXJpM1pOZG1PRThnU2hFd1R5VmFXNjJFT2syekRod25oRnBybWF6U09OSXFMVWE1cHNacVROb2NMR3lOYktYWktFdGtnTUNRQ3pkd0hFZTR6WmlibTE5R0t5a1RtWVJhS2NxV0FRQXpweHdCTVFCWDROLzdyUGNMWHhFTUtaTkFKankxOTF6OC9FZEFLYW5aSDMvdDdmLzJoOS9FR1VobG9Pbi9zSlF0QVFBTkoxc05GQW9BTnk0OGxjYXJnb256N1lKMWZqVmtNb2tFQkNaY1Q3aWU2L2RkejNlOG5uRGRQMFRPdkx4NVJHUzBOa3FucWRiS0dKWFJvWVROdGJWTmh1TzEwdzNQU2h6T2R5NmZzeUVNNUYyMk5GQUJwVHFZcDRTTnVTT0htT0NPRi9TR1l6OFllc0dnVXhxSktGd2NwbkZZLzVVWWNrMXlPemszMS9zc2s0MnNlcHN6T3lKcDZrYlMrbXRBTzh4ZkYyZlh4QmtBc0JKSnVRRGtoNTkyVng5bU9nVG10c3hUVG8ydU5mMENubE5zV0QvcXYxTUdyeVVVU0xJa0drZmdERFJob25CdkJYdEx0b2h4bG9BMmlFQXNXN0hKbTFsYnlUaFdDRXZLSlVtWWhJdHNjMnBYYkUxN2NtS3JGdnRoKzZmaVZ1YlVabHc0dnVONnd2VnU0cEx0YUozUlVpWXFDYVZTUmlrRWNqaHlqai81OGVBL1BNbDhoekFMMStDdTNINGlldlpqa3BBejl2UGY5N28vLzlaNzQ5Um9ReVhYV1l2ZEZxNUc3QTNXR2dreWxFcjNyMStNbGtlY2liYjRsZjJ1TXdZQmdjbkQ0ak1uc3NzY3ovTjdudDhUWGlDRSs0ZTJrd0Jnak1vV1FyUk90VXBORmpOQWhnQjYvYkhmcngxT1JHU3VYM2hheXlqclpSc3JOYlUyNW1MYU5mWlVpYTdSbVM1MFBMODNIUHU5Y1dBZFZGUmU0V0lhUi9Oc1JjUmVNK0FvRHVUbG5lUWNvcWk4dmptYldtdUpOa2lzVTdtOEFSa3VBVE1RbXlmZCsxenNHMUtGSlVWaUFaZFhnOFh2aVBSQ2ZvNVl6VUxrK3NqbWg1ckpCUVRRU0ZLNjkwU0RQNmFkTXdnQUpLM2ZrUUd4N0s1aHl3UU9RcmEzeEpYRWVaSTUzSExEZ0ZXYnNwWFdtMTVFUnFaSkVpMjBUSWtJa1dYbTJScSt0cmxyUk0xQ3ZSdTFkOHZWTThZNEU2N3Jlc0wxT1hlNjlOU3hMVlJweWt4OHRJcC81Zys4Zi84a1d3dE1GZzFEcEpQblBxNFBubzhrdmZUMCtPLy82VmU5K3pWbjU2RTB4eXE4b2t5RTRXUkwxRE5FRVpudGk4L0UwWklYU1N1NjIyTjFEK3FsNXl4TmtKMHl4NENRY3k0Y3grdDVmdDl4ZmNjTEhNZTd4V3g1blpVYmJZeFJTcWFHVEs4L2F1ajc3VXZQUnN0cE9iK2xMa2FEd2hnU3RZVFFpbkJ1dm91WmxTWkNJbVBXdG01Yk8zRkh1MzFKdklxV016SW1ONzdGUklDRG1Pb2IyK216eHBoTVNpdDBhbzlVZzEyYmkzUVZnaldrSE9admlydkc0Z1NDMEtCeTlZZ2VBTGpKVi96WmJ6R0s3S3daRnQ0Rmk4a2JoQ25RS1hMbFB4UVB2a256OVNMZmFTMmlFQUVZUWhaR21TZzhpdUFvWmpzTG5NWm9ERGhsQ3BIWndYVXZHTG4relFCOWVjazBUTUpWZHZCbHNTWUxMUjZEMXBwTWM3MnJIV05WR09kQ2tURVUzT09PY04yZWNMdGhRL3Q2OXNoODUzOVpuSjlTejgzR2paRk8wM01mVHZZdkpGcTg0WUd0Ly9UamI3MWpvN2VJS3IxWWlWRTl6b3ZBOUVkYmJtdVpaT2ZxdVhDZTJjQ2EzWU42K0xIOVMvdHE2S0ZNQ1JreVNQbWdDY2QxM2NCeGZhL1hkOXhBT0U0N1hPRVBkMFhMMmZhbHB6bDNyVEhLMjJHTFZ2WXA4MXlVeHJEUkJiQUZ0WTZJc3NzWTdRL0dtNmZ2YnA4SW9tUzZuTzRYV1hyTEN4bnlwVDdZVFY1SUthd2ZkRk9YZ1Vhd1d5ME1GUm9JeElEcHM4a3A3eVVlNnhIcE1na0pNWS9KM1dENU8wNXlMa3MyWDNTM21JelU3RzhuRkRCQXFXR1RlUEJIcFA5eVloNmF0UDVBOVR4bUtYMlFsSWI5RmJ1K1pGZW1TTmt1ck1PZEt3REFCWFA5b2VmZjByS3NrbWtTenRJa1A0aWpRY2V5QlYzT1VyRHhST2Q2VjlYcWdpbTRjQndSQ005cm43WmhYZm96VjgxZitLM284bHoxWEFRaTRENGs4L2lwMzVYTGd5U21QL3FhTy83elQ3ekZZU3lXaEZXRzVrNGxDb2FvUDF6M2dyNWRBUkVkN1Y2WjdkL0lwcThOaTFHV1ZCUE9MaFFISlpQVTc5dEJ5M2x1ZjBKQUVFSXc1amhCSUJ6ZkQvcU82d3YzWm1kTzNlUWlZN1l2UFpNbVlRbWhhMlNvTzJOcUw3YTZDV0RMUkF1TkY0OXByUnd2T0hYbkE2SVY5S3VWV3MzMmk3T3ZMZGxIWVNpOWxqd1ptaU1rSnhmVVdvQmIzVEIyL0ZwY2hYQWEwaHpGbG52UGlKOWl5TXBEWFFrNUF2TGsyZDdpdDdpZVVwSDUyKzUwdlgrWm5OYW1JRVFhUVN0eEloNStxL2J1STFKSXpld25KVjJ5SFF5Y0FRUGFYdUtYcnZGVXN6eHNMZU40enJuVEczamV6ZUxVeWt2SkpJMURHYTgwWlFpWUhkL3VOaWkxQjlIK1RQYmRuRkpFQk1RQW1YQWQxK1dPSnh5L3NhVDUwWXZwbi9uMU1KUWdza1V2THRUdTgrbUZUNlhSRW9IL3pULzU4STkveTBzRlE2V3J0SXRsaldXOVdNaE5mN0xwdER3eHUxZWVDeGRIR1dWc05tM0NBTHVMUmM5cXo5c2plUHhWOG4wbWswUkVaQmd5eGpoM1BjZngzQ0R3L0FGM1BIRUxPV2F5YTM2MHMzLzlnc2h3ZmwyY3NHeFlsODV2ZEt2RE5OVDUzNzV2ak9iQ1BYSEhBMTVIN2trVkxXZHBISlpPdGVKRmhnRFgwNmRtY3B0ajRTTkFyTEZKSzBMZFVtTkZxM01rblJzMkF6cGc0MVBlL1Q0Ym1XS05nWkFoT0dobS92TDMzUEFMd0x6aWZLZ09TbENPRDZyMThPSURJcVdFVFBxdkNRZnZCRDZ5c3ZHRFpiMXI0TmJoc0x0a0gzdUJWMkZyZVRXR0dPZHUwUGVEd1lzbkxBSFFTcVpKbU1RcjY5aE5xK0lhSElVdWc5NjJSUTN3WFgzTVRRUWlaOXh4TTl2b2FoQS85NG5vUFo5T0NiWERNa1RqNk91UEpSYytsV3J3a1AzOEQ3emhlNy94Z2VVeWxzYTBwS1VtU2xramU4TTExNi9aUUFBNDNMazAyOXZtUWpTR3BkbmlsczZwcjZPMjZGTHZaYVBNemc4QXVZTW5Cd3FNdWE3dnVMNFQ5RjNYRjY3dnV0MHhuMFRtOHJOZlJqSmwrb0xPU2kxYm5ZZkZOZFlxaWwvckpkUjlsdVhUdVZRWkE0eXRiZDAyV2ovVmJsYTBtaWZoSEtyWkRVQys1WTMyMHl1SDhuSzJzcFZYVTBrZ0ZBQ2lqcVB0Wlgyd0FHbzJHS0FSK2FaejU0WTRDNENtek9rR0RpRjY4V1B1NnVOY1hRSU1BTEJPcERZYlUrRVBLZ2ZIQUtXYVRXVHZkVW52TFlRY1NUYU5UUzBnQVZ4T2oxNXJDR0dlYVllQWlITXV2SjRmREc1bDhjQVlrMFRMTkY2YTFnbTRkZ1J1M2Y0MFByZHhqZjBCNmlRZ0l1TE05RHp2cjM5Ni9LOGZqZjBzU3k4S1FDNnZmQ0c1OEtsWWliV1I5NTkvN0sxdmY5bXBlWlJtaDhhME1MQ2xYd0dNMGIzUmhoODBKWEM2ZCsxby94cEgzclRTSlR6cEZyOE9SZ2U3dDRYbHFSNXVHcWdLN2RkOElmWTBqQXhrN0VBRWpDRXkxd3VZNDNoKzMvVUR4K3NKN2dCaUVzNzNkNjdvSkM1U0Z4d2o1QmE5RzAxcTJMMEdjQzJGRHRvS0lPTTZJa05tY3VLMnRjM2IyalJKb2tVNG41Vk9pYUpZRk9qTTlNNzErR2xDZzFBL21iZ0l6U3IzQ0Z1Q0J6bTdsNFFEU3pqQkVHaWZyVys1ZHc3NHBnWlo3SllHd29DWnViZjhvQnMveGlDaElyUzFGTzZ5M0p5WGlJbzZyYjN5cEpDazlPNkordCtpdlR1QkRKSnMwc2t5cDV6VnR6TGxLNTZsdFRVR3VmRDh3QXVHdHlLS1dXcmRKRnhvbFFKbStVaW9NWW5PaWRVTnhleG53SHF5Q1FtQXdCZVFNUGRuUHhIODRoZlZXZzhRQUxpREtvNmUrWkNaWFl0U2VPZkxULzNjOTd6MjRiTnJpMWlXNzNZVVdQQXlFWGk5VVc5UTgrWUR3R0s2djMvOVBPUGNGcGlPUmlNaFdmemROQ0NVcWFiS2dOVDdXbzlkdENUTk10WEhDR3JsTlNsREU3UGNJb2pBdU1PRnd3RFROQUl5eUhnMi9qVmExQldIM1RxYmFoYXRhb29BV2s4MldscStTMEJhNmZIbXFZMlRkMExyVWpLSjVvZEtxL3JlYUdEQUk3TzRGajh1S1daTUZCaXdyTksydjViZHE2eE5YUWlMdjRZa2d0aHc3dGh3N3N4aXl2TWVJQ2QwaGJ6c3ozNUx5RXZFdkVhRVpybkx1RzBZTFpNREFKTEFVZTZEMGVpYmlXMUFiYzkrVFFnWkFCN3RYYzI0RUZtV2pDRExLbCtKRFpGaFRBalg5L3ZEVzFuRU0wUnB2Skx4U3NrVWMrcVVib0Q4LyszV04rMUk3VmVBT29PT1hYcHUxZnVlMzNHZVBkQTlOMHQ1d1NsZEpzOThNSjNkU0NUL3JqZmU4d3MvOElhZXk4TlV0ZUxSMnZVQ0dlcU4xNzBXQ2ozYXV6YmR2Y280YjBwSXE5ZTJXYk1mczNBWk5qdlVjdHZZWFcvN1NHN3ljRmxVUTdRS3NTVEFLa21WdFVvTVVGb3VxdTVBVGJDclNqcXRZZ1BHVkUwdEd0QWduVEU2NkE4M3o5elQzaDlqdEZyT0QzVVdIbXdCVG9hWW1uZ25mVzZwRHhod3E5RXRROWU4WDk5N1VWRXNsMUpEcWlmV052alpnYk5wckpNYUFEMmsxRjkrd0lrZVpSUURlcTN3ek9aUVVNVUNCZlFqdzBCcU5rNzZiMHQ2YndEQU11NjBnZmp3Y1BjS1ErVENYYzcyOTY0OHUzbm12dDU0QXpGYmVxMUdnWWlBb2U4TjNLREh4YTFzSTZBMGlaSndxZElZRVZ2Smd1ekJiUmlvdHFCVzVETUVBd2UrUEIvOHhkL0ZGNDUwMzBNQVFPSFJZamQrK3YweVhDU1Nmdmlidis1Zi9vV3ZYeVZLWnF2SDNlSmRxVXhERlBSSDdaWGw1V3gvOTlwNXdXcmJoWnNxd1dweFUwaXNSYlphUHhyM1c2YW1zazdITUgzVENuVVcxWVlRdFNaMHQ3bDlzL1A1WTlWQnZmc2RjQWNCQ0xSUmpoZWN2T09CdHZjck8zWlNwVW1Sb3lSbkNZYmNBTzBrejg3MFRtNmFta0VtZGEzZVVMaHRwc3M2UVVDZ0NkaUd1SDNEUGN2QktXYUpTQURBUENGM3ZNV3ZPL0pTbHZDNlZtWWxqVFh6V0hVbHI4Y0FTZW5lbS9hL1FYb1BBUmdyOVhDaE1LZDcxNGpnYVB2QzdwVm50SlNBYkxDMmRlS09CL3JqclNKb3VJRit3UEVDdnplOFJWR1VTWnpHWVpLRW1Tck5LTmcxVWJTYlZkS3BObjlEb25IUCtmaHU3OCsrajJheGNRUWdJUWhIWGYxeWN2bUxVUnhPZXY2Ly9QN1h2L3MxWnhOWmJHbHBrdCsrZ3dCa2lQemVvRGRZYTdRN1dzNnVYM3BHY05HMjBaMzg5NkxjM0g2bWJBalZOVHQyUG15UnA0bjJYa3c3TlB2ZFZXYnpsVTRSc2t4YzUrc045VUcxSnkzcmdVREdNT0djdU9QK052b0FvamhhUnF0cEdkSmQ5SUp4NUx2eWhiM2tBczl3YVhOWnRpdnRRQjJhSWtLVktNOTZWNU1PeFBDVTgwQ1BqMHh4a0FRZ0VnZ0U3YTArNmEzZWp3VFpzcjVGRElDMnQ2Wm1mNHNuS1FWa3FmL2FKSGlOZHU1RVNJcGRVUUFBZUhEand0Vm52N2c0MnViY0JRUkUwRW96enRkUDNibDIrbTYvTjh4UzM5bkRBRVRJbU9QNnJ0OXZLN1BPUzZaSkhDMTBtbEFWU2w4TVNOTm5VeWtZNnhsQW9MN3YvT012RDk3ekdhMUpPd3lBTVdSQ1h2aGNldTNSUk5PWmNmK1hmL1JOYjMvWjZka3FyY2ZpMWFsbXIrUVNCTU94Rnd3YTZHSTFQempZdmt5a2tiRE5kbFRqNXlKdTY1aUZoMk5rbzNxODlyVzFsZUVtYjBIejE2cWlybmU3YmJWdGFUdWEycktBaldjNlpMTDR1Y09uMm1KTVFKeHNuaGx2bkc1M0o0MVg0ZUtJZ0JEcVUwUVVDN1czazU2VGxMQjhMYUVZVmlqa3lsNU9yQ3NDNjlleVpUbUl6Wks0amNYcGs5NTlBb1RLRDQxSEFFYk1FZWxGZC9VSGJ2d1ZZTDUxQkFnVkpUWDNmMWZWRlRjSmlGRmlNRWlETjBXRHR3Snp3YVFJQkVEOE85LzFtbWc1NVk2Yis0Z3pod3JpYW40dzI3MmlsT3dOSmtLNEpmMFI4blVJcmFSTUlpMGxJTHpvbWMrY0M5ZlA5dW1TMGFySUdaNEQvL2FibFVzYUVJQUVVci9uLzgzUDlOL3ptVlF3SXhnaWQ1Qk1ldUZUeWVVdlJBbmNmM3J5dnIvK2psZmV0YllJWmEwOHhEYnY1YUpPeHUrUGc5Nm9JWUhSY3JwOStWeW1lc3RHdEpwWE5yRzJKdFA1dEgyZjhpNDFncXhyN25tN1VydnBqUTNVdG9tamtsTEZCNENhK0VEcmZzMm8ydTBvTHJKYWJzdGVCeWtibndxK3Q1OXZ2SnZMQjBHNG1oS1pvQlZieDRYTGhTT1R1RGhLS1g4TGdIdys2UFAxbFQ1U1VCenQycmxNYjdjeDArMW9qMWo5SzFHV0VTODJpNFhlQldBOXZsYUcxaUpwWXVzeWVJaFlYOGlMQ0FtQXFLTGVDYkNXWDdjMEpCVXQ4djZpUURCQ25oUHBSUUp1M0RNRUhFSHp2L0Nudm9WeFhxMURGaU9WZFc4MTNWc2MzbUJjT0wyQmRaWVlBQ0F5QkVDdFU1bEdLazA0NDB6Y3pJT0tBSXh4MXdzY0x5Q2dET3RhTzNTckR0Z2Jjb0RJRndqZTRLYy80Zi96ejZZRGp4aERGSzZKWi9FVDc1T0hsMUxOLytJN0gvemx2L1NtMjllREtOVmRhMkNXM1NydUdESitiOVJJZVFRQVNiVGN2bktPSU4rTGdKYlZKcnR4WmFQcnpKV2JiSXZQRzdKa2U0ckxkNDVsNitKclRSU2J4cWttZnJhSTJiUGhtMURrT0NGc2FaZHFoT3cwY3VYVmxPZlNUaGEvRjFyTEtod0JrVVdMcVRHeVBTUGd3bkg5bmxLeVNLUlFOdHNJNW83RUtVMHFORWNNZUVkYmEzVkFjd0paRzdhYWVVUmttdVJTSDZTdzZ1TWF6OCtUQmdDTlNNcTVWN2wzTTBxWnVvckk4aU1BYWhZd1I2ZDFLMkk3UkJCUU1IWG9KbzhKdlVmaXBPYnIvUHUvNTl0c01qY1VOREt1VXprN3VMN1l2ekdZbk9DT1d6ZXltU3lpMFVxbWtVb2pZcGd0VE1IeFZ5YUtyaDhnTUtObEhhQmlmU2hoNUxITGN2UnQvNTE5OEFVMThoRUFrQXV6M0l1ZmVyOWNIaVVLLzdkM3YrSWYvNWxISE1FU2Fab0NVeHNWclA2UmNieGV2N25GTGg4THZ6Y1FqaXU0WTR6U1JnTnBLdHh1dHBicVpPdk03bUloQm5aTDJteEM5VHZVN256R3hwYU9CZnR6NjkxTWo5cjZ2UktiU3JsYXI3UkdxZmlwUEFNRXdGNkZMLzQyM3JNVlFhT25WTFVFMjYva3BUR2VSS3RvTlExNm84WktHRExtZW9IU3lpalpPTlFOZ1kvRUZvRU96YnpJZVk5Vi95c1NZTkVJck4yMG5jTFZaRElYQVFTV21NWFM3Qk9ZZ0U5S3hZdWdERitUL3NQQVIwSmRZU1lFeGd1dXFEcFlTS1BOaE5WUUVDQWlCeFJNWFhlU3J4Z1VoUkFXTUxPVzM3MXdFeU93NGZySjBjYnBZdjhFMWxrbzkzMXFyV1VheXpSaGpPR0xKWHBDWkk3cnUxNlBDYTZWeXJhTmwxbndFUUNJZWc0K0hrNis1N2ZvMlgzZDh3QUptWEROOUhyeTlPK2xjY2c1LzBmZi9jamYvczZIVjRrMnVoYW5haXVTK2lRT3lSaC9NT3dQT3lRUUFCampqaHNFL1hGL3ZORWZiL1I2SStIMWtDTVprNkhvT3F2WGk3WjcxL3JRK0h4ck56czFmUE5KMi9CbXQ4b1AxTlVTdElUV0xzZm15c2J6MlBWNnV5VU4rNEpXUVprVTFORzFWUlN5N1BUSXRzTVBFVDB2eUpLN3NocHVJZ0FZOEEwR0dPbFpFVlhUMW8wTkFTa0lsQ25XZkFvR3pXZXl0T0trRm5vL05TdFBqQnp3Q2hlQVFkREt2Vk42TCtWcWwra2pvQ3poYmRuZHFobG90d0ZLVlZGcU5ZRmtuUFJKL1BUN2Z0RnFIR1VPSkNpU3JodGp1SEJ1ZStCVncvV1RwRTBXeHRtWTdaYXY1LzR3QWdCaTNQRjdBOWZ2M2NybURDS1R4SGtHcDRJRU5PbjduejNvZi9kdnlNT0lIRzRBR2FLUWx6K2JYdjF5bU9oN1Q0MyswNCsvNVpWM2JxeGloV1c2WkVzb3FFYUNxaWJINy9WSHQ1NS9Pciswa2ttOFNwTW9XczFWRW1tVmtxRXNZak5UVE5CYUo4d3JiS3VCbkZwZEpJVGM4dHhNZTdYbS90MGxOL3BkL0ZKOTdtcEE0N0cydkhWWTczSkpFM01VMzM3eEprU29IRkZFeGlpdk56cDE1NE9JdkszQTB5UU01MGZ0RmpFVXNaNWZUWjVRbENJd0tGZ3dyd0NoOHRCQWhsTktOclZJVUMxTFF2VXVFQUFhVW9LNVcrNTlJN0dGeFBJRGN3RUJCUUU2eVZQQjRyZVpuaEowN2k2b1RSMWFZVzVGVFovOHpWL0lqK08wWVRjUkFHa2xlNk90Mng5OGxSc01xTWk0WExhdlR0TWF3eGRyeElZTDErc05YQys0bFowNFJLVFNNRm90MGNpZzU3M255LzEvOGltWmF1TndBTVlRTUgzK1kzTDNtVWpDYSsvWi9QZC82UzMzbng0c0ltbVJzaHIwT3Eva0R4aWkzbURpOTRZdjJwS2JYMXBKbWNSeE5FK2lWUnF0ak5hR05DSUNNSVoxT3BSeHZsWjd5bCthM2JjRm95VkMzVkxYNlUydEY5SnBxMHZUMmZCaHRsY3ZHMzhiVGNObUZkVGloS1lNTjdwTkFKbnZYYmplY0hKaU9ObThTV3lXbEhFNFB6RGFGSnE5MUNjc3BmQkcvSFJJTTU0djZOVjMzQmZ0QllBcXlpMXJwWTFGbTEwdlNBekdBUFhZNUtUM1FNQUdHcXJVNllRZW1tbC8vanM4ZmdvUkFVV0wwcll0N2xSSGhDOTg1ZVA3MTU2WFNaSjdPRE9KSlFMQXRSTzNuN3o3WlV5SUxEaTdQZ1JZVnNBWWgyd3ZaMUVaNWRBakl6RnhMdnpld1BVSHQ3WUJoMlpSK245K1RyL25VNm5uRUFOQUxrakd5UXVmVUR2UFJwcS80K1ZuZnZYSDNqd01uRVRxbGsyMmRYMTFNd3ZCYy9yRFFXdjJEMkNJOE5ZYTFuRXBtU3FWUk10WkdxMWttaWlWa0RaWWVKamJERmV3Zm1PSm90YjBidkhvK2d2UXdkYk42dXBmRytMVUV0UXFzcWV6QVhCY200ODM0TlNxcUt6T0dBTmdITDgzV2o4MUdHM2N5alk2bzlQbDdFaXJ0QkVFeTVBclNxL0hUNi9NRVN1WEVFb0I2elNQcGZXck9WZHJTcWFxQVlsSUk0aVQzbjBqZm9JaHo4OGRBd0JraE15Snp3ZkwzMlJxRDlCZXRDUDdmM1dqVlhFcHpnOTM0dFgwNFBxRjZkNVZVZ281SjJPUTh6c2VlUFY0NjNhbHBMMnFhRGN4QzRWQ3psZEgrOElQdkdCZ2xMSXJ6cWhBbEhHZDRjSnozTUR2OVcrK09XTTNwTy81OWRVZlhFeUhQa013NlBobXRoTS84N3N5Q2hIWTMvdXVWLy9ZTjcrVXlFaEZuZHpiNkhsR0pHTzA1L2ZicDZBUW1kbmhMa1BpVHVDNEh1ZmlSZGRhanIvSWFKMG1ZUkpGU1RSUGswaW5hYWFZT0xLU3RScytqOHBjdEQ1MzlLZWhQMXVtcjRZSDhrSXNhVytMUVhISEtxUFZlZXVaVmdQSTBybXRWalVLS1ppOFVONWt5SGhCZnpCZUgweE9mRldwUFFnb21rK2phTUhyUW92QUVlRkFYdDZSTDNEZ0JTbExZMkhKSkxTMkJkY0tzcHRyN2EwSHlFeGl3SVludlFjQ05pcVBUQVJBUXBmUjBsOTh5QTAvQmN3QmNHcWtLa2MvRi85YUlpazgycjNDbUdDY0xXY0h1NWVlWGg3dCtvUFI3USs4SmhpTXROSlFiNmtkaDhtRm8yUjY0L3pqMDcwcmpodHMzWEgvMm9tenlFUnhWRWhEblVCK21pem5YakR3L0VGTDdSRUFYWm5UdC8vWDFkTjdxdWNnSVFEMzZQQkNmTzRqVVJpNnd2bVhQL0Q2UC9mbWUxYXgxbGxlTXdCc3hTdlltcG9BRUpHTWNmMStiN2pXc0V4RXRKenZxU1ROdTRhQXdMZ1EzUEVjeDJQQ2FZU01mbFdYMFRwTlZta1NoYXU1aWxkS3BnVEVpQUZDTlVtMnNWODVwYXdCd2hxWEZ6MnF1U3M3VFdLSjB0clJiWTNIT29XcTFZeDZkUTBNWE5yUVk4U3Z1ZCtDU0J2bCtiM3g1cG4rYU9ORk1VZ2pGMmg1UmVFOENSZDFxRWtBeUVCTTliV2Q1Rm1UelE0NklyeXR2alVXR0J1U2FRUGE4a2xFQXNPQWJiaDNidkE3QWFrd2lRUW9DSVViZmRrTlArbklpNFIrM2VpMVUxUVhGQzEzVVRET2phSFozclhCMnBad2ZES3FPYjVZU1NManp1TGcrczZsWitMbGxIRkJoZ2lNM3h0djNIYlA1TVFkZ0VoYTF5c3JTVW9BeUJoei9MN2ZHeGE3VVlrQVBuYzkvZUgzeGMvdTY3NlgrWWtkdGZkYzh2eEg0MFNlV1IvOFg5Lzc5ZC94aHJQVHBhd0RETFE0dFpGRU9TdlhlUDZnMTdVYXNad2RxQ1NFNmlDblVsRVJBQ0hqbkhQaCtNTHh1SEQrTUhudml5dkxEWmVFaXlSY3BkRlNhVm1lVFYyc05YZGd1UTRJV2dZSVF6NGExdGcwcE12S3Y5UXdtT1Y1eU9WYnRoRStwc3lhSld4UFJPMkdIdDhGSWswRVhtOHdtcHpzalNZdmF2MjBUS053YnBUcWo5YzdZeVJsbXF4bSs5WVJhTGxlWnNnV2FuODNmVjVTeEZBMHc2ZHEwQlNLblZBMkZPZ1FQd3NCNXZjMTZBRmJXM2Z1SElnMVU2RkZJdlNSWW4vNVFTLzhESUFCZEJ0N1N1dDVhd0NBN1AyRUJJaWNDNk5WT2UyRTB1ZFpOQitaUURDN1Y1N2J1L3dzQUdDMTBBK1o0UFVuV3lmUFBoZ00xd0hSYUYwcm9DclRBQUZqd3ZNRDRmV0U0L3pTbDVNZi83MllNK01LQkdRQW1ENzNNYm43ZEJqU20xNTI2dGQrOG0xYlEzOVJPN0M2Z1RrYlVEdVRKbks4WUREZWFPaG9Jb3FYUjNHMGFpVHpMZ2hVY3F2SmhnTVk1MEk0anNlNEt4enhvcWN5M3VReVdpbVpST0V5RHVjcWlaVk1NMldId0lCQjIvQjJXSjVLWmRUWS9qaHY1N0dHcGpTTUxWUFdEUzh0UTlxTlBPMWRqcGEreU9NRGliamZuMnljN0kvV1g5UmhycFdNbzRXTUkyTU1ZMGlJdy9GR08wRUdBQ2dsVjdQOWNqVy9iSmhBa1ZKeUxYa2kwck44TmIvV2RJdFMrVTgybHhaL3E3ZHNVMWxaUlFNYWdXKzRkMnlJdXhpaVpSSTVvZVBFVDNpcmo0bjBJakNQb0R6UHJPUmJMTkFITklXdzdkdTNnNjA1RnpLTmJ6ei8ySFQvbXNnVG9WZDhueFZxak9hTTl5Y25UcHg5MEIrTWliS3NZalgxbmJlQURFZm9qNFkvL3hYLzczd2s0Z3c0QW5BSG9tbDY4ZFBwL3NWRTBwOTh3OTMvNHZ0ZVB3cEVuR3E3THF0bVM2K1UrOEdCeUpCdzNYYnVkd0Jhemc5bG5sTEJ4clFOTHE2REVpS0EzTlhDaGNzZHB4M2g4ZFZlaHJTSzR6aGF4TkZDSnJGTVl6S0VXVFdZTFg2VWxLclpITnQ4NVYzcW1wNkJMV1hsYUpZTzJ3TElOMlFNd0txeG9tZFprV1dUNjJTejM2MXVHbTBBL041Z3RIRzZOeGpmZ3ZpbGNiaVFTWnlkL0ZFS0FBRUV3Y2dmRE50YXhXZ1pMV2RKRXJPcU54a1RNMEo5STNsbXBuWUVXRGt5b0JRcWk0Z2xjSzFzWUlPVVhmNGJSQ0RRSUFkOGZkMDVPMlFiR2lvQVNNeERvN3pWaDczd1UwZ1NzSDZRWGw0d0FlUkhvOWx0d3ZaWEFrQkVKdHpENnkvc1hIeEd5NFFMWHZsNWkwZHM2d1RHTU1iV1Q5ODFPWFdYNC9wZEFJZGN3YUUzK1NlZjUvL0hINng4QnhFSnVVZno3ZWlaRDhod0Flajh1YmZjKzgvKy9OZHJBcVYxRFVoa2pGQW9rcFlHQnlKdy9WN0hQQkFvV3N5U2FHR2xpbXRUeHByOTJqQW4vMnNBV0crMDNrN0U5clZjUkNhSnd6UmNwa21ZUkNzbEU2TU5NR0xBd0Q0VUVrdWV0RGpPbHMwV3NpMWx3emFuMEdBd2l3bkJmdDVLSFZGcmJaZk50SDRsQkZSR00wUTM2QThuSjRhVEpoaHBYMXJKSkY2bTBkTGtvWmhOaHNtZ2JHOHc3aXdxWEJ3bEZiVEoyOFdBRWNCTytzSk1YYmN3WWNHbzFqU3YrY0dpUkVXd2l2M0t4L0tXR05SQXNPNmMzWFRPTW5BSVZJbFdBSDBoWHdqbXY4blZOcUFMd0ZxaDNsUUtZYVVoTzRsTVpMYlBQMzY0YzRrekFZV0txbVVXQU1nem14WmhxRVlwWk96Mmw3eDJ1SGFpTHFWb2pPNzU3bEtzLzVuZlREOTZJUjE2aUF4UmVPYndRdlQwQjZNNEdRYitMLzdRRzcvamRXZFhzZGJHV01QY09Ib2h1MlBEU0NBaTRYaUR5VmJiRTdPYUg4Z2s2a29hWHl1NXh0YVdac2tNd0dDeTBUNHdPSW5DTkY0NHJzK0Z3NyttVXlqQUdDMlRLSWxXU2J4SzQ1Vk9FMDBhQ1lwbGo1WmRxdjIxRXNQWXhpM3ZZdGRDUXYweDI0bzIzRUpRNTR6eVh1NzZMZXloTVlveDd2VUdvN1ZUdmVIa1JmdXJsVXlpUlJwSHJRQkdJS2piWVRLZTMrK04xanJsTUFvWDhmS29DRjZwMnNaQXpNenU5ZWdKUkFTMGNtU0FoYVJzbHd5MFRHV2xvdXFyL3dRVmhZZ01hSjhOVDNyM0Iyd00rUkVzbVVsMlFjZHU4bmwvOFNHa0JOQURzR0J0Tmllc1cxL29HRmxBTXJSNzlkbWo3UXRhU2k2YzFuUTJheW9XN1NkanROY2IzdkhBSThGZ3JMVXFnUzRCQWRGa0dGeFB4OS81MzVQSGQ2VHZBZ0FpWS9MYVYrU1ZMNjdDNURYM2JQM2MvL3JJMjE1eWNwRW9TL01YTGFsR3ZSVENTclVZb3gzWEc2NmRhSS9UYW5Fb294Q3c1TVc2cDZvR3hLSGtBSXV0Q1lqOC9qam9OMU5nR0dQbUI5dFVKTHBqWERBdUhOZGozQkhPMTVSYW00eE80a2ltVWJ5YXl6U1NTWndmQzRQWkVSZ05PMjhOWGpIVHFWM0h5SnY5TGtETlZWdHpMaGFmUzB4cm0yZ2dNRVlEb3Q4YlRFN2M0UWVERisyZFVtbWFoR200TEpOS0ZVaWtYRnRvbWdRaXc0WGJINjEzSGd1VnhtRzBuQmxTZVdoMTBTMkdQTlRUYS9IakNsSUdsbyt0WnZHTFdtd1J0ZUdndmNMUk5xYzVrMmdBSEl1VEo1ejdHSE9JVlBFa0krYng5SkliZmRxUEhpVjBpRmpWU1F1T2dpM1hMUUZqVElob2ZyQi85Zno4OEFacGpaeVh2UURJdlV6WmtvQXhlcmgrK3ZZSFhzV0ZhNHl5ZFRJWk14NlB2akliL3REN29zZDM1TkFuUW82SXlibVBwamVlaWpWNzF5dk8vSWNmZmN2R3dGM0cycVpqUmgyaVdveG80eUl5d25IN284MjJJVXJDeFdweGxLMkxOQ2VCTldzSDlZNWJNcStOOEp0SC9HWFg3UEE2NldMcE5oc3h5clVnNDV3TGwzUEhjVDBtM0Z2TVRYak1SV2tjSi9FeVhzMWxHc3NrTVVZQ1pTSDArU21mbFFXSFNvUXF5NGJZWEJLenUydS9XL3hjK0ZxcTVjMEdqVEwrTktBUnNEOWM3MDgyZ3Y3NFJSY2VqRmJSYWlHVE1FdWZWelc5TW9CbEEydU1EMW1lVHlZRzQvVk9WNDNXYWpuZE5WbzNaaHdJTEtibGJ2cDhxSThxVjQxdDZKb09rVmJNalcwUG9TNlRDQlpER1FQR3hjRXA3NzQrWDZPQ0dRZ0kwRU1BTi82OFAzOC8wb3JReXlvckhUTnRRUWVMRjR2eFlvd3h0cGp1N2w1OFpqVS9ZTWdZNXpaT05rWWo4dFAzUGpRNWNSWUlLRHQ5SVRPYlJJZzQyWmo4NnRQdVgzN2ZpaEE4RHNCZDBFbjg3UDlNOXk4bGtmbnViN3p2My8zd0c1VW1xY3NzZDFVYktudVk5Ym1nWWo0MnhnalBHMDVPdEFjbVdzM2ljR0VCYnB0RldzYWk1bzBvQ1VKY2VQM3habHVLd3VVMENlZU1peXdJZ1l6SjNLcVZROHNRSUFBaVE1NnRRd3JIRTg2eEo3SGUwa1dVSm5FU0w1Tm9tY2FSU2lLakZRRWdZeG56TlNBbHRJUUhTdDZ6Q0lHTkp3cVMxRUo4TE9TSlFOb1FJUFg2ay9IV21WdXhmbHJKT0Z6SU5DYWpqODhjWFRhcGNpSGE4Z0VBaGt4dnNOWVpoRWpHTEdiN09vM3JZU0dFd0FuTWpmU1p1ZHF4NU5DcXFyYVViOTJwcVlLV05JS2x4d3ZwemFEMWdHK2VjTzl6MEt1VzlSRUpQSzZ1ZWF0UHV2RVhBUm1Bc0lTd0l5VjJWYVd0SUpGeEluTjQ0OEowNTNLOG1pTnl4aGdCYWFYOFh2KzJCMS9URzY0Wm8yMGZnaUh5T1BjbmsxOTRsUC9OajBTQ0VXT0F6RFdMN2ZUOEoxZEhOL3F1OTNlKzQ1VS84a2NlTkdTMGdYSjYycnBLR29FbElVaEFuRHVEeVdZYis4WGhZclU0NHF3Njk2T2dhL2Fac0lGR21qQUlpUXdUem5qOVpMdEY4WElhUlVzRUFNWjJMejNqQjBPdlAvSjZROFlGa01rRnNtQ2VQREVoQU9TUTFYRmNqenVPNE83WGNQSUVBSmcwanRONGxjUmhFaTFWR2h1dENZQWhFRERHY2lWdEw1bzNnR2hkSjFFcHczRE1NNURaZWpLQTJCOXZEQ2ViZnE4SjBUdGFxVlVjemRNNE1rYXoyakVzNVdVcnZ2S1R6ZDEyTTVESWVMMVJyejlxdFJlSVRMS2FSK0dpTGVlSXVKZWUzMDh2Y2hSUWJBaXNRNkVTWkVLRlRpdVhxZVZFcmswdlcwWVZRWlAyZUc5TDNETVdwd3hwZ2p4WkRLR0xJRVQ4YUxENEFEY0h0VFByYzU0clJOR0M1cldXWmp6TUdWZEs3Vjg5TjkyN0l1TUlBRWViWjA3ZC9aRG45M1E5YUlZQWZFZnd3Y1kvK1p6Nmh4K0xmSUdJaE1MWEJ4ZmpjeCtPNDJqb2U3L3lvMi8rMXE4L08xK20ydWdNME5zYW9ZNFNBR3VOeVJaUDJIaHlvcjJrbmtUTGNERnRnNk82ZnNNT1ZyUzdqRGlZYkluV2VuR2FoTXVqZmNZWUNyRjkvdkg5cTg4aDQ4aDUwRi9yajlhSDZ5ZGN2OGRkbnlFekprdHJYeGYxWXRrREViMysrRmJNeUl0ZUJDVGpLSlZ4c3BvbjBTcE5ZaWlPandmQXpPMVl5Vmw5emxITTlDd2g3REtWbEIxcXozblFHdzQzVGdXM0lINWFxU1JhcEVsRVJnSHlwZ01pYjFLSmVLdGZMWlhSUFZoRXhnc0dRWC9jcWNXU2NCR3VacGlMU2xValF6NlYxL2JrUlUwcEFyZUdHNXBFS2UvblA3WU1Ka0ROS3JhTVo1YXVadXljWGhlM2UyeVFuUStWU3huNnpCejU4L2ZsUXRnS3FBR285WlpxRFNyYWlvakl1WXhXMnhlZjdvL1hOODdjYTR3cVBBYzVRWTB4L2NCZnVldC83amZDLzNsZTlWM2tISUVIZXZ1SjZOeEhvbGlmUFRIOHRaOTQyMnZ1M2xqRXNocjhHam5xSWxjMUluK01DNmMzMnVTdGVhQk1rK1ZzajJWYmphcTFqUnJGMjZYWmZTY2dNTlNmYkxoZU00VzdVdWxxdWs5RWpQUGw3T0RTNDU5RXhuTFVZRFFSSWVQQzhZTFJ4bWh0eSt1UHZkNlFjUTRFeFlHS1JhVmt1T01QSmh0dGRFckdmQzBXa3NqSUpGWnBISWVMT0Z4S21WQ1dXSVFoQUVQTXB2RzFhWk50TGNFaVIwa2RyUlhqUEJpdVRUWk91NjIwOXUzTGFKVzVlYlBqRXpNZFVDSVpXL0haeTlFdE9OYXdrRFpxeTNDS094eHZkRVkxcFVtMG11OGpZSjJ4Z1NPUHpmeHkvTGltRkpIYlR2SzJDczZvV1YrcHJ5TW5oQ1prcm1oSGdHQklDZlJQdXZjT3hTWVFML2RERVRJRTNuRE0zS3pieDBsRnZ2c0wwZWhHcEJzUndkcDRjRU1Pdi9POXE2L3NLRjlBMXVIazhtZjE5cE9yU0gvNzYrNzhSOS85NnJ1MkJxdEUxenZYcnJSeEZSVXhNVnJiYXFQUU5GcXRsa2VNY2FNa0lqTEhCUUlpVFlZcUNyWEJmYjFHSXZKNnc5NWcwaDdkK2VHTzFwSWhVeXA5N3ZNZklHUlpySjNkWGdLVE9XeVljUHplc0QvZTdFODJYWDhnSEtkOENKR05OOCsweTQvQ2VScXVoT3NJeDJmQ2NZUURYOE1ja29oVW1xYkpNZzZYU2JSVWFhSzFoTndsd3JDd2tOVmxMWEpBWnYyMDRVTDQvZEZvL2VTdGJBY3pXc1hSTW8xV1JDWUxGK2lTcnZiWE92MXJxS2RUTStSQ2k0ejN4eHR0dEFJQVdxWGhZcXBrakRrR0xpZ1BYRkowTFg0eW9qa0hVWFBWNU1VM28wYXR5bk1VUTVWeWJ3YkUxWlk5c2xVQlVBRmZPKzA5R0VCZlFlazR4UTd2YUtPVE5aVlF0YktiYWphbGlHQ3l2djdZb2YrRDcxczl0aU1ISGdCeU1DWjU5a055NzRYRWlPOS8rMHZlODcydjRVaHhkMmFLeGdnaFFBMmpBaEZ3UGxvN3dWaFRCU3FaTEtkNzJidVhudnlVbGpJWXJRMG1wM3FqaVhCYzVBSW9QL0Q1R0NpU2s5UHZEZHY1U0lrb1hCNmw0WXB4QnNndVAvMjU1ZEV1WTZ5bXd4dEVJcE9mRVFxMGR1THNtZnRmVVhoNUtSaXV0ZFArYVpYT0RyWVpZMFdpZGN6bWtNSnhoZU55OFRVNmRZeFVNb25DT0p5bGNTU1R5R2hWb1ZVc2QrWG0rSlNNQVlhOTRmcmF4aG5IZi9FUUJhTmxIR2JnMHhSeTNrSVp1YU83dzdLVnNMTzlxTjJxcXVJNklvT005MGZybmVuL2lNeGlkcUJsWEpoRUtPU1FhVkw3OHNLUnVzWkFWTExmN1ppcDExdUtROE9KMm14amJlcG9TREVtVG9yN1I4NEpCanc3bDhaMnpFQ2RGNnN1ZEVIVGhzV3RhYXpzL0tEeDF2cXZQZTM4OEcvUENkRGxBTUl4U1pnODlYdnBmQ2RWK0RlKy9SWC80RSsvZXJhU0ZUYXJkYU5iS3F1MU93SmdiRERlYUNkeTFsck9EM1lRQWJtNDh2UVhGZ2ZYczJFQVFNZjEzVjYvUDk3eUI1UGVjT0k0SGdFU1ViYkVWNGE4WlR3aGhET2NuR3lyaE5YaU1BMVh5QmdYNHVJVG4xa2NiaGRBcU5vNFhWSWliM3htVDhnNG5uL255MTd2dUFFQUVoalg2OXJtVDdTWTdXa3BHMnlYenlFQlVRZ2hIQzVjeC9VWUUxK2JVd2RrR3Nza1N1SmxFaTZUSkNLZEplQmlpRWhnRVBsZ3ZEbFkyL0Q4Vy9GOHBra2N5bWhsY3V0blI0ZGtseTFvMElWeE9peGtuYnNhVHphUlMrZENidlpUdURoSzR4RHFDOFFJREJIMzA0djc4bEtaWDYrYXJEVDhMbkE4ZWlxc1d2ZDZCdFRDQlFtTWo4UFQvb01CR3ltUytTNks0KzFQZzNaZzk3bnFvZjBFZ2V1Ni9uanlpMStDbi8zUUVoZ1RqSkE3K3VCaWN2NVQ0Znp3OU1ib0YvN2k2Ny81bGJldFlvbFc0WlVNSUVMbElXaWJXU0lBeHZod2JhdkRCaXE1bk8rVDFvSTd1MWZQN1Z4NGtndW5KSXN4bENWdVFzWWNMd2dHYTRQeHBqZVkrTDBSTWlSVElIVXdqSW54K3FtMjJ5MU53dFY4UHp2SWMvL3E4OWVmLzVKd2d5YU1xUWFyRGhPSTd2LzZkem1PbCtGMk1tWjg0a3o3NVBva1dvV0x3K3hJQm1RY3NrVU9xL3hxV3dBeVpNeHhYSkVma01hL3Rqa2tLWldxSklwWGl5UmFwbWtVRE5iR202ZGM5eGFzbjFIeGFwRkdvUUhOckppVnVyOGRHckVxME9BY1N3TVdqM1hKWHBXdG9QRjhOZ01pTHhqMkJ1UDIyQUZBRXEyaXhWSFJ1cXBTanU2UnZIbzlmUWFCc1RKSFJ0c2V3akdBMDQ2bmdSSWp0OVllTFpJWVVBeWNUZWVPZGVmTzJoSkZGOEpzaUdLbndhd2VJREsrMytQOXRYLzYyZVR2Znl4MkhVUWs0SjdlZmlvNS8vRWtrZmVlV3Z1MW4zakxLKy9hbUMyVE11YSs3bVdDRW5ubTNhb3B6bXdvMlhDeTFZNlowQ3FkSCs0U2tPQmkrOUtUZTVlZjQ4TEpFRkdSNmJUQ0VVVFpoQTJZRUY1dk5ObTZmZjNVWGFWWkhreTJPbXlzU21lSHU1bTJYTTBQTHozeGFjelh5YXY5VU1XQ1RrV3V6UDlCUnAyNis2SDEyKzZsZk9aTS9kR200N1d4RXgzdFg4KzhsSnlMNWRHZThEelg2MlZiTjRoTTd2Y3F5RjU0WEEwZ1kweHd4M0VjWHdqQmhQdUhUaGVRRmE2VUVsMVJLVTJhYUpWRWl6UU9TUnRvcFBmb01HdFFqWGh6V2FnMjVjcWVzV0l6d0phRG0yRTNKRExrK1FOL01PcU1WWkpKdEZvY0ZlazJxNkZpS0VJOTNVN1BKV2JKUUFDVVVabVdJSFNzMTl0Q0NFWFg2dDJzTFVqYTdTY0R1czgyRzBKWXAwTFdKMHZ0V01VMFZqcXp2dE5nTUZ5SnlmZit4dXFEejh1K1R3dzU0NjY4K21qNC9DY1RDYTkvOE9SLy92RzNuRm9Mb2tSYmp0RGFPTFc4WkhVaEpFUEFobXViWFNoVUxhZDdwRFYzbk9uZXRhdlBQcG92V3lOa2FxMXV4a3Q1QmlEU1N0MzEwQnY2a3kwd1JBREQ5UlB0S2I0aHZUcmFWMXBtNzhzNFdoN3RMcWU3YWJSSWt4aklJR1BaMUw4V1VwczliVXgvZmV2czE3Mk9zbE5LeVFSZDJXNkl6R0s2YjJTYXVVU00wYzkrN29PY084THorK09Ob0Q4SlJtdWUzNE1zbjBpK0NGa2pZNGE2RVREYmxPd0hRK0UyQ2ZYLzFXV01Uc0o1RW9ka2RCR3gyWlk5MnpRMHBqTUVUYUZxSGx4WHdqZzh0dkR1MG9nTTQySTQyZXAwbVdxVkxxZjdoalJXSjlzU0lETGlDdElyeWVPSm5oZUJiNWFkYUVmUDJNYkR0dkcyNXJCYlYwbHNaUnNOcWRxY2tBcXJZVFhZMm1oRk9XaUUzS1ppUmlMTTE3cjRhRHpZbDczditLL0xMMXpYUFllQU95U1Q5SVdQcDNzWHBESS85YTB2LzlsM3Y5eHpXQ3BORjZhdGpWQnprYkJvRlNMdmpkYWRObU1Selk5MmpGYU1pM2cxZSs2TEgrR00yd2Fxb0VVclVCRFFLSG5xM3BkdjNYNmZVZ3BJQjhNMVAyaUpCOUQ4WU52a0IzY0JBQ0FEWklJTUtSbUhzNFBWZEM5Y1RkTndhYlRNZ21PQXNSd2VrU0Z0SG56OU53dkhJNk1KaURFKzN1anlpQzZQNG5CWmJKaHlicHgvNHVER2VTUXdSRUFHZ0lRYmVNR2dQOW4wKzJOL01IRzk3Q3hMTWtaYktxYm9sOUhqelRQOGE5aU9mTnhsdElyQ2hVb2lrNHVmelZ6ZHc5cVFtL1ltQ2V2SDZySUtiUDF0TVdxakVDSmdIUHZqamdWZUFDQXkyUlN4dnFjVXNnV3RmWGxwUDczQWtMZE1VaWxwcldiWVZySHgyRTNBTFFBUVdMc29pc1ZGZ2xMTW9JQllqQkFZY2tSQXhqZ1R3QmhqUEp1RU1NWVlNdVQ4eVgzNmdkOWVmdkdHR3ZsSXlFRkd5VFB2anc2dU1lYitnKzk1NUNlKzVXV1JsRm8zYkVTdGl4YjFPNklIQVhDd2RsSzA4bndiclZmelBTVWxZeXlONDh0UGYwNG1Td0FzZ1dpcnRnbzZhaVZIbTJmdStyclhLeVd6bGQvZWNLMDladUhpS0k3bURIblZub0theURETFJxNlVTc0o1dER4YUh1M0Z5NW1VTVJCbGhMcjl3VWRHNjZlTmxwa2FHNjJmWkcwc3JmWGk4RWJXWlNiRTBZMkwxNS8vTW1hSjhLeVlLYU0xQUNFeTRYbkJZTjN2andlVGRhODN6bDJwRlEyTmNMc2lYWWxXeXlNaFhPRzRqTHRmTFdMVldxWHhLb2tXWkF6azZMTXhiV2tMWWNXcExhRnF2dzdOZDJ0Zm9hUHc2aWVBdWh4a214TDdvNDB1MkE4QUVDNm1TYlRNQUZNbDBneTU0UWZxeW03eUFtQjJHSWJkQzdzSERZQmF0TUV1elE2K3FmVWlLOGRvTW5pNGM3bmtTRURPTXBiS1JJdHhaQXlaWUl3angzeHYyekhqOXQ2bjB4LzY3WlZCY0RrZzkvVnFMM25tL2ZIc0tQRGQvL3ZQdis0dnZQUEIyVHltbGtMczhwNVJpN2o1NThGNHN3MnVpTXppYUZjcmljaUo5UE5mL1AwMENwRWhNcDQ1M0xPbnNOeGpWUTBWa0ZiQlpPUHNTMTZEekFFeWpEdmpqWTdZdENSYVJvdHBXVlJGM1BxaVJBWUhnQ0VZSTVOd01UdElGa2VMMmQ3RzZiczNicnZmcUFRQUtkOFVOMmtUY0hwNEE1UUNSRUJVTW5ubU0rOFgyUUVoTmQwQmxXYWw3Q1JKZzhJUmpudjZycGVOdDI0cllwV0FFSWVqRGRIeTE4czBYaHp0NW1Nc0JPT080L21jQy81aWVkTUJnSWlXc3dPWnJCampkc2RibHMxUy8zVWQyb3lMdEgrMFhtOWFpbXl3T3VKSnJLSnNBYThwY1RBR2dzRWc2SGZRSENBN3JIdFpPNUViaUJBRU9UTnpZemM1cnlqSm8ycnEydENxM0E2dHE4cG9DbTJ0QkNEUUJPUmdiMDJjeG1nMXovS09jczZSOFV5cHYraDRXSmN4aFAvdXk4bGYvMkJra0J5T3lGMTEvY25reXVmRDZlelZENXorcGIvOERWOTNlcnpJSGFIUTBtZDJSY2VKSWhqQzRYak5hWVd0RU5CeXVxZGtra2U2RWNsNEdZZkx4ZUdOY0RGVmFaUUhuV1FyNlprb0Z2aUlpQnpQZitBMTd5UkEwb29MZHpEZWFHZUNNMXJQRDdlTDVqVzRwZ05LWlZUUHRBQUFxRFRtUll3N0dSS2VQeGh2dGhWWkVxM0M1V0VXSk0yWXVIcnVDNHVEYlh0cldQVm9NN1lMTTYvTWc0Kzhrd2xSUEV6SG1jSGwvRUNtc1gwSEFCQ1JjU0VjVnpnK0Z5THpaalhmTFY1WXpRK1RlR1c1UU51Q1Z4S24vYm1KUXV0TVdwczVsTSszQXJpaFRmYjZaVWZxRXdBWUlqOFk5QWFUVHNaV01sM085c3FXbHlxQW8waE5mRFY1TERaTGpyeFlsNi9EWUJ0azFuejVyV1hHNHFzQkEwQXVCbXZPbVJFL0paaUxSTzArM09KRkFLakkvUFBQeGYrLzM0OWRKd05tanJ6NnhmVFNaOE5Fdi8yaDIvN0RqNzc1NU1oYnhxcjJYcVhkTzQwZUZ2L0xrRDBoc3NHa3d4TURST0hpS0k1WDVYbjBBUGsrRHlLUWFaU0d5K1YwTjV6dlI4dEZQbFZqckFTU2lIRG5RMi8wQnhNd2hnQkg2eWZiMHllbGt1WHNDRXpaL21yamM5Rm1XNUhiN1M4MGNUWmx5c2VQVFRaUHQvbGJxMlE1UGNnakI1QXREM2V1UFAwNUZNSXlDVVV0MWxoYXpHQTJicnR2NjQ0SFN1UnRqQnB2bkdsN2o2Vk1sa2U3VU9rQW0xOU1JWS9JT0hlQ2Z0Q2FHSmQ5REJmVFZuYUNUbUc0eWRVUVVXZ29tc0l2MnJGUTN0anBhMXZhOGs1V2lHMjRpTWoxZXNGZzBybmZXaXNaemcra1RQTmRNaVdSQVEybzY4a3pjNzBuZ0djS3RyVVNhSWxpNDdJYkFFQ2drZERqbzAzbmJJK1BPYnBBUm9QNVdvVFFUR1A0dnQ4S2YrLzV0T2NDNTRLUXFRdWZEaTk5WGhyeG5XKzQ2MS8vNEJzZHpsT2w3U0czNkZYSDE5VittVEtPSExKZURzWWJUc2RTRlMybSswb21OWGhqWWNWc0d6VkRwclZLbzJXMG1DNk9kdUxsVktZUkdUSkczZkdTMTY2ZHZFTnJCWVREeVdZYnVSSFIvR2lIVkxFZjBnWkZGV2ZZUnJ1aFVNRHFMd0JBYjdqbXRvSmppR2gyY0kwTUlUSkE0a3c4ODdrUHFqVEprMXFVZnZRbStxdkUzSEc5ZTEvMWpaVXhJWEQ4b0RQVi8zSzZxOUlFY3VGcDQ0NnlSUVFBblhraXkydTFPRXlpRmN1S3FqV3gwejFUMG9UcTFkWGxzQ2x2ME1YeFRROXFsOUd1NGVISzMwNkUzQm1PTnpvM0JKTXhpOW1lbG1uWjVVemhJWElDMmttZW1jbmRuSzg2OEhLNXFtMW5OTUNzRENKandIRGdBNzR4RkNjR2ZKMmhZMENWME9wckVVSjQxNi9PUDNsSjkxeEF6aWxleHVkK1gwNnZDK2E4NTN1Ly9udS84VjRsamRUVTRGR2JnK280QmhvSUpHT3EvbWlqSzFDWVZ2UERKRm9WbTNUTHF3c0NZVDREQWpJeWlaTndzVGphTWNyYy91Q3J0RW9OUWRBZmRjWllMR2VIS2ducld2Y21LYUdnZzhOS2pYNjhZRVRMV1J6T01kdDN4UG5CMWVldW4zOHltODB5WkZDY0dtMnAzUnBxazNGNDkwTnZIRzNkYmt5ZWhZQnhNWmgwQk5OcUpXZUgyOHh5WmhhRk5DZTMyVjFqbE92MytxTU84SnhkU2J5S0ZvZkZpODFwUlF0YlZzTlJsdzJvVWF5dG5hc20yYVUxUGgvM1dQMEJKRExBR08rUDF6dUFGUUFBUkt0WnRKeGxpS21BWTRpQWlHeXV0cThsenpLa2ZBTVVOVHkwMWtwOTRZd2hNQWEwZzk1UW5Cenh6ZXlBcDJKdklSWmlEbCsxRUJJUmdFSEVYM3BDZnQrdnJUYkh1VnFOdi96ZlZ0UGRjYi8zYjM3a0xYLzhrZHRYc2FMR1lGZlVBWnVWQ3lJMUNVcUV3YkI3ZzArNG1DWHh2RnprT1NiSXNBWnJzNVprM3Bwc0hZKzBOc2I0ZzdIZkczWk0wdUpsT0QvQzJpSkg1d2RvR01ONjE0b0puS0h4MW0zdERjRktKc3ZwdnMyeHhzZ2tETVA1d1hLNm04YWhURUlpWXBqN21ScFdnY2o0ZzhsOUQ3OUZhNW4xRDhqNGd6Vy8xeVFha1prZjdwTFJKV01kSXlwUTd4cHg0UThteDhwaEdxOVdpeU1rZ281RGVjcUJycGRmUlkzVzZFblpxY2kxdHhxQ1ZKWUQxcDF1dFZ0ZmhiTG9BSkMxdGpkY2MvMmd5NHBDSEMzaWNBRmFVNllhaTJZd0ZFdDljRDE1VW9ObWdBVlFLNnF1b0Z0ZWxRYmpvRE1XWjhiT2FSLzZtVUMyZ0FNQUhHTUppUXdSR1cySU5HbHRTSlBKTGdXR3ROR1RqWTFYL1dKeWVXb1lKeUJVQjVmVDYwK2ZGSWYvK2dlLzRZKzg0dlJzbWRaVG1kaTBxK0dxb3ZuMTFWZ2lRT2dOTjl5dWNPRTRYRVMxZkQ1Rmh4Q0F1dHl0T2RCdGpCa1M2VXpUdDZ2SURnQnF0S29NVzRWbTdOWE5oUkNJYURCZWIzdVZBR0IrdEtPbExCejlBRUNJREJqTHNvU29OSWtXMCtWME4xN05rM0N1bFNRQXhpb0h0ZU42ZDczaXpVSzRoa3lwNnRkTzNON21yVGhhUmZNRFpJMjljOURnWUJ1bkZIZkljZnhndUhiY2VtT2FoS3Y1SVVKaEgrcmxkd2FnZGVIVnB1RTZ4bTFlL2xUanBEb0FzYnJTTklZMmFEWitieFIwdWFrQlFLdDBNZDF2Ny8xbnlFTzkyRTJmaTh5VW9WTnpscGI2TnZlN0ROYWQyL3BpNG1IZmtNbkZJV2Z0R3JjQUVjbzBJYU9OTVlZVUdLT1Z5dmNYWkx0KzJuNTlBR1E4N0oxK3d5OGNSVmxDSUVQcHRhY3BEcC84MzE5OTBsZXJTTm90dDlDbWZiTm1DZXR3RkFBb0dLeDVIVGFRVm90cG1tZTJhOHYyc1ZyVEh2V0N6d3puem5DeTFYYUhhcFhPRG5aWWNWUjJIVFcxcjA0TGJKa2FvcUEvOXJ2Z2JoVE80c1dzeUo1c056Z3Zxamd0QzBpYk9GckVxM2swMjEvTkQ5Tm9aVWlUMW5lODVEVnJwKzh1andBaE1yM1JSbnRQQmdETURyZnpETGwxbmRzbER6WFNaWUxOR0Irc25UaE9EclZXcTltK2xoSVpxeE1CR3A4N3RnTjFtYzI2Z0ZVbDJIRmFlY3N6eWJJanhhcm5tN0Nvd1FsQXhnMEd3YUI3M211MFdzNFBWSnJVZ1QweEZFRG1hdkxFUWgva2NiOEZMalZvRUNEQThkRFpuTEJUakxsRTJoVDJzZFFROXU1NUFQU0RnVmhNZDh1QnFhaFZLdWQ2QXpNMTZiaitVenRHSWl1eXAwQWF4NC9jTWJoank1M3VSZlVlWlpnWDZvYTd6QUpXbVFETFFORndzdFYya3dCQUhDN1RlR1c1S3RxS3N4d2V1emZaRUZsVmdpRUQvYld1QlFsamxyTWpWaDFXVHpuVE5NUC8ycGJFcXFxNFR3REllS2NFSm5FWUwrZklXUFk4NHc0WkEyRHNGSkxHbEJsNklPaU5ndjVrN2VRZEtrM2pjQll0cHF2cC91VGtuVWJKc2t3bW5NNXNxR2tjNnN6djEwSTlsbjZzV1F5cmF3U0lodlRpYUcrd3RpbTZzbzl6TGdhVHJlVjBYNnZTcXR0RlZiclAybUNYUFdmcnpVb3ZsMkxaVUtrbDcySTVwcFNuMlN6RTJPWUthZytUclFJQVdScXRqREc5d1ZyYlpjcTRHSTYzb3RVc2pwYldSQnF5Vkc2MytTL2JUYzd2eTRzY1hjeHVFZ3o0K3NRNTAyZWJEQm1CeWVaK1dXQ1p4ZVE1UnhFWjRmbEJmeUtFS3dDZ21Qa0FXQ3FsVFFqTU9VTTduditsR3lyVmtDWENKQm5IOCtXM1AzSzNtUys3WUdkNzYwZDVXVit6MkJZeS9hNGxaZ0JJNDFXMG1DSXJ4NmxXbWkzSng4VVoyakZBZzBtM2l5eGNISkZPaSsyemxvRnRMbjkxQk04VzlkZlVmR2ZpS1FDSVY3TThsb0FBT1o4ZjN2Q0RrUk1FMlhxVU1Scks1RFJFV0p3aUJrQ004OEY0YXpBK3NYWDdmY1VHWlFRQUl1MTV3NDd0K1VUUmNzcXlhVU9USm1XZjBPTFMwckdRRHlVQ0FEQXlLbHBPaCtPdHp1NHd4b2VUemNWMFg2dTBFTFVhL0t0UEFodFVhenM4b2Q1SVN5bVVZZmhGQzYybTFtUU9xLy9iTDJCdFdCRmtITTVWT2h4MzdBZEF4bnJETmNaNHRKb2pWcVVSR0NBODZkN1AwVDJVbHdGcHdrNFBuTTBCMzBCQVE4WlVtUTRidmM0UGdIQ0U2L1Q2bnQvUGZoSllFYUhjMWRBR2RUYTdJeGZlcnp5K0xCcnF5c1B6a0pwdmUrWGFNbDVrN0VzQWhibUNZdk9DVlZSVmhRVVJDY2pvWUxqV21UUWhxU1N3eEpQdFlZTmFnYTBoejluQ1VIL1NuVDg3RHVjeUNSdmdwQ2lxZGVVTTF1YnBnb2hFYnEvUFcwRjJBTEJhSE9aaHFBUmNPRGN1UEw1MytaenJCY0Z3emUrUCs1TXR2ei9rd2tORVl6UVJGYWZUWlNBS3RGWVc1MldFSnU1NDdmM0hBS0JrVW14RUxHblE3RWs5VUxkaG9ESlNFeEY1N3MzeVdTRGpvN1d0Y0RrdmxoRExKcGIyc01HUlphM0hhQWRzNmo3TDZKWDN5UnFneXZEVzY2MklaWmRlTmh1TVdVeDMrNlBOanBoa0FMOC9RaTdDK1dFV0psM1dhMEJ2T0dkN2ZJVEFBajRtSU1qQzg0c0g3SGJtRGdYU2lNenJEWUorYmFlVmFQV3dRWlJhaHdHdzV3ZlBUUEh4NjJwenlBREFxQ2c1MkhuNDYwNmNIY05xZ1JYVUtLd2Yya0VESFNndWgzd0UxQnV2ZTM3SEFuRWFyOExab1NXQllFR1huQVd4emw1WW1hcWFjaVFDdjk5dko0d0JnRGljUjh1WkJRcWFHTWtxcTg0dU5WNHBjQkdSNC9wQmY2MVZEeW1aSmxFV2NVS0lMRm9jN1Z4OHh2RjhyZVRpY0h0K2NJTmRmYzcxKzczeGhqOFk5OGRicmg4Z2N3REJhQVZrUzB4bERZQmcySlVRRlFEaTFieUdDeXA1YTN5b0xlSGFEUzY3NlFRdmxsUUdXVzh3Qm9BMFh0UTFWQTFTTlFoaWZjRGF0SkRzZHpNYldHZElKQ0I3VU1yR042Q1F6UVBGNEZhalJnQkF4b1N6ZlgrNDVuVzVURDIveHhrUGwwZGFTVXRIRTRFTzJCZ0FDOU5YNjJuTm0wQUFhTHhnNFBvRDRUUUR5c3ZVR2xpWVJKczZIWExvandhLyt4bnBPZ2dBeUpqY3V3Nm9mL0J0ZDZaUmJBV2tROVhieXZyWlYyM2diK0pSa0VtOEttd2cxTGlrYWhpVzdiVW9qclgyRjQzdmQ2U3JnQ3daNW1wZVQ3UlM5cUpFUWQwV3IrRk55TmdJR1I5TU50dkRTUVRSY3BwbHo4N0szcjc0dUhEOUREd2dFeGxxU09KVkhDMkFpRHRlYnpCMis2UGg1SVRYRzJXQnlFYnJ2SHRGaXh6UDd6eDZOVnhNWlJxWDU4L1ZPYldtQjYxbDhiSkhGWGtOVWRBZnRSMXNIUmRpYnpnQmhrbE9UK2lTdlRaZjVmYldmc0o2SUxkMjF1aGlUczJXZHE5ZlpJMmp6WndBamRGRUpLQndkbURrSU9pSzRNOU84MTVNOTdSSzdIMWJWS05rcDVJelFDQmMzdzhHempIbmx3akwvMWxhOWFZUHJld1FaeHpRLysxbkZ6MFBBWUMwVVl1RG51OC9mTHVqc3VTek5iNXZqRUVIb1JBdzIxelhLWUZHeWRYaW9Cc05kclFPb0ZWN3Jvc0lzK1RjbmVreHlaakYwVzRPY3hsSFpFUzZ5QkZhU2xyMWVIdjdxYzBFMlpKSU1CeDE5bmM1MjFNeVJXUkF4RGc3dkhGNU9kM1B6MXF6ZXNrWUkyS0FZTFJlVHZmTjRjN1I5UXRNT0wzaCttano5SERqTkpMRnBnanR2VmNBWUl4SndybTFMQUgxc2FqZkxEdmJzUHFaVURMV0NSK091M3I5a2VBaVhFN0pOQTRQckFqV1dMMDR4a2xyWDRWTEk5YytKZEtEVnJDSGJRQ2dYbXhOTlpjaHB2bWlQSU00V21xamc4R2s3UXBHeGtacko1YXpBNWxHSmZVYlpLeGhKU0lDWXB4Ny9iSGZ4ZDdsVmFhRnR4cmRsTUQ4UGlJd0lZNFNlR3JYaU94QnJaR2tCM0RucUtTUjFaSzZrbWg5QlFJZ1k5eGV2MU0yWkJvdnB2djFmYXRndFJPTHNheFJvbDQ4WUQ2ZE1ZeUx3ZVJFaHpPYUtGd2VFZ0FpSWVPTGcrMzlxOC9KZUFVRVhMaklzdXp5YUZYYXJxMDJWUUFpNGZ1ZEdWblNKRlJKWE9XdElMWjk4U25PUkVXeWdyZW9VTEpGS0tlTGpHa2xaL3RYVldwSDZoRUJjZUVmNDhvS2dYRkxoZGw5dDd0UTY1Y1ZZbEU5d3hrL0xxRmcrMlpXcHV2Mys2UE5WZ3dHUU00b0RYV1FNN1hOT3ZXUERTTlo4aWhoNC9IcTZUYXVhZjZFbFNEbHpVQkVtWVRMNlo3UkV0b1g0bUM4MFJ1dVFSbmFhcGVmeHdNaVpLRi9pSjdYRzY2ZHZMa0VRajRuN05CQnVZb3RscitKQU1BWXovUC81WmRTTEJBYTZDU0owbGZkTnprOUZvdWxLa2F1aGhBYXNlVlY0Wm5GR0UwNnRialdNcHdmbVByaGVGYW5iZGhaMGNBcXZMenlvSy8rTVdHUXkvbUJTaUpFaHR3eFNsMTcva3RLcG50WHpybTk0V2p0MUdCOTB3MkdYR1Q1UXZPZDdEVnQxMmljSVNMb2R4MWRhSXlPbDdQU0xoR1pTMDk5em1qSm1JQWlTSzlzYzFWa0djWU9pQWlEeWRiV0hmZnJhbGtDeWVoc0d0YTRpRXdTTGF3TzN4eEpObG04Wmk2SVBML1hwbDZXT2ttbGNUQmM2eXpkY2IzaHhzblY3RkNyeE40N2k0VWNOTnRRN25DeGpFd2RhbFkwdDhXMTNtWXF5RmJyaFZWYUF4YzBRQUVpTXRKcWRyUTNtbXgybkJDTTZBVURSQXlYczhxRFhhRUpBQUJEeHZWNlFYL1M2WlpyWHlJVGhtNmNVSG5WQUFHTW9hWHhmKzJ4S1BPQUloTnFPVXZueWQ5NTkvMUtxam9WY2lIR0RzY1hGbzAxZm0vWUtZRXlqY1A1b1NGVHl6NVE0bzZ1Y0daTC9xc3JYMUFpR3F4MUpJd0JnRGhjeURqS2xwaGxIRjE2K3JOR0t5NGNZMHkwT0lybUIzdlh1T1AyUjJzbisrdGJ3V0NkQ1lFQXhwZzhsNllOUUxLMklQYTc5a01CUUxLYTYySmpQaksrZCtYWjVkRk9rWVNLQUVyZmV6Nm8rWEtvQlpka0V0LzlpcGNiVXcwOGdmSDhmdWRhU3hLSFJrdEVEZ1ZYdHFaT05jNkRFaERXSnlNRWhuSGhkVUVWb3hVQ0pkRlNLemtZYjNibW1PSmNETmMyRnROOW82UlZkOG00YUlISnFsWDJNTnNxajNMVVZvTWVsWnhWSTBMV1N6VmQyV2hETlJIRkNnTVVCRGVMNlo3ZkczYkNOTmZ2TXlhV3MvMzhNRk9BZkNNS0V1ZE8wQnZjU25LNnZBTmtoQlVFbEJ1OXVuMnYraUFjOGR5Q1A3WWoxM3FNQ0lsTXZIL3RGUy9kZU1kTE40NzJycGJVc1hMU2RFSkZBZ0F5eHU4Tk80T0d0SktyK1VFeGw3Q3ZCbFF1V2Flc29tbWFFSURJOUliZDBib3lqZVBWSEJraUVERis3Ym5QeG1HMmR4N3k3Ym5BaUNDTmxydmhuTjE0d1EwR3dXQThYRC9USDYwTDF3VmdSa3RMOHJPNHpYSG40b2VXU1J3dGM5Y3JNcFhHZTVlZlpWeFFHZTJCOW9JekZDWW9WeTVaMU8vbTdmZjNCdXRhcDJWUE9ZcE9Md0lBSkt0NWtlczJMNnNrZm9XZENGcWpZMU00LzlvZnJYZFdrZTFIUVdReWpaZlR2Y0ZrczFQN0lQTGhlR3M1TzFBeXJ2WW5WREpRS2xld2ZpdlZXem1tRm1LMFcxY0phbU1IYlNiaTF1ZzBvV2xXVlBsMFE2SG1SSXVXTXlJSytoMHpmT0Y2by9XVHErVlVKVkVXWm9nTS9XQVU5RHJPeGpqdWtta2NMbzZFUFNBV3FMTkhJbXVQR1kwbUgvcGN5b0VSQURDaFp6c3lYdjJ0YjN2WXhGTmJOeFc2Qk1wd25ucTkyVGtlM1dtdHlaajVkQmVKck1DTGdrd3RQZGUxK2x4clBBRjR2WUVYZENKeUN1ZEhXZmxjT051WG4xM09kaG16c1VlQmFoZ1R5SWtvalZiSmFqN2R2ZXI2ZmI4M0hHNmVHVzJjTGhGRWx2ZStVMnRxTFJmVGd6TFFoeU5iTEk3OC9rZ21rWlFTa0hpWjlaQ3E3V29sOFRLL2JEQ1luTG4zRlZwTDZ3RnllLzNPaEdMUmFrNmtBVm5kQnBTV3AyRFNna0c3SXVCenlqUHVkbS84SVJPdjVwbUZSY2ExVGhlei9lRjRxOU1lSW1PRHlWYTBtaWJoQXZQQW5kTGlRYUVNYk44MEZ1alIzampXV0JsdWhURmxsQ2szY2xxOXRvbGdJZEp1dHJGRkd4SGkxWnlNN2cwN05CSGpZamphWEM0T1ZSd0oxK3NOSnAyMDZyeU1NZkZ5bWlRcklCQlVVYUd5S20yTWg0eUI0LzNPdVNqd0NBZ1pvK1R3NnZwNDhQQWQvZW5zZ0ZVdXVLSW9Lc2xiMDF6YTZLQS83cHpHR0dOV3N6MHdCdERpRmF3WGJGMDJoTGJhanptdGliZ1FuVWtOak5ITDJZRXhLdk42TEtaNzJ4Y2VyOUlCNWNrUm0rb0RFWkU3QUNDVEtGN05ITDgzM2p4VDhnOWpmRERwamlhSmxqTWdCWm1OQlRKR2pUWk9EOWRPeWpRT1ovdXI2ZDVxZnFoVVFscG41WlNBdlp3bHlDUTYrOUxYSWpJaVZjMFZnUHgrOTNRNk93ZXVMWUVBTlU2SGd2V3gvTmp5OWd2aGRxYmZEK2RUQUFQQWM5T0V6TWgwTWQzdERTYWRYaUpFNlBYSENCaEhjK3NFejRydkMyT0FEYWMvQU5nQnpEVzlYTzNHZ05vc3FtMjJhckU3aldjYWdtcVZENWdOZXhJdGpURzl3YVRETzRVd0dLM3JucngxOFNPQ09Kd2wwU3BMdTBpWXpRa3RLbFFXb0tnazAwYWNNYVhabzlmMXdBTUFNRElGbFo1YUN5WWlRV1ZybXFyUCtmY3E2aDJJd0E4R3gwaWdXaHp0WnJtZmk0THM1ZU9xTVZEZExQVkYxdjVLYXhDQWNMeitaS1BMR1VPcnhhR1dTZVo0VEtQdyt2TmZjWVFMWlZaU3Uxd3FCS0lxSE1Cb3Z6YytkZmZMeXFneFkyZzQ2WEJxQTRCTW9qUUpHWEtMdE5taFl1RDZQYTkzMTlxcE8yVVNwOUZpTWQwTHAvdEp2TlF5QlFCa1BGc2R6VkpSRFRkTzJ6dVlpYWcvNmxpSEJJQmt0UVF5Z0kxWTZocGpXZDJ6T0xPQ3JKVmw4SHFERGdxU2tUSUNzQlkvQ0FDWlZuSTVQeGlNTnpxUDd3VEVZREJHaHRGaVdoaE1ySTl5RTIxYXpiUDdZbmZIYWtCaE83Q0Vwamt6RURZcDFRd0ZyanBSWWJlSzJJaGNwdUZpbWc3SEoxaVhyK1hXSlZESkpGek10SXlSWlR1aUVZRkVhZjFhd0M4MzNOa0RudUQvMStjbHg1d3dXa1pLbXdkdjZ3OGRsUmhtV2RIR25MQ0tSVFdrSFRmb3pHVm15S3ltKzBicGVoZys1Z3UwVmdzTGFOSmVFcTNaVzRiWUg2MjNrMXNEUUx4YXlpUm1pSWlNakRuLzJNZVVUQm5qQ0pXMUFLSTg4THhoUEJDQnlPMk43bnJvOVNWREVJRGorbDE3LzRHTVhzNFBXV1pKU20wQkJiNG5qVVlqSUhmY3ZyYzFXRHVodFV6amFEWGJYODMyby9taGtnbHA0dzlHWjEvNldxMFNleVdOY2VGMlpSQWpnQ1JaRmt0emx1S29NU3ZVTUh5UjA4QWlZOTVHSnB4MmVBY0FLSm1XNnNtaU95RXlJTE00Mmg5T3RvN0xkK3IzUnNoNHZKd2FZK3FUanF4aWdrb1NMQVBlYURQWUwxTHRweHI4dEdOaXdjS2lEZGx1bFEwbE8rUUFBWUVabzJkSE84UE9UQ3UzY0JtandzVk1wUkVSVktDZEFCQkZjYklEb2MwbVpROHlRU0NTelB1ZFoyVFB6ZDNMRksraVJmS1gzbkZudHB4UzlDbnZpa1d5N0hFa01uNHc3TXkwUTBTcjZiN1dxZ3lMT1I0aDJCVEYrazlRNkhnQW9QNm8rNkNzY0RWTlZndVdSK09hcTg5K1VTVXhFMjVoQldvRFdPRFNHaFF5UnArNTcySGhlc2JraDlnd3hPR2tZOHM4QU1UaG9vaHNJVVRHRUFBRkVBQmxqaCtzWnRJR0VFRndQeGlPMXlabnNnV0dPSnpORHE0SG84MGluV0UxSEo3WDZ6d1RaalU3ckRzYk8xUEsxN21Rc1A1QWJpcU4wYjJnUTJNQ2dFeWliRGhxTHhhN2RSRmdOZHYzaCtQalBJU2UzK2RNTE9hN2hjNXZDRnNoTEJVbXFkUjVXMXpxUGNvZXJIV3pibHJ0YnRiVlUxMmhWUGVvS29mSUxJNzIrNVAxV3prYXdDck1SS3ZzZEZTSnlJc1RTa3B0VEtKbDYrdnR5WG1UcG1ad0VDNkxaR1c0MnQ5KzVmMXIzL2lTamRuK05lc1ZzcnBZRlVYR09KNGZkRXVnRGhkSFNpYVlKMnZLS042NUF0dE96MHl0SlFzaVk0SmpwaVZLSmttNFFHU0VoSVFxaVFIQjhRSWxFMFBFc2hTSlJVb3RxbGVUWFViTGpkdnU2NDgzalphRmFqWEJZS01KZGdBQUlBNFhjVGpQUXB3WU9ISVpiVjk0cHIrKzVvMTdMSWdTbWlsYUtRaXoxSGNjL1RCV1lhUlNDVXA2THV1UGdvMXhNTm04NnhWOXh3TWlUVWJwUkpNeG9JWGorbDJRWG11dDByQmdYNHQ2dVdHcGVDN3ZFMW9jWGp4YTZrM0VMRXFHR3Iwak1rbnU2VVdyTkdzS2pVQkE0ZXlRakRudUJGL2hlcVBKaVdMakxLdnpIbGdTQmEzdU5FS1FHOTZhNmh0QjRSM05FUlhsanE3cU1idlMwcEEyNExGVkx4RnkwZXVQbmEvR0VxbzBDcGVMQW45eWFEYVlJQXRiZy9va3NONHNJaUxmNzUxZjRWNXNjbExvSkowZC9SOC8raFpRUzlOY1MwQ29SMWVEMGNJTEJ0MWJZR2c1bThwa2hjMEpUUG1oOXRmYU1WaWgvSkprMlNabHZ6L3lqam5IZkRuZHo5ZFJDQUZJZVA0ZFgvYzZHUzdqY0Q3ZnY3YWFIYW8weXB5YzVXN0s2Z1FMQkRMYUg0elAzUGV3bHNVaWdTRnZNTzcwdmhvdHcrVVJaNzRBWDBPYTR2NXpoNysrNzMwa29tMEtsMkJ1S0pJbW0xTVduS0VOYUlJVUlGU3dYTUxSdEIrdUpwaWVPZEY3OE96YVMxNTU1eU5uSi9ldTliZUEwQnQzU0NCa0Jxcm1JY3lLNzFTeStkaTJXQzNYUGtqUXFUUUJJRTFDeXQzWGRtbGtPU3NSZ0pEeGFEa3pSZ2Y5Y2FmUjVzSWJURTVFaXlPbHlybHUyZXdDbCtTMnVnbU5pc2RzOXJDLzJsMHV0QXFBdFZYSStyV0FBeTFudXhXN1Q0WXg1dmw5YnpCbXQzd29uWkpwRWkxa0VnSWdNbHRuMlIxQnpCZnJ1KzE3OWJFM0duL3dzVFNTNEhGQXh2VDhhR3ZzUDNMWGVEYmJzNllmMEtRU0FBQnd4K3RNY0pROTN4dU10UitrYWFSbGFwVE1QVzIxclF3VlVTd3lWOVZWMVJ2dEJQMWpFaGJRYW5GWXJLdGFSazRyeHd2YzNtQzBjVm9tVWJ5Y3p3K3ZoN1BESkZvQ0VDS3JGcjZJRU1XWmV4L1dNc21CQ1JGM25GNVhPRGdBSkt2RVk1T1F0aStuSDcycTNqdFhYMG5HZTdCV01JQUJabS8yUkNBQ2g0UExJZkJoZlFSNENnQldxVm9keks2ZHYvejVEMXlHLy9KbGYrRGMrY2lkcjMvN1MvL0U2NFovekcyQmJhMmx0V0dpSmg0Tjdtb050cTEyQzhEUFdGZG1Bd0NBSkZvMXlGZ2YrNHF6RVRGZUxVanIvakU3UElSd0JwUE41ZXhBcFJGYUUzaHJpTzFLN01rSTFLR21yVXJLVmY2Nk9RVUF5MktYWUsyRVhjWFROZkprRnRUeC9HQndiSUtQNHk2dDBqU0pJQTgxeHJKZjloYnp2RlhaMFdqMWZ0cHlSWXl4MGNhcFUrK1pTMjA0UXhCdWN1R3hWMjdSNy96RXczSzVqNWxMSTN1cjZ3aUo0ZnJwZG9LajltV01WakpWYVNUVGhMUWl5TjdPRzlOUUhqWEs1aEpCWExpRHlWWm5YWXZaZ1U0amFqS0tsYk1WZ0FITHpwQ1dNZ25uQitIOGNINjRJNk9sTVpwelliUysrK0UzOThlYlJxc1NCZzhtSjdyOUZyRzZkdlNwOC9KZjdxblBMczFGQkdCUWVkOXJQRy9qYWFxaGlLeDFuT2Y1MW03c3d2a3I4TlJ6RUVYZXkyOS96VGU4NUk5OTZ5dS9mMzFRNFl2NTRZNVJhYkhpVU13M1NtTm9hZUgyTExGc1NHRTZqQnNNZThOSlI5ZGt1cHp1UUdOYVU5UmxjVUpsYllpTTR3YjljYWVuT3FNRExlY0hhUnl5V3F4NTJidzIyZXhOYWxUdkJUVFpvd01GMUFmQXJyRzJTSk14RmZkN2sxczVHTHp6MGxvdHAvdEcyeHVnR2pRSEFPSS8rOU0vWlZsdzYxSE0renYwdlgveE9QdjF4MVhQeTl4V2tHNi84TTB2UC9YMmUxakJNeVh4YTEwaUlzZnJlYmZXQVVUR2hlTjRnZWYzbVhBWkUyU3lvNjF6dUZzT2R0RVRpOVpFQUd3NDJlelVWZkZxbm9RTHF4Qm9qUk1Da0FFQ01rUUdPZmQ2dytINnFjbm1tZDU0M2ZVSEtvM1dUcDNkdU8wK285S2Nka1IrZjFRZm01eDZrZDc1eFBhUFBCNy83WDMxWlExVFZyYTFCaEtiN2NqdU42ZkNCSnBBYXpBYWhrTzQ2d3pjZXllNkFwNjhlTzJUNS83blI1OTVyMkQrN2VzUGVJNm5WSnFzNWxBb29GS29MSU5RcTlDU2xvd3NOUjdWUmc4blhXRm9SS3ZsTkR0UHJocTNvckM2Z29PaTM0Q0lXa3V0cGVONjNRY01JN3BlWUlpMFRMRlJXRkZGQzI1aFU3b3NsVjF2US9rR1doK2dmQXZxTHhRZEpVUVdETWU5d1hxbm5yM0ZpekhtZXI3UlVtbHByY3JVZTRGWW5zclVia3pleWNHZzkrcC96MjRzRFNBZ01qMC9DSzg4L2NHZmVjUERhNkdwQmNTVS9hZWlKeVlZcm5mbUxMeVZpNHpKWFBZcVRiUk9pMHB5d0Zwb2svenFqZFk3OTlxa1NSVE9ENkR4aXAzZzJacEwxR2Y1Q0lpTU15QlFNbVdjVmVIRlJNajRwSFhRdkNMNWhmMi85ZHpzMzhaMElLejVhODN1bHBZZHVxaGUxKzkxTFEwQXdCQ0VnSjA5ZVBxNTRKbm5qVGI2eE9TT0gzbkh6NzNqdmorK1doM21PV21xSHVBeE5iWHRSdGxZUWlBbW5OSGFxVFl4dFZiei9SdldNaEpVVGJjTmVwT3dPZFdZNE1QeGlVNnZkWGJGMFNKYVRPMVE3L3FIcW1QMnFCMXY5enF0SDNTOWE0RVNBZ1FqdkY0d0dQT3VoRHFkRnhtVHBsSG5kcnlzNzR2WmdVNWpDd3JrMklFeEZpK25OVGhhQm45YVpvZU8rUHJiZmprSml5M00wWldudDBSeS9wKzhkYmEvblNYTWFKRUF5akZ4M2FBL1BtNUNlS3NYR2FPVVZESk9rOGpvUE1XdGJkLzdvL1hPN1pKa3pPeG9HL0pUTlJ2b3BUMUMxRFc2bWE0cUk2MFFnQkQ1Y0sxMjhCMkJPYi80elM4Zi9xUDk1QXNDb0RJaGJSMEw5b0RYN0h1RDR4cEdzbXdPQW5BR2lQRDhKZkdaei9lV2tlWThmY3Q5My83amIvMjVpYisybE12S3gxWS9aTE11NjQycWFqWDF4eHVkSjc5SHEzbThtcFlocWRaVm1hTXVVYXhxUjRiRHljM2tNSW1XMFhKSytaSWp0R2xnS2RPR2NOWWxLdXQ3VFEyMVJ4WWFOL041L25DTkMvZVdkakFEQUlCTTQyZzVVeXIxZThOZVBXK0ZmY1hSTXA0ZkVZTWlZQ2l2MlJndGJCTlJmenZuOVhOVEw4VUVEQkFDeUhSNWVQQzk3N29IUUJhNUxtc2tLajdsdnVFMGpmaHE0ZnJCVGVqK29oY3k1cmllNDNwQmZ5VFRWS3NrVFNLdFpEWVY5SHZEVGdrMFJpK25lNlJONFRwcW1BaUFHdk8zV2RZYTVtcFBJeEZCZjd6VzZNN245LzcrVnc3L0hqSGpaSGpTNWczVFFkWXFvSUdLUHdYOXlsZU5mYlFCNWNVaWdBSElEaU84OTI0MUdpdy8vMlYzZTd2M2tYUHZQWC93MlBkKy9kOTY2MzNmRXNsVjNuSTdxc3N1cUliV3FIbWY0WEh4SDJtOHRJNTg3Rkl3MUppOU42czJXaSttZTRQeDVuRlZlTUVBQU1QVnRIQ2ZZRUdWd3ZWYVNhWk5OdnZLMTFJUkxEMVhOdy90ajBTYWM4ZnJqMXd2Nk1iTVhaZldLbzBXU2JUS0VqUW5ZZWFGNnJZNmZqQmdqRWNyQzg5bmlvc3gvck0vL1pORlp6RFg5QVdTSmlMUEQ5Nzdndk9weXhJQmdLRmVIc1ZYcnY3alAvZnkyNEpFeWZveEwzVnFsS09VcHJGTUlxMVNJTXJUMGYvaEwrUmNDTWZ6L0w3akJRd1lGMjUvdE5aK2pvaVc4d010VTZ1NnhxNlpjbmpxRUtGMkI2R2lUQzZsamh2WU9mT2xDZjlnNTY5ODVlai9MM2l4NWFIK0w5dUFhTitCOWpQVWZJWWFiMkYxQndxZ29qWDBlblQvZlNvTTJmU290MGozUG5IK2ZRTi80K0V6YjVTbU9IZXA1US9vc2diVk13VGdPRzZuWDFUS09BNlhyTGFtMTdDbFdOU0U5VHNXaFJHSktJbFhnanZIeWFGd1hOY05aQkpEUFJpNHM4YnlnWEovVXgxZTFHcUhxclRxeXBaYi9ONG95NDEvclBlb2ZoRlFHcTZXaTMyVnBvaVl4MW9pMDBwS0dRdmhkQWJXYytHNGJrK21pY25UQ2lNVVN4VEhRQXNBSXNPYzRGZStFbWVJREptYkxnNkM5ZjZiN2gydnBqZWczT3lITmVWZjhtSkdGSVpBWk5JNFRPS1FNeVljei9GNnd2VTZXM21yRjZJUXJoZ2VPMk9PdzVsSjRqeUxlZFV3dE95TmpUWnRoQU9OOGE3Y2hrVElzOHd4K2JXVU4zNzd5dHVtOHB6RDJ3WXZ2OGdxRjFxZnk1WUJsRzdhcmlkdC9ZSFZBMlFBREx6dTZ5UEhOVTgrN1F1dTN2UDdQMmxJZitlcmZuZ1Z6NHF0d20zZzNXNWovaU1aNmgxempsKzhuTEhTcGRFTXZTU0xYUGF0a3NKUXdDUElFcG9zNXdkOTNPamM5Z1VBUEZ1Nm1POGJwZXBDMFVDaDFMNlBIZjF0WVlGOE5SVUl3UFY3WGpENHFpTFJsSkxSWXFwa0RObjVNRkExQ2hHMFRKZlQvVUhuaG1BQXh2bGdzaFhPRDJRU0Z5SDlCVHl0UTZHOHVRN0RKMmZPY3pzcWp5UkJTQmV6SDNyN1hXaWtzVEplMXFPRWN1R3VDc3l0Q0RKRVl5aE5vdFg4WUg2NHM1enRwM0ZvYXE2Mi8yOHVtVWJoL01oVThLVWFxdllTVGZhbm5ZZ0ttdVFnWTR5OUtyaFNlNzk3L1k4ZnlYT2kyREJFV2Z4L3NRdUE2anhDUlIyR3dKU1pYT3k2TUxlS1ZGQ1VxSGdMcmZLTFo4cGl0WUZIWHBYZWQwK3N0SmdFay8vbmszLzduMzdrWjN5M0IxanVTRUNydHJZZXlMOGFZNEwrcUgyRWNQYVRNVmFlRVdvUXRyaGY2dzNtcEFVb2N6Tmt2MmJIZ1lTemd6UUoyM1ZsRnhmT2NMTEpoZE1hbXRiT3YzcmQxWE5RNzNkNUV5QUwzR1dDRDhhYi9kSEdyVXVnMFdvMVAxZ2M3U2daWitkKzVaVTNkRDJaK2RHZWxIRm5JWXl4L25qREg0enlBUWJJNEdndUtBMklNaDU0LytaSjkxTlhVazhBSURlcnFiUGMrVC8vN01zbklpWXRvYjVpYTVIaXVMSEJQR3dja2NBWXBXVVN5U1JTTWlFQTlyVWkxUm9WaEhBSUlOK3ZBRTJJWVNXM3pkV0ZCV0FxdnF5cE9ESmVmK0pic1RqLzQrcDNiTWNmZDZ4Q0c0elEzRWZadmw3MGdmSVpXMXhMZzBuVkhTSzQ0MDZOWkhaMlhjSFk1eTUvd3VYZTYrOTZSNnBxZklBQTFzc053SWFJMkIrdGQ4NklsRXpUZUZVK1hOL0xrdjBscWpGUVpZN3FvZUhXS3docEhJS2g0ODZ5UnVTdTM5ZGFxenc3aGcxQk94NHZIOGh3UTkwbVZ6TUxJbUtNQmYxSmJ6RGhYODN5UXhLSDRleEFxYlF3Z1BYdTFxZ0JDSkRHRVpIcGRIRWhvdVA2eUlWSzR5eDJ0SGkvelJQQjhIM1BwSUhMc3FsdWVyajlrbFBEVjkweG5PNWZweWJVYXlBNmUrSUIxZ0JVNkM3emx4aWpUS0prR2pFVXdoSEM3VG1lL3pVaFZZQXNIWVBiRzVGV2FScXJKRll5TWFRUnNKREhacXVnZHJPMEh2a0RsS2NlektlQ211Ukh0My95MHVyOXZyQWtqZFdMQVFBNzY3eE5qd1kzVWhlMTdKc05pb0tWa2JDb1RnTW9DUzk5bWR6WmkzZDMvYTNCMnE5KzRaK2NHdC81MW52L3FEYXFZRW9iUnRwTnlUOXd4em5PSnhHdFpnQlVIdm5TOFBma1VZUWw4TFNJMllJZW1MOVBrQ25IS0pvVFl1ZnV0dXlKd1doOUJaVEdrWlZEdXFvOTQ2UTZybWlHSWxURFRFUUFydDhQK2tOMnk4c1BBQ0JsRWkrbVNrbEFhbWVtTFdTbk1XeUVRRmtFZjlDZmRLb096Kzh6eHBmVEEyYWhsSUpHQUFERU9idXhjcDdiTnlLcjFPaHdNZittbDU4QVVFQzYxdW5xUzRQTGlsWjJ3UHI4MXd5WlpCWThUYUp3Y2JnNDNGM09ENU40YWN6WGhGUVJnSEhoQjRQQlpITzBmbUl3Mm5ROW55Rm11UXp6VFFrMkhhdjJsKzBzOUFwaUw4L2RSQVRtWXp0LzVmSHB6M3NjREVHV205NFVJRE1EaXRXZExITjkrWm5xL3d4UWNaL0lLczBBQVJoVEw0ZkFRUFY4OWE5OGhjQVF2TzF0c2VjcnBUVkgvZ3NmLzVsRUphN3dTck5lQU8rMnVpVUNjdHlnMHkyUnhxRktZMnRyYjFPM3ZoZzJ0TDlTc2VFN042Y01XYkthaDh2WlRZYXlQMXgzZ3g1Vk9xMlN4dnJlWmJ2R1JqZUppSVRyOWNjYi9kSDZyVXNnR1FvWFI4dnB2bElKVnBGSEZZYXlxTkhpSWtSRWpNUEZjbjU0SEM1eVhIKzRmcUt0K1hJdmt4RHVMMzVKYzh6bUhRaEc2ZG5xM2E4NUpjTjVZWER0ckZrTjJGUUhaMFQxc2UvR3E5bEI3MFJheHF2Vi9IQnh0THVjN2FmUnlwaUdHL2FydmhoM1hML1hIMjhOMTA3Mnh4dXUzMk5jR01xNlpsT3dZWHJ5eHZ2OVlSRTJnUWZ4czQ5UGZ6SHp4RlNDVi95dFBoUW9rUXJoeWZxZnlXUnRka2RBQkJxS2QrdVBtYkljK3dHcTNTeUx5dVQya1VkaXJVa3d0a3lPZnVhM3YzdVZyckkxTjdJR3R6MFdpTXp2Q253SGdHZzVSMllmSFdVRFlvdnpPdFlEeU9aSUtQNW54ZWNoSUFMREpKd3ZaL3ZIcWwzRS9uQzlOMXkzV0FvdGpGa09ZazM3bDh4cHlDQ3kzbWg5T040NnpoWFVlY1hoZkg2MG5jUUxCTEpDVzIycHF6cWIyMlJMUVdUTEpNaTRUTUw1ZEZmSkZMb3VJWnh5RDE5T3ZteW9qS0VGdWYvdDhUVGJlNEdJT2c2OWtYamt6bEVZUmxDZ1N1c3Z0SkJXRFlSZ0xlcStPU1dySzB0RVJJYmNHQzJUZUxVNFhCenRybVlIU2J6NDJyMDRqQXZYNi9WSEc2UDFrNFB4cHV2MUVSa1JFSmtHVFhQZ1J3WVo5M281V0NLaTkxNTZGNElpWTNtalNxb1Zmek1XeUd3dFZUUXRGdnlOSlVoZ3ZaaFJ6ZFNMTFpWLzhVd1pIMUYrcmtvRzBCck8zS1llZWloSkpRVE84TG5kei96OHgvNkcxUy9NVldxdFZDUkR4M0duVnBLZ0pIdFJUU0c1ZHNPcEd2UVdYQzU3bFBHcWpZYXljNzBRMHpoY3pRNU10U1RidkR5LzN4dE5Dc3JhUFlKeWRjMmVSQ0FBa1FIRW9EY2NycDN3L1A2eDA4bXVYaStPZHFQbGxJeEM0RVYxMkdEVThoUGpuSFBIa0NuQ1grM0pBeUF5TGRQbGRFL0xwTE02Qm9CRU9kZ3BDMlpnemkrOFovYUwvSmFjcjNhM2YrcWI3d0ZLVE9FWXJUZWxCam15TzRVcXdveHdGbmZuZkVBMXBGcFNOditBdVRneVkweWFST0Y4dXBqdXJHWUhjYmcwK211MWpZak05WUxlYUgyOGZuSXcyWENEQVRKT0ZVek5pY0U0SDA1T2xHMzg0dEhQUi9vcVl1Vm1MYU05RFFGaDA1U1o0ckhzMTRianRHYjA3RUphcnhqN0w5VCs1alZpOWFJMjhQQ3IwdUZJYWdPQkdIL3kvUHRuMFQ1RGptV3ZtbU1IQk5RKzNEZTdwRXpBbEZFWkNPWEEyZFlzLzYwQmNDd2orU0pNQWdEQUdGTXlYUjd0a3psV0RsMXYwQit1VTducnN3aGpzaXhoWVplSXN1TkFCdU90WU5BTXJyakpaWXdPNTRlTDZhNlNTYmJ4MnVwQ2FWeXJDNUV4SWFMbDdQcnpYNzcwNUdmU05FVHUxQ0JBL2hnQzBIeTZGNGZ6ZHFWc3VIYkM2dzA0RjVRMUhRQUFSNEgzd1FzZ3Nna2pRMHBDSnozNms2KzlMVnlGaFIwcnE3SFVFcFprelFhN2MvSlE2a0lidEJEVXdHME55dWE2RGhrWms4Wmh0RHhhSE8ydVp2dEp2TkpmdXpReTdyaEJmN2crV2o4NUdHOTV2U0hMU1dISW1ONWd2Y3pmdWxKN2p4NytNODVyOHowRG9BbE1JUU1Hd05RL2FMSnVJaGdFamRYTlRIaUlXYThUR0FDZFBXeC9MZ3UwNTV5WWkzclZFZ0JERUNmd2lvY1RLWUdBcEE3LzRZZis2c0FiNTRSRnNNUWoxeUhjOFZqT09zM0xjVHpoK2xTYW4zejRiRWhXV25Tb1JyL2h3QzBldytwbUE5UmhWcTVSeVdKNm9GUlg5dXVzUFg1dnRIYUtDMEY1NHQzU0VsWjFHU0l1M0dDNFBwaHMzbnI0TlJIRjBYeHh0SmZFU3lCN2wyeXBQRzErSmtUZzNDVXlPeGVmdnZqNEp3NXZYQWhuK3hjZisxUVNMaGdYZFdDWVcxRUVpSmF6Y0RGdFZDMjRjSHFETlNLU2FaUkdvWkt4TVpxTlJ2L2oyZFJ6c2pnQW51NWZPVFYyVDYvNUt0MUhiTVJTTnNoYXI3Z1pFMmZMTFJZcFpHd1JCYXVyUUxZakxwdE1NQVpBaGt5YVJHa1NJWExodXE0WENNZjdXaUxqQUFDUk9aN3ZlRDRSS1Jtck5NbE9ZaXdmK0wzcmYzbWFQdTh3eUN4aFRhdlh6MU1wU1VMVzdySHlTSFBHSUpOclkwQXB6TEF3Y3VBY0dFSVpnNTJYVnV5MXFrcXdxSnZWVjlvQ0xJTGR0SWFORS9yRTZXUjZJQUkzK056RjMvL3l0Yzg4Y09JaHBXVkpYanNpckRnU3JBT3RjZUVNSmxzeWplTGxYS20wNVQ1RnFCcVYyVWFiQ1pySHZGUnZGYzgyVTZJZ2FCVXZqL1lHNjF1ZEI1SkN2b1M0TlQvYUphTktoMjFHSkVQRUdQTjdvNkEzdk1YWWwreFNNb21XTTVsR2pISHJKUFphYy9OaEpFSkF4amtRSEc2ZlA5cTVITTRQR2VOY09BUWdrL0RDWXg4N2NlZEx0MjY3VDBsVjQrV3NLTVFrV2hDWjNuQlNFbE9VdjdsZXovVjZXaVZhcG92VSs4ck9jdWdoQUlCUmFqazljMXQvczgrV0J4SnpDYXlKRTFRVlZmdDFjdXJYb2l2YWNsdmViRDVrajJVeG9jVGlLeFdybENaTlFwbkVpQ2hjei9VQ3grMCtuK2pXTDBSMDNLQ1J1T2t3dmZqNDBYc25IZ0JZMDdOaThNdWJqUTlZaEk1U29aRWNBYW1FM1cyMnN5MldDelpkSkZLQjcvcDlUNnhOL01tNjdJOFNkeEFqZ3NNQUtYOHhLOU9Vc3k1VDAzWlFqb1M5c0kxdzU5M3FhSjhUMFZxdi83N0hmK2wvZTlmUEt5MGJQRkZNblk1RmdObmx1SUZZOCtQVklvNFdRS2FMdjdIMnFhQ0NwWGt6cjBVaGRZVzBrcTNRTWoyTENHQ1dSN3VEOGRaeGRnd1pINjJmQ3BkSFNiVE04elVURVlMbjkveGcrRld0L2htdGttZ1pSMHNFWW96WGViSzhLaTNEdVFCZzRXSnY5L0s1eGY1MXhnVVhUb1orRVJHWUlHMXV2UENZa1dycjdJTmtORUdWUzdKWXkyRkp2RlF5R1U3eWJRQk42OEdGeDRYM0R6OFJ1MFdRSU1sb05RKy84enNmNEtvOFFMTm1sOHRXa2dVTWN1cDNycFJscHFTbTB4dkNhZU9ZZXJJTWdBYVpHUElNTGFna2trbUVqRHVPNjNnOTRicU1mVTIyMGI0ZVBmeFhQUWQwRTVJQUl0Z0gxclFoU0VrQUlVQW04TXlUM3M0TmQzNEV5MlRoT1lOSHpyN2oxWGU4NWI2dFZ3emRpZWYwbEpiU2hISHd3bm40enkvTVArandmQU4rWG1CR1U4cmpVZEVpdW4yVmxXNmRrbHIzR1NPQi9ORXJ2MzkxZW5HOXYySGx4cTFVWEpwRVBhS2JtdzVFREFZakwrakYwU0lKbDlBS2dXZ0JJbW9Qc1kyR1M2eFE5Y0V5bVVSbU9kMDdibjlNMXA3K2NCMFEwbkFGUUZ4NGZuLzBWVGsvSWR1MHNacVZpVGJyN1dpd0xnQUM1MDRjTG81MkxoMWV2MERhY01lM2duUExiUXVNSVc1ZmVrcXJkT3ZzU3hqbnBZNHJWUkpEUmxvdHBudjkwYnB3UEt3OEpzVWxEWHpqTDgrZlBkU1pEVE54bE41NDl2cDczanBNZDVlcFV0cW1jdHVDMlV4WWExODk0ckFSY1Zyd1Z5VzNEU041bkJ6YS9TL29SVVNGdzhyMSs4TDFiMlZyLzNFWEFTbVQvcnZ6cjFySXA1dktzZlFMdDZNcDdXY0loQWU3MTlrVFh3N21SNTdCRUJEZS9mRDNmK045NzM3ZzVNTXU4eVJKSXBPSEV3QjZ3WVFGN0FzSC8rcHpCKzlaNmZPODVJRjZGUjFDV0crRzY4Rm5QeDdjdU9vSlFZbGV2ZjNCUC9zMzN2bWVWYnFzTjU4QWdJenhlcVBPcmZTZGwwcmpjRG5UV2V4SW14T3NSdFFtRk1YUG5UY3RrbG5TVE5RYlRyeXVBMHVLeHlsY3poREI3NCsvS3Z3cFpaeUVDeG5IUlk2L05qOVhueEVCdVd1MFBMeHhZZS9LODFxR3lKMXM0U2RuN1F4ZWwvdW5nSWpRNk5UdnI5MzVzdGM1cm0rdHZsam1od2dSKytPdERpRzhNamZmOUI4WCsxSHBCVVVPNXVVbm5IZmZIWDdiUGZxT2dVa054QkswQlJjdENoWWtybVNzYlRZN1A5dUd4TmFnRFoxL0V4eGIvWnF0Q0JNWUFHUmNjTWQxWE05eGd6OUVMQTRSUGIvOHdHOWMvVGFBS3Jsajk1TXRZNWp4b0JCdzlaTDN4QmQ4WTNoS3MvWGVtYi8yem4vK3VyTnZWeVpOVkZ4ZzFmd3l4cXlkdUNQanA4UDB3bis2OU9aRmVvMHpxOE10MDJmWGJsL0lJSW5ndy85ajdEaGdTTTNpMVlmLzhyWWhSVjJVSndEWDd3WDk4UzJTaUFoa0VrYXJtZEZaakRWcmJVUzFSTWxTdXRZZXY2cWxWcWsyZ3MwYjUvZEh4MlZ0KzBOY1pGUzhXc2JSUEpzaGRQRncrUWtSZ0hGR3dHWjdWNCsyTHk2bnU1dzdMWTFUNUNiTlFrSHlMaElpYXFNOXYzLzdnNDhFd3pXakpHU20zNEtCUUVCQVRSTkJ4bHhmcUwxWVFJbmxpRFN3ejF5VGYrM2o3aC83cmVESFB1Wjk4b1p3T1l3Y2NvcXN0bGJ6c3hyYXd3eFFvMzNiYkZCZHRDekNFRFhlc3VPU3EzRHg2bzFNQ3lCa3EvOUdwL0VxbkI4dGpuWlc4OE1rV241VnEvK0llSDc1TVdWazJZSnFPYjRlS05OY1VnY2dBT0hBdVNlQ0wzM0dBNFRFVEY5MitvMy82TnZlKzVvNzNyeEtaMGtlMkZtdW1nSVI5WWZySldPc3UzZi8rYnUvNUlzejFSSkY0ZWdvMTVTcXVvbzc1VCtqSVJqQTZkc2xFVEFRUnNGWHJuOUc1RHhVMUZoUkhOTW9YQnp0UnF0NVd5OTNrUVZjdnpkY1Ara0ZROGhYdVVxTVdSLy8ydkMwQVVOTjdXTHpQZ0JpdEp6RjRlSkZtL1NpRnhISmVEVS8yb3VqUlJZWllzVzFOaUFvQUNBeTVFTEVxOFcxWno5LzVablByZVlIOW1iZlBIeVVxdGVwUU54VVZNY1pUNkxWcFNjL3M1cnU1NDdEUE9OYnNSYkFFQkg1My8yN2Y3ZlIxdDM5Nlg5OC82Zko3VEZ2Z0V5QTBRQWtHTHFjamhMMnVSMysyOCtMVDIxelJMWVdtQk1CQ1lhNnZwdXpSVW83cGhZYmozUXB2Mkx2SHpiT3dTcTZYNnVoWVF5cndnc0ZrRzNoUUNCU0tsVnBuTWFSU21NQ3c1RGZTdFQ0Si9iLzNsSmRMZ3V1OW1KZ29mckwrMlI5QldBQ0R2ZndzYy8zaElOS3A3ZFA3di81Ny9yQVdtODlrWEVUVVFNQWdPTUd2VUZ0YTdiTCttdnVTeCtmL2laSG1kZElVQTB6MUQ3WEl2QUJLUGUxNHU2Mmd3Q0NRZCtmdk9YZVA1YW9xQ1NQMWZ6c2RaSnBwR1hDaEhNcnljVVEwZkY4eHd1SVRKNEpFc3ZSdE5MY0ZMU3JEVzVSaGwxZSszTldva29qbzZYckJyZSs0TjY0dEV4WGk0TTRYRkkxKyswc0NyTktHUmN5alErM1g3aHg3aXVyeFpFUURpSXIxWXdGdWFsOEtZOEdzaXhJc2RDdHB6dVhnY3h3L1l3ZGpWRVNvWFVtTU9COXA0ZWI4K2VldS9wMGIrMlVPUGxTc1hZV0hKK01BcU1kVGc0SEF2allWZjdScSt5T3Z2T3Q5NWx2T3B1Ky9wVHBPeFJKa0swVWtRMmVvY1pjdklaZFM3WW9GcFJzYUZDOVEva1lsM28zNzNOcEhpdklXa2hvL2lvclhKbHBHc3MwWm16QlhWYzRudU1HTitHNTdlUXB3SnArejRvdkM2NDBVTkcrN0VwVCtNSW5CcHdqSUxuQy95dmYrQjZsVTIxMEszWU1BTkNROW5xRE5wTzlaUFJILytpWmYvMWIxLzVzVUtaZkxMdUxkZnJhaUtFQUpGN2ZNQ1FDRk53OXQvdkZrbVhzanRqcW5DRlRNbDNOOWgzWEQzcmp6cU1YR2hjWFRuKzA0WHBCdUpwclpXVVdLN1IrU1NscmNNdG0xbWxYWFJWeE1xbU9veFVnNjNlZGpuVHp5eGlWaEtzb1hMQXMvTm82SE1VaVF0RklCTVlFRUUyM0wreGVPWmZHSWVOY0NJZnlreEV5SHJXdFJvRkl5eEFVS3UrV1JTSXczTDM4akZUcTVObjdPWGVwRHU3NFQvM1ZIK1dNWVpaL0dnQVFQRWVjM0J3L2RXbi82cFVyeWM3enRMakdqR1pPZ0Y0ZmtZSFJpT2d3RWd6bkVqNTluZi9HZWZFSDF4M08yZTBEbWdURWdYUmQveFZhc1lQUzFyMWlzbE9EbDVWcWhTWjcycW8weXpOZjZscnJBV3pYbWQvUFV0d2JKYVZNWkx6U1NwSXh5RmtqdTJ1a0ZoL2UvanZPViszWlFXUncvYUszZDhOaERKUUozL0hnOS95Smg3OHZWYkUxZ0ZqOEF5QmdndmU2anZnRmdEUHNGWS9OL21zTSszOElLMEFFMXkrNmhvQWhreXA1eDB2K0ZLK0ZZa05ObzJVdFJ3QUFyV1NXVFZnNFR2ZTQxUzh1SE5mdmNlWm9KWWswVnBTM3g2WEJHTGI0VmFxNTVJVHNsVXh4TzM3UEQ0YU1mMVd6ZWtyaVZUZzdrR25NRUMyQzI0b2JvREJnakF0Z2ZIVzRjK1BDay92WG55TkQyRkROYUduMnZLWGxmZHN4MUxBRUNBREllTFE0WEI3dGpUWk9DOGUxbFF6LzZiLzZZMmtjeVRReXhuQ2V3N09YbmQzOG5yZDkzUnRlZXZzODFzK2R2N0xhUHE4T1g4Qmt6cHdlODBmSW5NeXFNZ1JQRUFCY21PSHZuT2UvOVlKemZzNVBEL0cyQWZuQ2FJTlVvN1hkUkxESHRRQ3JkTXhqdG1XRjFzM1NmaUxrU0JXdFlxMVhxa2s0RnQrcmpabGFwVEtOWlJ3cUtiTkJ5VndVenl4Ky80bjVyenJaRU9iVGdPS0QvVGxmbUxkL29xZSs2R3ZGR09MK0l2eTU3M3B2eit0cHJjaFFjZnAxMlJja29OSDZTZXc2d1ViSmREbmJCeFRQeHgvazdRWTAvcldhZ1FoWHpudGtBQUdWVWErOTg1dldlaHNOWWFqaitVS1JJd0tRVENLVkpuaDg0cG5hcUNBS3gzWDlIbWlqVkFwZ2IveUJ1b2Z0WmxKZFFOck12MEZjdVAzeFJ0QWJmVlVTcUxVTTU0ZEpqajhaVkRDNG1ETllkZ0dSTWU0a3ErbjJwYWUyTHp5WnhrdkdIY3lzWG1FVmFubHdzWndOMmx4YXR4VFZSQ25uVE1hWVRLUDUvdlhCMnBicjk0bDBOdUhpUC92WGZ4SVJqZEZLSm1rY0tTMEJpSEVldU01TGJ0LzQwMjkrNlZ0ZmNlYzhOanY3czZOckYvWFJlWmhmQXdJV2pOSHhFSURJSUtERHdXRndsT0xudHZHL1BTZStjdURFaXA4ZDA1cG5FRWhYOXJra0JEYmhjLzBxRlNkaFkwN1lGdUJhdnkwTDB5aTJwblJiNWdnUW1XMEIwaVRVTW5WNTd6Tkh2M3dqK2dQR2FqNlBLbmJVL3B5TEhnQUM0N1I5ZFhqNU9jNEZKV3A1NzZsSC9zSmJma2E0dnV2M0hOZEhSQ0tkTFI5bENzRnhYUy9vZGdCR3E1bFNrY2ZYbjR6ZkN4QlRCZFlCb0hMVjVGL3RiRFFBUk1BRlhEdnZrbVlBb0VtOTdNd2J6NjdkYTRNeHkwYVZoS29rRXhHTlZta1NhYVhFY1lsRDZ4Y2ljL3pBY1FNaXJhV0Vrb3NiQUtjY2g5YVZUeDNKSU9OK2Y5Z2ZybjlWMmErTk1kSHlLRnBPdFZKbEFwaFNVeGZ6MGp6d0ZSRVpaMGFwdmN2UFhIdnV5L0Z5eXBqSVRqSXQwMnNXL1NvY29kWGQrdlN5Vkt3MXcxZ1NNN09IekdpNTJOOUdMbnFqRFNLRGtCOFNDb1hHSWhtdlpMemlYSGpCVUhnKzUrSnRENTE5MjBObm43NTIrRzgvOE5pdmZQaUozYXNYK042VllMd2xUajRvSm5kZ2Z3TVFRYWVBNERMamNwU0dmdnQ1K08zbnhYM3J6cDkrUUgvYnZmS2VvWFpScnhUb2FybVdLaTlTWTQ1cjJYZ0EyNTlidmdpbFFyVmV4b0loeXc3Ym5iZW5vMkN6WVBiVkJyRUZlU2xOSWlabjE1YWY0NncxRFN2ZnREUmpXUzRCQ0E2WHpobk9PUUJKcmIvdnpUK2JQYzhZWnk1M1hKK01VU3FWU2FSa3JKWHFQQlErYTVsTVFrUTJnVHUyeEVPNzZwUDJtaVFSV0MwSFUwejRNaXJrK2RvNHVENFVuaUE2V3UzWXMwbXlabWhkTk1seFBnTElaTFZVaWVzUHZWNy9Wa1JST081Z3ZKbjZVUkl1bEV4dHNGWkJBR3N1YXYyU3IvUjZ3VERvRDcvYStDY1pSK0ZxbW1kK0tNVFBpZytwZERvaUlISmo5SHoveHU2bFo3SlRickFLOENoWDd3citxZGdUYTV4UUxyb1VnbFpSc0hsbEhoQ3VWSHIxM0JlVVRMWnV2eC9JOEovNTZaKzB0QlJtRU0wWUk5TTRqVU90SkRMR3VkZ2FCZTk2MVYxLzRodnV2KzNrK3ZiUjh1cTFuZlRnRWgyZXAyaUtpTXdmQTg5Z3JtRUFqa0NId1g1RUg3bk0vdXM1NTlGOXArZnhzME1hdVFZSmRHT1hiM1ZWcXNOcUVuVThVWDJ0R2NtNng3bXQydXZvMUhyRzBwRldhWWlBOE9ud1gwallKV0xabmh0Q0pBdnZVVzdlRWZKRDUvUFFVa0o0NXRGQXVBZ0FnVGY1b2JmLy9iNVhXM0ZHUk02RjR3V3UzOC9XTURzcHNwb2ZHcFVDTXNIY0E3VjdWWDQ4ajJ5a29sSUxKRk5HZ2V4bWNaOXgyTHZtUkN1R1NJVG1nYTFYUEhUNjZ4c2Q3eUp0ZWJka0RNd0NqRlVhTXlGdXpUUWhGNDRiOUJDWVZpbVI3b0NGUmZrWmUyYVpiTG5qRHNZYlhqQzQ5ZXlEQUtDVlhNMFBrM0JSS2tVTDFwWXR5cnZKa1NQankvbit0ZWUvZEhEdEJhTWtZNEl4bXdqRktjYTVYTm5zUkxXdGcyaVJxb1M2RnVTcVRZL3puNUF4c1R6YTBXa3kyam9qQ3Rka0xVdHM0VVNFTkFuVE5PSkN1RjdmOVhyM25WcjdtVzkvN1k5OHl5di95OGVmK245KzcwdGZ1YlFYWG43TTIzNldqMDk2cDE3SzErOUd0MGNtMzNydmNmQUVKZHI4N2d2NGdRdk9LMDg2MzNTV3Z2MStlZDlZZ1ZHeEFrM2w2YUpZZ0x2U2FwWGF4aDZ0OGlmSS9DbzFoVjB6Z0hXRFZhamMxbFVydWRMUVJXUG01aWhVNXhFSEJKUVByY2tOU0tramlERC9pMVRzaGtPVkFpbEFCd0JoZlhEU2Q0NE5wMEprNGhnSjFFcVdSMVlod0taNHFUTGdzWDV1OHlpdjJ0TFp4WFFyOS84UklwSWhMcEFNSVVjZ1RGVlUwTlltU1plVkIwdkhsOUd2eUxXV3k2TTl6Ky83dldGblZxaG1CNEg1L1pIakI4bHFtU1FyQUxKeWdWUTFJaUdSNFZ3RWc3SHdncHRQR2xzWGhjdDVHcFZIRklQbGhHOHhBaUpqUEkyWGh6Zk83MSs3aUdCWTVyK21LdmFsSm5WVWNoeVVZMjg5Q2RsR240emdWcitnck5xeTkrVmdJU0J3NFJ4c1gxUXlFU1V0cUtBNVZueWVGNmVsak9RMEN1ZWUxM004ZnhRRVAvaE5ELyt2My9peVR6eDE5Vis4NzlHUFBYVjF0bjFGVFcrSTNvaXYzK1dlK2pyVzN5QUFNQXFBT0VMdi82M3R6Y050TzZyN3dMVnEyUHVjYys5OXM5NGdwS2VuQWFFQkJOaGhNalNERFpnQW53RVROemFoYlV6aXIrT1krSFBjdGhQc2RDZGZKL2s2cHQxeEo3WWhOZ21PcDRUWUdFOWdCd2NCRnJJQlN5QWpDYUhoU2U5SmJ4N3ZkSVk5Vk5YcVAvYXV2V3M2NTk0bnZTN3B1Mitmdld0WHJWcTExdnF0cWxwVk93TWkvY0JadU84VS91cEQyZHR1eXI3M1Z2MXQrK285bVpyV29BeFFSMkRrb1FaNjBwc3lyN2t1aGptS0dnWnhnTjFVR2hUYmVaRitxWUJqZmFFR0pVZzBNTk44NXRyNkh0MzNaTHB0dkFEdHFJR013ZTRVQjRsWjhwdkJXeWFsS2tBZzBrMjBlb2JMQkdCSTlUMkt6VVlVbDFkMmMxM0xUV0pBVFlBL0FnQ2g5cHlrUUJVRERuUUZXNWZFaWdrZ2xMTkpXYzZHbzUyRHBhVzBmZk1UNTNLMFkzZFdqYWFUZFYyWDFLTkRVNmdCd255NE5GemFkWVhuZlZGWlRLdnBacnZEd3h2K09RMngxOGlaMGZyaWlTY3ZuSHhDNjRvaEIrVFFzOHgyS2RwdFFzN1lpQURhUUt5bXFIYnJQTFZPVUJzeEE2MTh1RllSbktkOWVRQUFRc2lOUzJmYWpVK2RHK3Zna2lQM0NJMEdsN054T1JzTG1XWDVhRFJjZnZOTGpyenBKVWUrZGZMU1J6L3p3Ty9lKytqNUM1ZHdjMjEwNFFtMjU3RFljNlBZZlJoRVJrWUJHUVFZU2lSQm02WDVqWWZoZHg0UmQrMFg3NzNkdk8zRyt0cEJEVWJOTk5QOThDL1F3T1JGN0UrQ0wwWmdCd0JkMkZRbldhSFErQTVTWHhUWjdiTUF6WTZqNWhSQkd5Vkl2Y1h0cW1ocWFNWWJCQm9SU3oxVHorcUVqaXdmY241TlhSYTZya0RqMkZ4eWJTcFp4dzU2TXREVzM4Y3hFaHFsR2c4YUNZa2ppelNtVSthT214aXhpUkI2UzQrQXdJQ0FacFBMZFRYTlJ5dmJqSndXV2I0aXI2bUthVG5iMEtvOXNJOElzc0VvSCswUTIvN3llNU9NMXRQTnkzVTFBMEJuLzFFbi90NzJSV1FjeUV4V0w1dzcvc2hzdklhTWN5NDdpMlNQWDI4ek41NEZJcmE3V1cwWUdyVTNIYS9KTG96MW5PdDFxT3NlbTZmM0xIcUpZVUlHbm4zbyswRS9qOVNRd3dCSXEzcGFyeFhUVFpIbHcrSHlIZGZ0L2FYLzlZMy82SHUrL1hjKy8vQW52M3owa2FmT3Nza2oyYmtueE1vMTR1RHRZcytObUM4REtUQWFFQVVId2NHUWZ2QWNmTzBNL2Z2ZDJldXV5My9nTnYyeS9kVUsxck1hU3VQMnZXZkdISlJ6RjhqdFBjZHcrSExtK2JIV0F2ZTJ6Z0p2RHd0a0F6MGs1RUJnWUFCZ09zenJvM2JiRjlvUGp4cnJraUFDbDhpRkl1S0lkSGw4YmxxTmQ0MnVlSlVaa1FrNUVISUFCS1RONWNuakNKa3hPWFFLQm5ZTjJlNnY3Nmh2WU5nWVFBTktHZXZXbUlFY1dZUGpNSzJST2Q5LzY0eldISkZvTWpDdHF2SGF4V3d3SEMzdjJzNldUa1RNaDB2NVlEQ2JiSmF6TVdOeXNMd2orVEdmQlltSVpwUDF1cGdabyt5Nmp2djFUOGVxTmtFcmpFMDJMcDk3K3JIWitubER4SVNFOWhTRnpwL3ZTbTRWQ1oxN2dhUmhIN2xoM2N1bUU5ek56TTFDZEJDcGllQ1dDd0RHS0FCc2o4SHZXT1JvWFQ5UmdmMjFEVUJESUNLdDZxcWNWdVVFZ2ZidjN2R0d1NDY4Ny9VdlBMUnY1Y1NsNmZtMWFUSGVnUFZuMUtXak5GMWoyVEptSzhnbEVEVzJRbkRJT0d5VThNQTUrdVNqK05mbjh4bmsxeXl6YTVlSVNCdUMxclh3M004Tzk5RDlHVHkxdW9iMmRYSWZXRmUzVjhrdzlXTjY0cGo5OWVTamhPMW9qN3JJVU93RFJkc1JXblBIL3VYU3JGK0Fjc3lRd2FSY3ZXN1BIWGM4NzlzVGRXMHpJVlJRZnU3aS96NVRhd2FnT1Z5ZmtJaE1VNlBwaUdrbWFCb3VOODR6MHVtajBNU3hFTkhMajd6MTFtdGVTTDNDZ2ovNlFzL3dvY05KSjVzZmRJYUlvRlZkbFRORTRGeHVhemNETXBrTlJKWVBSaXRYZFBvMUFKWEZaRFplcllzWm9FVW5qMXB3aEFhWkVLb3NMcDQ2ZXZyb042clpKakxPK2tPcmJHWUV1NmJudDUxYWw2YlI1VTZqK3NYbGJzNjNzMWg5MjYyQW9idFc0UmsrbzlWd2FlZDF0NzRNMXk2Y2RQMVl6OGx6OE4xWFF1cXB0TTRSWTF4bStXQzB3a1EyS2RYbkgzejZWLzcwZ1M4K2NMd3NhNW14YkRCZ3U2NlhlMjlpZXc2akdKQ3BtL09KbXJZUlFGR2pNbkI0bDNqM0M5aTdiNjN2MkYwUHFaclZwalNBRU16ZnRMUzFVVEtCcVhhSGsvN1BhRHJlODE1Y3puVVZaYmowNzg2L2RaM3VaNVErb0RaNnNiMW1ERGN2NHNQM0dDRzVNck5SZnVqVFAzbDhjUW1MMDhuWnc3OTA5QlVjaGYxMGhWOXA1Nk82VVFEUStneGYvM051YWdRQVk5UlB2ZkZYWDNIRDY2bVRPa2Z5UEk4NmFaNUNqeVA4U3dSYzhNSFN6aXRGdG0wbVZkZkZaTFdxQ3VZNW55NnBuV1FpTWlTaXRiUEh6NTE0M05RRm9uQTFwNGZNVnNlY29XQ25TOVpsYk52VytJT053eGx3ejc3dmd6SFlPLzRTRnBEUkdwSHRQL3o4UGRmZXpJVGtQL2tUUDhhUUkrdWpSekJzR2RxV0FiVDkxQkdCMW80Z0FDaFZWc1cwcm1aRHlXNDdmUEI5cjcvajViZGRXeHM2dlRaZFg1dVkyYXBaUFdaV1R3QnBscTJnSENMbkRjSTB5LzI1aEhGcHZuUkNmK3B4ZnM4WmlTTGZzeXdQamd4RHJadGNEcWV4WFpCdzBEcE1yUWZmY3lRMG1hNmo2MkpzWCt5QUxaOVdKMDZVOXpBWUVpQTFTeFNFN2JYelB3VFhCdk5sUHI2SVZVRUNzL01iNTE1KzNSdjI1UHVVcVJuaXM5ajcvK1hMLytueHpjOHhKb21hNVJEV0hLalhmRFhTa3NRQW1FY1lvVmJpL0pNTm14bm44bnRmOGc5M0RuYTIzV3hiYlNmandXRkw4QTMzZ05XZDFmTUFwQUhicXBnYU1vTHg1M2pFZ1p1TTF1VnNQTjFjMVZxejNzcjBGeDQySUdNTXA1dXJweDkvNFBMWjQwQ0djZEhGdTdTdWV1c0h1R1YwWXh6YmJoL2NyS1IzZnE2TGc2NUxZZFhQcXJmVlJRQUFJazFrZGgyNC90cGI3dHA5NERBQWtOSDhKejc0STFyWFpEUUNzdWJMZDRGRVk3OWM0cmE4ZDd0dFp6U3JPa2Fic2l6SzJkaVkrcmJyOS8rZDE5eitqbGZjQ2d5Zk9ydTV2bG1vYWtwcko5VEZvekJiQmJuRUJzdklNNEQyQ0VIR01CZWdEUnk3YlA3a3FQblVFL3laMlhEZmNuYkRUaHh4VU1ybzFqOXdWUWdjZW5xaUxjVTloZDdUbnV4ZXZCd1k3RHFCRGRtT3I0ei9jOFoyQUFrQVFjUVJCUkVIRUFpQ1FDQUtJb0VnQUFWQW13ZUFJd291OE5KcFJHWXlLZGFubDE1Mzg5dW4wL1htNUg5c3BXVzdZdm9iVC84UUVTQklJQTRrQ0JpQ0FCQ0lUWTBOTWR3bEEwZ0FpbW9tTDV6UVpCQ0FWdkxkNzN2Wi8rWnRyWGIvYVMvUmswVHNqcmQzV2JjZ0lTTG91cXFLS1pFUk1ydWl2YmJKVkJXVDZlYmx4dDNGa0dUM053SWlZN0lzTnM4OCtlQ0ZwNzlWRmhQV0JVVzNXYkF0eE5HM0xvTWZJa09CNFBkaGE1Q1VHZ2ZyZXYvTFUwNnRhamxZdXZhV0YxMXorRGFaRDQzV1RRYitVei94WXdCa2pOSkthYTJnSGNseUo3VEhaWHB2SUFQMTkxbUJBRlRYVlRrYjE5WHN3TzdoMjE1KzJ6dGZlZXZOejl0Ny9Qekd1Y3RUVlpjd3U2UXZQbUhHRjlBb050aUZNb2Qyam9FUVNRaVVuQ2FWK2F1VDVsTlBzUHN1WkJzMHVPVWFzU2MzSEV4dGV2UHJDaEE2WkVURXgrWXp2Qm0yQlZGVHZVZGUvNlhOLzhDUUVmU0JhVzYrUG5xVStyOElRR1FHTy9EUzA3VldqRFAyek9yamgzYmRmT3MxTDFTbVVrclY1YXd1SmtyVlFtUUxKdVdiQnY3aDZmL3pzYzAvRWppeU90SSs2djV2eEtrWmJaTVZzTVlnRjVQeTBra0FBa1BteG4xM3ZldXVENVJxYXB2c0FscllpMTNucG56VG1JZWgzU1lpVlZkVk9RTWdJZktFcDdLTnBGVTFHNi9OeGhzMlFOOWQ2QTlMWkVKcXJTNmZQWGJxc2E5TngrdUlERGxQemlsMFZQWncxejl5aEJwZEpPelFIcnVJTnJDQTExMkRNMFN5U290QXBKWGluTzAvZk1laG0rOWMybkVOYUUzT0NhdjhKMy9pSDZJRkRqTEdhSzExVGRvZ1o0aHVHM3E2ZW9KU3pFRGJGTllDb3k2TFdUV2JITnladmVyTzY5NzNoaGNlT2JEcjdQcmsvT1hackt5d1dEZXJ4OHpscDRFMHkwZVlEZHNQd1JJQklrUE1CUmhqSHJtZy8vdVQ1aytma2hmMVlIbVFIZDdKQnN4b2JReFpuSFk0MTlIV2tVUGUwNWpZNExxL1NVQURIRDVVZkdaaXprTWJGVU0ySW9XSTJjTzFuVm1aSmc4aEVSSXkySDB0UC9kVUV3Q3JIemg1NzV0dSs0R2R3ejIxYVQrY3FGUlZWd1Vna05GTmNGSVFJNElBWDd6dzBUODkrNkVCMzBWb3orTzI3bWR6Y0RGaVh6VmdUd3dBQVlQMUM3UnhGaENwMHBNM1BQL2RMNzMrMVpVdWJkbjlmSnZiZHV2dDkzZDhDVTIvUzE1T0czcWxOWkdSdzlHVjRpRVpVMHpXSjV1WHRWSTJxaGNoUEhDb0N6L2hpSHo5d29tVGozNXQvZUlwQUdTTWdRTmNGdUt3MXcwQWQ4SEFNemc5VHRvM3ZkRk1XNVE3UEhPY1RrY2VtNFVpcllCbzEvN3JEOTM4NGwwSHJtTk1rRkdCNWNKVHh4NE96U0Foa1dFTWtRc3Vjc0dGWFRBaHgzYUNPekp0bWVKTTFiaTYycnhDcEFGd2tBK0dPM2JVUm56aG9aTy8rQWYzZmY3Qlo2cTZscElKQmlBSFl2Y04yYUVYc3BWckFEbnB1djlFTGdJUTFCb3FUYU9jdmY2SWZOOGQ4SWJycWgyc0tzcXFVQzFidkY0TTUzSzhRWTQzQ2RHQ1d5OXBUbGNnQS96TXhyKytiL3piQW5PeTQrQXVnTE5qUkM4YWR2R2dZUnJqNXZJejhQUTNGSUtwVGJGNzZib2ZmOTNQdi9yR04wM3JTV2NMdTBGL2N5Nk9ISXhrbGplblZCMmIvTTB2SDMwRG91aHd2bCt3Q3F1MjM0ZTJLeFJJd0RMMjJKZUt5V1hpakoxZFgvdU5EOXo3L0QwdktPc1pVQ2RIRkhaVHY4TVRvcWVkdkNaalR0MDVSeVNqdUJ3TVJzdlpZSlNBMGtXSjZyS2NqVmUxVnE0bjJUSGZHNFZ5emdCbms4Mnp4eCtlckY1bzdqZ3E1QklWT2VFWWhNZzRPZnVKR29oYzhTNkRvOFdPeGVwUFV5TFN1aDRzN1Rod3cyMDc5bDlIeHRodjlMbzJEUUFRVHgxN0NMMHkyb2ZZTmhzWkUwSkt6Z1ZqQXNCZFhlbHordTEwdHNQYnZ1ekZtZ0NRT09QTHk4dUc1L2MrZXU1MzczM3N2OTN6Mk9YMUtjOVl4Z2lZRUx1ZXgvZmVLUGJlaEhJQVpNaG94MklSQVU0ckVCenYyTWZmZERQN1gyNm5XM1lVVUZmalNtdmpxbUk0akhIdHArK3Mrc2ExMHlaTDh5T3plMzV2OVlNY2w5cFZ1VzQ4NzB5cDlWYWFXaUh1SG5GaHhtdHc3Q3ZUdWlEREZHZmlBNi82UDk3eG9oOFNUSmIxVkpIMmU1QUFRUEI4SkhjK1d2L1Z4MCsreHdCeUZBQm9JL1RhYVRqcUJtdlk4SVU2b2hxRlpZeW02L1RJNThjeVo5clV0YWEvK0xrcEVOVlZVZGRGWFpXazY5YjZXTXZkQzIvTU0wY3VIQjRtUklwSU15Nnl3ZEpndEhJbHdaOEVnRnJWcy9GNlhjLzYyQU92ajV5RXlEaXZadE1Menp5NmNmbXNWclU5SGNmclIzVFcyYjJsVVY5SStoVUk5RVlVYVJYdEtHaVhhSHUxN0Fvd3BJVElkdTY5OXBvYmJoY3lON29HQ0pURnFmMzA4WWU3WWxQZVA3VEdsekhHcE15eUZ1ZzlIVXlJY3R0RGlEWU9MbEVvUTF4WlhrS1pIejAvKytpZlBmamZ2dlRvbVV1YnlEQVRnSVI4YVJmYmRWZ2V2STJOOWdBUUdkVWJKd1FnS0JYVkJ2YU81RnR1d2JmZklyN3p1bXFGRmROcFdabmU5NGdJQStkT2tBSnA2Ky9YVlAveStiZHQ2TTNXRjBBbk0zVkhxL2kxOUVCSkFJenhxaHpEcVc5V0cyY1VBUlY2L01vamIvM09GN3p6RlVmZXZIT3dHeHVqMlF3NWlRT3hOWFgyTTJzZmZuRDZCNENTb1d5aXVtekpiai81QnNBem5pZ3k4L2dYWnROVnhRU2IxZVAzditaZmZ1QjFIK3I1YjB4ejJFZGRGVnFwdGczOTlyOEFhQU11eFptc0JVR1ErWEMwdEsyRmUxOGdUREVkejZZYjdvaHJYbWMxdzcrTkM2Zk9QLzFJWFJiSVJQQ3B2dlpORzFTQjNVZzZBV1hVNS9hek9PVzBscTlmd0FqYjM4dTdNWWJJN05oOTRNQ1JPL09WbmFTMHJTS0FxSDdkQWs4ZmU3am5nOC83SUJraWhvd3hMbVRHUlRNcnFGT2RCTTZkV0FFYU9sck5iT2diNVhrK1dqcXpVWC9xcTAvOXlxZi81ckVURndsWWxpR1NaaklYdTI4UUIyN2p5d2RCU2pJMUdPb1pnYUExRllxUWlidjI4L2ZjeWQvMWZIcmVzSUM2bUpUYUVHQ3pFWEN1RWpvMyt4bHFDSHA5eUhaKzR2TFAzRC85Zlk0WnVKYXZ5VW51NjExMzlnalZQR0ljRWMzcVNiVitXbTFlTXBQeDFKQStzdmUyT3crOTZ2WURkKzBjN2gvd0pXSzZ3UFcxd1JNUDFYOXlRVDJSc1ozb3hEcGpKMUFPWnZXdGlOcWhTL1hJbjVkQ0FnRHVHbDN6a2ZmZnUzZDVmOVNyQUdSMFhkZDFVWll6b3hXMG4xVkZSMWdEVXhzMDIvNGtJN0pSUGhwbDJSVXZFbGJsZERadURtN3oxTSs2MXYwNGdqRU9pQnVYejU0LzlxMXl0Z0dNTVdTOUpYUmExY0VnOUVDSEFIMkVaL0RUUWN1ZWlWN3IwVDd0N2tQM3FObEJRTWFvZkxTeTU5Qk5ldzhkSVFBeU9wYW8zbzlvdGl2TkpvNFNPbWJkNVVUUCs4YmlOaDRNUXlGeXhtVnpIcmd6UkFoNksra0FoRHJRdEVkS1hCNk9Wa3Y0dlM4Ly9ja3ZILzJMQjUrcDZqckxKU01ORE1YU1Bubm9UcjduQ0dZaklOMzZxSTdtRklxVVlRZVg4VFUzaUwvL1luemwvanFIYWxxVXBiS0hyL204Y0ZubzB4YTBtempLcDZ2SGYrSE1hM2VLNnpxZzZaSFFhWFRRWjFaYldvdUhBQ2lRQ01xeG1WelV4WVNtbTVPeUtJR3hMSmVqRlRIYXcrV3k0WU5Ld29pekVSa04wSnZka0ViWHVlbU9UYkdQR0tkTHgrdXpqMVNJVU92eW5YL3J4My84VFQ4UGkxTnpFRlpkMUdXaHRUTEdJUFR6REg1WGVrYk5rT0ZNREpaMjVNTnRCWE83eVdnOUc2L1dWV0gzdjd1R0VudzVZVnp5OGRyRkM4ODhNVjAvVDlTY3NodjZtVDI0dFVOejhJeGo3NXFDalhWSDU2SFZ0Tjdoc2p6dHU1aklIdzgzdmF1MVJzUzlCNC9zTzNLN0VNSW9uZlMyTEdJeUlOS3FyS29DeUhoSW1HUnlyRWFXOWNRWU1pYTVrRnhJUkFUQ0pxTEtSeFZ3dUFsUmo0TFRTaUF5a3JQUmFLaUEzLzNOYzUvNnlsTy9kKzhUNitNaXl6bEhBbVBZMGk2eDh6cHg0SGEydkJjUTdGUnZML1ZLVTZWUWNIalZZZm5kTjhwM3Y4QmNQNXlwY2phdFNSdXlROFplMDF4SkRveTlTL2FRN2Z5NVU2K2Ftak1DaDJTL1VaTjJhZHNtZFFHSTFGblZ6c2doQjJRY3dRQ2hNWTBUcUlraGttbmlpTHFjVm9XOWVHb2lZSWpHbG13bjB1MnVqZGIzNFUvZXUxbFBBWkhPcjYvLzZVOC9jMmpYOVJHNTZVUUF1cTdxcWxCMW9ldktnb25kSUF2UWF6MFJNaWJ6MFhDMGNxWCtKeEdWazQyaW1KSnhKMkNTM2dvQWtDRno3dGdqYXhlZUlXUHMyZXBKeSs2ODNzK3MrQzRQeEQyZk1ERzIrNXhGb2Q0RDZzMnVJUUpqQmtzcmgyNjZhMm5uUHQyTW16ekJkaHFDQ0VCRzFWVmRrdFp0eGQ3c2FGS3d0bUlsSWlBVFhHUkNaRzJNdmpQem01b0NBWWM3WHNYVytoQWlMZzB6SWVURHB6Yi80OTJQZitLZVI4K3ZGNHd6S1FpTlppSVh1dy96ZzdmejVmMG9CMlNxZmxkNVN4VVVOV21OMSs3aTMvT0M3UHZ2Z0JmdVZrc3czWnhXeWdRbjNnWElrcklkaUFMRW8rVjlIei8vQWQ1MGY2OVV6cnBoYktqU0xHdmY3c3V4N2tCZmZkQWxUcmF3b3JCU0JDSW1hTzFwUFBtMU1jK3hxQ1p2ZmVtUC90TzMvOUkyS0Vza3JldTZtS202Vktwc3poWkZhTFkrRUFES0xCc3U3ZWJ5eW5ZL0FJQ3F5dWw0MVRtYXpmWHFBRUo5QUFBaU1oZFBQclYyL3VtcW1ISWhuR1k3SnJISFBRY1lBUnhoQzJEY3ZtZDNQOWlKSE5kdW9pMjFWK05HU2dIUUdDV3l3WUViN3RpNTcxckdlYmYrM21Uc1p5anRUVUpRWmFIcXdwMjhiSkZ3bS9JekwyY0RnWWlNQzhGRnhqaG55SW1hS01jQVhtSlQ1N0RHY1I0YTJ6U1NQQnRrVDE0by8rVCtwMy8xdjMvenNkT1hBVm1lY1dZVUlQQ2x2Zkxnblh6dmpaaVBnSWlNZ3Q0MUprQ3NOWlUxRGlTODlLRDg0WmRtZi91STJpdExWYzdHbFc2aTJsb2ZlNUVwYmRoUEdSdjlwd3NmZkx5OEI0bTVpdXUyeEVFNzIrYkFLSUV6RVJmOWpBVUVJcG82RjlncG9VZEtBZ0FrT1JLUGZXWmRGUVlaTXN4KyswY2YzTHQ4TU9xMEswdGExWFUxVTFXcFZFMUdNNUVOaHFOc3VJemJGWnl1SERXYnJLblEvd3o5bytabVlNRzVsT1YwdkhiK3hQcUZrN1BKaHVDOGkvSnZkTWNXQW8wc29UTitkNzdENEFWNVdrVWpBSXQ3VmdDN3JuRnZkb1dUMW9Td2MrKzFCNC9jS1ljam8xWFlNYjRSQVdDa3k2cXV0YXF3T1lXeXkrZU9DUmZBNEdJdGRhd1dFaEJuZ2t2QmVkNERZd2g5aVpMVDZnMklRQmxubzBGMmRxUDR4SmVQLytGWG4vN0xiNTNUUklPY0FXa3d4SVk3K0s3cjVNSGIyZkkrQUE2bUpsOFZqS0ZLZ3pMd2dyM3l0VWZFKzE3RTd0cXJCM295bVZXMTdwSFJtZ0dYenA2UEFzVzNpcTk4NU53UExQRnJDUXhTN3kzMnl3TU8yeTFNa3BNRHlaRU02RDBqMi9mZExKN1YvYzRqQXJ0TDBDblpxNjZiNW1NYzFrOVdweC9ZNUZKb1U3M284R3MvL0o0L2t0ditTdnVXeVdpdDZrTG1WN3orRGtURmJGeE9ONDNSam5QcmcxSTA0eEVZT3NZNE1sWlh4ZHJaWjlZdW5pbzJWeGtYelE0N3RKT1luZS9nYm54dzVsVGFyWU91dCtranFJMzRwTFlqK25XT1JsK0pqSzZYZHV3NWNOT0xSaXU3Z2NnWUUwWVJ1Q0tFUU1hb3VsSlYwUjFjNWtwN0Fna0Q1UExTSEovSWgyQm83UTRUUWtnaEJIS0IvUkVNZ1oyTEs0aUxiOVUzNDJ5VWkybWwvL3pCTTc5MjkrT2ZlK2lrcWlFYk1BNkFwSkJKdnZzdzMzOGIzM2tBNVlDTWhtWnVxbWNMbFJwcURTc1pmOEYrL3Y0WHliZmVUSWNHcFNwbmswSVpzckdDUGh0Y2oyS0FPejUwNmpVVGM1RkR1S21pQjlSZ0l3YzY0Lzk1Tm01TFA4U2RLdGlxQkYzcTQvZHNOS3ZGazJMekY3N3ZzOTkrNDZ1NXpLL3d4TTZybTZncWlyTFlWRlhwei9SQTVJaDJhYzdRb09FMFk1eUx1aTVYenh5L2ZPWllWY3lhV2RPNFl2UWNCOWUyUXUvNzkwYlNxZHNhUWVqc0d5SVFHRjB6eHZjZnVXM1B3UnNaRjZaZnhMYTIwUGE1OVRWQjFaVXFad1lNT3RKSXpsSVhuanIyY0crQVhkSzI1Q3NrZE5yTFlLR2NDOGxGempudlZjSTNjcW5KMWVCbjcyUXpoT1ZCVmlyOVY0OWUrT1JYai8rWHZ6cTJPYW1ZWkJsblFBb0orUEllZWVBT3Z1OG16SllKQ0UzdHJiNENhY0NxSm0zd3hqM2krKzZRYjd5UmZjZWhpdFhGYkZaVld0c3ZIem9telE0UkpNcG5xcU1mUHZmT0FTNVpkaS93SGh6VzlQbGlIM09MZHpIeVB4Y2tMdG1GYjA0dlBURVJVbXpNMW43a05mL3EvYS80eWZYcFJjWTU0eUliaktRY1BNZXZxVjVwTWtiTnh1dGxNVUhBMXY5MGNNa2JQUFF2ZVRMZ1RQMTYvRWJHR09OVk1kMjhmTzdTcWFQbGJCTlJNdTdzbWtVN25QYjBNRkR2dm1EMEhSRDNSVVF3UmhQUm5nT0hkeCs2YWJpeTIvRS9FeExiSXJFeFZWWG91a3JZQjZjcGl4YnJZKzJ5cHNNYitiaGwramhpNDZjQWlJaHhLYmprTW1NTUFaaXp4dGdyV0hKN1JNc0RuN1djc1pIa2pNTUR4MVkvL29YSC8rd2JaNDZkMmVDU1M5SHNVRlJzc0ZQc3ZGWWV1cU1KZ2dPajdHZmlPdW1HU2tOUjB0SUFYM3VEL01HN3N1KzREZzdtUlRXYlRvdWFFR05pQ0VDQStNVnpmL2RFZlpSMUszZ2hJOXdGUTlmNHhzcmtpbGF5MVowSnNnNXVPblV4ZHNpNE9mcVp0V1krYnlYZjg0a2YvdnE0bWxpNUlBQkF4bVEyRURLWFdmT0o3UDhmRTVFcFo1T3lHQnRWdHlIQnZiZVhjRFNTQ0FtdFlMZzUrbDhFeEpFQkY2b3FWczhldTN6bTZicWFJZ2prNkJVSlZqVjhRWXIwcmJlcjJBMFRBWWpBbURvZnJGeDcwNTNMZXc4QmtTSHRJSGVrQjhpSWpLcExYVmRrakRlcDVyelRkNityaEhOVDcyWUhMbnJDc0xoMU9CbW9uYzlBRUVJS21UV2ZocmRWQnc0SmRyQWV0N0N4TWUwWUNYQWdlU2JZeWRYWkovN3l5ZCs4NThsdkhsOUZnWGttZ0FoSklSZDgxL1h5d0cxc3gwR1dqWXpSUUtZTlNiWHhDZ1F3cTRrQnUzWW5lOCtkK2ZmZGppL2NvMEFYNDgyWk10M0tkZDl6anhiM2ZlVGMzeCt5M2QyNmdjOE1lOUhINlVIYnFaN2JZaHZlS1ZqZ2NKTC9DQUNjd2FGVFVGOE80M2ptL292VHM0WnhxRTM1dnBkOTZIdGY4dmZhTURxM1NpSWdZa3lJUE9jeXovTEJWZnlhYWtkUFhaV3p5YnFxU3YvamtPaTBCNkplOXQyRWNId0kvclhqcWdBZ2NzWlpYUlViRjA5ZE9IbTBMcWFJeUxqb3c4cDZzQU5mcUtEMUg4RUpXTFBJZ1loYTFWeklmWWR2M1gzTllaRU5qRmFCQnZuUzI0NEF5MkpxdEhJODNQbWNBbWRpWmdIdWJjZDcyblpxdlRqR0plZFN5QXphNzI4SHByR3pTNDRmMlJ2Q2tJbVM0OUpBbmx1Yi9jSDlKLzdncThmdmZ2aXNOaVF6TGhpUVVRakVSbnZrZ2R2a05jK25iQm5Ca05FQTVDN2RFb0pTVkNqWU0rSnZ2Q243N2x2RU8yNkJaVGFyWjdOcFdkc1JJeERBTXQvNUo2c2YvK1RxUHgreGZSMS92QTVCWDRQQVByUFVOcFBnNEtLQTA2QitBdHh0b1J0S2tjSkZaS0RHNXRTOUY1RnhJck4vNWZCSHYvOXVyYXZFd2xibitKQW1BczRsNDBMbWd5d2ZYQlZzTktxZWp0ZXJhdXA4L3NFaElIYTJmSlByQ0lDelZiZDlNUkNCMEt0RUJNWmxWVTVYeng1ZnYzQ3FuRzR3SnFDeEF0M29vZFB0bmhXeDBVUUFJRkpHMDg1OWg2NDVmUHZTamwzTkp3d1NEbC9mS0FTa3VweHByWnF2Z3ZicWsvUWJ1MS85eEl5ejRoWG1qc1ErYkwyYkg3eFhJa2pzS2lJZ1lFeWdFRUptRExuVEIwSEZQY3VkUG5Tc1pqdmhoWUxqOGtCTVN2M1pCMC85aC8veCtGZWZPTDh4cVdVdUJHTkFpclRtd3gxODU3WHk0TzI0dkkrSm5GVGRibVZ3dWxzVHpDckRrTjF4Z0wvM2hmbWJibVIzN2xWVXpTYlRtV3AyK0ROY1lYdCs1dVIzWFZhbk9NcW1nN3N6L0tJdThqY2wySUYrdkhiYUcyR1hydzFQbkhCS1d5WUUvZ0lLZnY3K2plSjhTVURuTnpkKzhkMi8rK3FiM2pTdHhnSDdVNkgvWUpwZCtveEprZk44SUxQQkZSMDc3MUJzeXVta21HN1lEMUFIaHQyVGhjU0dUNnNFeVlGQW90OURqOG1lUUlVTU9WTlZ0WHJ1K09VelQ5ZXpNV01DR0VJM2xlM1pwYllveC8wRUFEQ3FIcXpzM25QZ3lKNURSd2dCdENLdjZwN2c1bTBBMEZxcGNxYkQ0THV0WVF4UEgzczRScndZRmJ1eVhNMkFCVzlGU3BnczFzN3pNeUVFNDFLSXpQY1hBbS9FQTMyQXZzKzZIaVVBampqS2VhM29vUk9ydjNiMzQzOTAzNG1MNnpPV2lWd3dBQU5hQXhkOHRGc2N1bFB1dVFHeUZRRFZiUG9DUjVFSW9GSlVhMWpKMlZ0dXlkNzdRdm1HdzVDenFoaHZGcFVpTXQ4bzd2MzFpejhqWVVoZ0YwUVd6MU10NHV3V2p3SkRGdWRrR2J2MDBNYkdrek1tc1ZUamw5M3d0di9yZTM1elVtNUd0aXdvdy9aY055TmhnMk5rbmd1Wnkrd0tzTEd1aXRsbXQvL0lOVEVRSVVhZ2hKNHF4bnZRT2hUclgzZGl6RksxTlBjWTU2SXFKNWZQUEwxeDRWUXgyN1M3N0IyckNGMTUvVEtqVVFvNTMzdnRUZGRjZjZzUW1kYks0VnZhZkJDWldsVzZLdHo2WXgxcHZaaElML3l3dFdRS1RIWktyYnZTUS9GSW9xdFBUWCtIRURtVFFuS1JJVEwvSVFSZDVhcXB1eFd3ZTRTSUE4R2s0QStkWFAzTnYzanlNMTgvOWRqSlZTWjVKamdpa2RGSXhFYTc1RFV2NEx1dlp5djdvZkZSeVhSTGRrMHlCcVkxWll4OTJ5SHh4cHZGRDk3RnIxOHhVRTNVVlB6aW1SLzd4dlR6RXBjSmRNTDU5M2xoZnpsM0Y2dXJiV0xNVndKaTBKeXdpQUFBRE5SWW5mbUxWWjRCRVQxdjUvUC83YnYvMEZ1cjloZ1ZDbjNJV2lCcWJXT3ozaXRsTmhReVgvQlZKcU5WTWQwb1o1TUdhaUpCQ2JETHgvNytVZndpK0JybzhxeS9FZXFuWHc1akhCaFhWWEg1OUZPWHp6NnR5aG5qREpCM0s3Uk4va2FwbXltRGxiMEg5eHk2YVdYUEFkSjI0YXFIbVdBYUh3SEI2TG9xQzJPNlV3MzYrY2dncFVBSWNLNFNMZ0RRN1VIYzR1U3dQNkNZaUlBekJ1MjJLWjVhdlcyTmtUK3g1Z3FaUjI0dStUQmpweTlQZit2ZTQ3L3h4Y2NmUGJNSlJGSnl4aENOSnRKTVpIelBrZXpRN1d4NVAvTGNtQnFhZWRST1ZoQUJZRmFUTm5CZ2liL2p0dXk3YnhadnZGSE05SmtmZmVpRkFwWlo5NjNWdnZjZE8raU9RZVpjdEhacDNtcUUweXh2OGhXQmlBR2ppMS9ickM5VkFIQmh1djdMNy83amwxNy82bExORXFTRS9IRjNmcExQT2R0NE82ZktSWllQUmx6bW5xZEtVRXpYaThtWTJzT1R3azZ4UlVGUWIvOSttakF2WjJxRTBoZWJVa0szVElUbTZEWE9xOWxrNDlMcFM2ZWZxb29wWnh6Y0hUWkV4cWhzdEx6dmViZnNPWGdFZ0V5N0FUZWcyU2tmMFJpajY2S3VLK2hiL1d4U2VpdFQvM08rTm9iK0RWaDlTbFFTTXNlRlI2OGNhL2FCQ0lFaFJ5RUdRa29BMXNURmRWdG1mVlBhbCtRYWYvZVo0R3lVOFhNYjVUMlBudnZWUDMvMHI0OWVtaFJWbGt2T0VJQklLMlNjajNiTFE3ZUxQVGRCdmdOTVRhUUFiQy9ic3BUQm9qS1N3YmMvVC83Z1hjdlhIdnpzSjgvK1NLVUhDTVpmOU8wVW9CR0MxbjNxcCtyNjVWcTBZdURGeDdqTjhVcnVkZ2swUmxUdzhkT1QxVytNV1laRnZmbm1Pejd3b1RmOXZ4dkY1U2JDczhubHI0T25lSmZ3N2dMWER0c1B1VEhHWlpabFE4YTRNYm9zSmxwVnR0KzY2VjJDY0s0YlV2cm1tcGFHRzY3S2RZUjFmZUNXQnFGSVJUY2Q1WFJSa2RYRjdOTFo0NnRubjlKVmhad3paRnByUkxidmVUZnZ1Lzc1UWtpdGFxZkxZeG1ENXFHdTY3S2NOcTRUYkRzbDRXcHJkelNoV2c3M3lHbmwzTXFpUHAzM2x1OTd0V0tIeUxpUWpFc3VoSTBGN014Mnp5bnlyRkZRUThzN3ptRW9aYUhVUTgrc2ZmUi9QUHFacjUyOHZGSHlqR2VDRXhBYURXRFlZSWU0NXZsaXp3MXM1UUFCQUtrMk90enhmSW1vVktnTTNicHIxNkViL3MyaDUzMVltMzFnZExyK0JDOFdlQnB1L21RMng0WnhwTnFjKzhJYUVtcFN1NFlIUHZiZUx6aWVSZFRkVHZDT3oveE8rZ1BraVZyUmJ2MXZkcTRBQWdCai90NGVsK1pZMzl5S25FZnRMeS9ZMHo3eTlsSE9VY0tnc1FucjdDZzhZNXpYeFhUOXdzbUxwNStxcTJKcFpkZkJtMTYwdExLMzJ3RGhGQmhoRFFLUnJzdG1KM1JQQm5VY21hY2EwYVArT2w0bkRIV2pRNmRJbHJ5YkMrVXFXWDdJcXJtQk84MkVGbWVjQzVGeDBYekV3NjJzTSsyeGlDZXVHV0l1R1dmc29XZldmdTN6ajMzdXdUTkhUMi93akV2UndLSUJveGpQeEo3RDR1QWRmSGsvWmdPamF6UzZQNG5HenVJb2hZeGxiLzZmWHBWbEY0MXB2OGVRV0s2UG1STjdUMXNtVjVBUURBRXBjL212MS9WVUUrcFMxZi9zdXovMnlpUGZaWnIxbm1EK3ZTK2hBOVJlRFJxd3RadkN3Tm1GUjREWWJKNndCYlRBVHVDTS96ejlCSS9LSG1BZGdXeUZKeG0wU05IUEFGcm5kWEYvaCthc3lWcmlFQnNIbGZHcW1KVFQ5YVZkQnhoalJpc25mMmg3YkhOQTFZV3FTMk5TQUpqQytPMmtSZTZvYlZJUFBJblpGSWVDUGh6T2FYaXl6Qjd4bkdZR05vTkNQeHNiQTR5SVhBeUVsSWdja0p3QUtFQ1BCKzNtNVhqQnQ1dGNHMlk4Ri96azVlbHZmK25veDcvNDVKTm4xd0Y0bm90bWhBMWFBMk44dUZNZXVrUHN2UkVITzhob2FqN3Q0akRCa0J3Tno3NytsYTgxTUxLZGhkWnc5SnpxUmFhUDZ2WnZkczMzekkrNzZtaWIwRVRtRG5EMS9uRnhlc1lsVHNyMW4zdkxiNzc1dG5kdUZ1dGRFVjN0N3JrTXpla0xRR0NhUklZTWFhT0p5QkJwVFFCa0RDR0NNY2FwcmU4RGFMNXBqSWlJeUZBd2pnd0ZGNHd6S1VRYm1kYTZyODRhWU1MMkJ2b0d2U2lrNzBBcVA4QldybTlLQXExdFJJYU1hYVBuS0swbEd3RUFTZXU2bW1sZE93V2t2Snp0cEFBVlEzYzAwQWR3c3ZyWjRtblB0TDZsNkF2aVRqczE3TFF1VWtoM2JyZGhIK2RjOE1aSFJRYjkxbDYzUGcvekF5WHNlaWpqZkpTejA2dkZseDQ3OTVIUFB2cVZKeTdXU21lNTVHMGJGUmlEZ3gxeS8vUDU3c05zNTBGR1NFWTNuNGl4OGladnZla2p0OXo4TDVXNkZraUhnZ1E5Y3ZiVmRxdjE0T1RzMzNKWTBqZW9LOFFnNTdPejA4MEhaaWhCNmVwVk43MzluLy90WDV1VzQrNDREd0lnbzVVeWhraHJyYlF5eG1qZG5CTnBnSXd4QUhaMDZWYldDVjJJUUdFL2swTnZvNXZJT2VPQzU1a2NEb1pTU291TkxqQjYwT2tBNzRMK1NsNTQ5RVJPME54M25aenUwMlRxQnppRW9LcVpybXNMZ001YnRzTGtLa0JVWkNTaEM1UndRWW9OMmhWVS9DeFM5THBEQUNFMFcvc3pMcHBEMTZtVERjLzE4UzhzWnJwM1NISTJ6TVNzcXU5KytPeXZmUFpiOXo5NWFYVmNaYm5nREFHQXlLQ3BrR2RzOStIczRCMTh4MzZRUTlJYWpHcGtTNXVsMjIvOU56ZmQ4QXRLN2ZkVkxyVnUwN2JFamQ2d0ZNN2xieDh5ZzR6cHNsaTlkNHBNR1dQMkxqL3ZGOS8xYWNFRUdWTXJyV3F0dFRiR0tLMk1JUUFpWjJkWHAyQk8yY2tBK3ExRlA4NUdyVGdTNXl6UHN0MDdkNEd6VkJMMGE2OTdVZHhtWkVOZEF3RU9nMXlxb3AvZDJwOGZ3TzJVbTlUYklEK1FVVlZWR0ZXM2dSWmQ5QVdBbXkza1gwcEJLRzVZODlLVzdtZ3lVYXIxNmRlVCtEUkhyeGJUMExPNWtmQStpSndRaEJCQ1pEa2l1aGJYKzhSVlQ0UmJqOGNaUkZqS3BUSHF3UlBydi9MbmovN2hmYytzYlZRaTQwSzAzOTlDcllBaEcrek1EdDR1OXQwRXc1MWtOQmhGeEZTOTg5V3ZlTnZPSGZjYnN4T0FyTmxOTE8wNmtOTStKYnV2TktMSW1sbG9uUUZrYUxUWnZIK3pXcXVVMXB2VDhjKy80NU8zN24zeHBOd2swc2JZYk5DTFN0UnFCOG9jVU9vOTNpaktPZndibm1rVGRCMEJnRGEwTkJ6czI3dTNDZUJLOXFZWHBwa29LeWt1OFUyL1ExdWs3ZS9QMFRwZkpQcTMrazZDNXRDZHFtb1BiV21ySVhER0dkdEtLUUQwV2hvZmI1SFdZRi8xdTBtVXFIKzJUVTJVRXEvUFdjeUlpeUpBSUVKRXhoa1hBeTRFK3ZiUEwyRmU3M1k1Y1poeHllRHJ6Nno5MlFNbmYvMExSNCtkSHpPQlVuQnM4aGtOcERGZkZ2dHVFZnR1NEN2UEF3UXllcGlmZStsTDNqY2FualJtMUh4VUZKek5vRzJ0N3VtbFZsWElpVTF0WmJMQmtPNTFHM0tGakYyNCs2SmFCZVRNa1A3QlYvemNHMi85bmtrOVp1RXVkUURvRmpOODk2a0pwa1JBWkFURTJrZzZJR29kYk5NY3h0TjNlZEtneitrOXkxUk5aaWtmWExOdmp6YkcwU2lJUkJKaW1lajFNNFRINEU3czcwQXFMTStyMS9GU3dOcTVZTjBMQUJCSXo2WVRJQTJNOWQ0S0JabTNZTXAyRXJWSW1EUXVYWVJhYXZVdjBMb3RsWEFMRWhjcUozUldZQnNLM0Y2d1poNVZjTVlKc0R2Sk8rcXpCRUk1Tk1Fd1k1a1FKeTlOZnZjcnh6OTI5Mk5QbnA4b2JRWUQwUm9nTW1ocTVKTHZ1aTQ3ZUNldTdEZDhUOGJIcjNubEt4aWZHWk4xc0UzUVQxZDZvZGkyYVgzdmUyeHA5MkhicDRaeHR2N3d4dVRSRWdWc3pOWisrSlgvOUYwdi9udWI1YnB0VkRkRFNRakFHTVBtZTN5Y2M4NFlZNXd6emdWckFxc1phNWlLN2RlSGdjQVlBOGFZdXE3S3Npd3Jkd28rUnJ4Rm5VOUVuUE5yOXU3aG5QdkFGSXFhMHlQZ1Awcm1qS1A1Kzg1YUNKaldBUG83cDl4b1IwOCtFY21RVnBWU2xYTnF2Vk9TUjN6UWEzTUZOZWs1V0NXY2s1VThJZkxtS3QzaXd2d0xxNDk3TEVHWkE0Q3VDVWkyd2ZucEJkOVFjMHFxRUVMa1RBaTBEb2JOSHVCM21tM05zMHp3WWM0dnJKZjNQSHJ1Vi83c2tYdWZ1S2lVem5MQlcvQnAxdnFSRDNiSkE3ZWFIYmNkT3ZJM2Q3M28vYWJlQVdqc3FiRVcxUnNqVEFCMklqMzBJNno1OC9rQkJNQnptancxVy92S0JwZDhwc1l2dS82N2YrcE4vM1pXalFHQTJzTkNnWEdXQ2NtRnlLVGtuQXNoRUlFaGN6MzBqZ04yS1lJQU9ocWhFWFVDT0gvK2dsTGE1MC9jaDk1VHNxUXZMeS90WEZsR1pKUTJmd25iMStoWUtxN0E5cWRmZ3VmbEo5ZGo1dHJzQ0cwU0dnc05aSGFxYUl4MnkxMGc4SW1iVytKSHE0VGJ3eG4zRGl5SVdIWU1VNUppNzVVWUdSelN0MVRYb0phQTVxYXJrRWtoQkJjWk1rUkNhbDM4SkI0Mk43b0lsYjR4bk9OU0pxYVYvdXlEcC8vNHZtZis4UDRURzVOYURwaXdYN0FoTUdnMENvNjdYdno4bDkvemdwZi91M3EyeDR0UjdHYldITy9Vb1o3UUhuSVBOZytRUFpXR2M3VmhMbjl1RlJBcVBUdTA4OFovOWZiZllZQ0dpSE9lWlRMUGNpbTVFSUl6M3UyTzZ5Y253NWdZcjlYVUtDRzFSMElTd1hneVh0OGMrMm9USks4Y2F0Q1A4VHpQbHBkR2VaNDFxNVVwcVFxMXFJVTRSdzFDbEd2OGNJOFl2K1BteG5QSEJ0OTlta3FlUGpjM0dKR3BxdEtveWhCaEVOOFhTZW1jWWhmcGwrT09PbG0zTHRUWEx0ZXViL09Wc0c4WFQvSTZUZDBPYmZIN0JNU1FDWkV4THBpd3h4YjJkUGsxaFhhajV3NURYTW9sSU4zMzFPWGYrSXNuL3V0ZkhsdHZEOWZnbGlvRFdpdTk0eFhmOS92WDN2bVFxZ2F0aGtWcTdxYXUzN3RWUVF0Y2pjc0k5V1c5ZnU4bUdhck03TUNPSS8vNDlmL1BnUjNYWmxJTWg4TkJublBPRzdzUmVXcUwwYUJEUGdCQUlGTXBWVlgxZEZaVVZZbmhNUjhPdFkyZmJQMUNBcEpDRElmRDBYQWdoWUFlYitOWktiZDJqNHhvODRTbHpOM2g1NzFDVGg3d2IwSzZCOVBSZWU3N2dYNmk3UklBQkRDbVZwV3FLekxhWGFtZks1QUxGYy9MZVByNHcxMjlDYWhKdlROWEdWSzFCcHJXWTJqVUwrazVtSGtHWW5GZFlXa1d0SWtBVVFqQmVNYTVhRDVmN2tmK3hLWTBialFoNEREbmdyRnZubHI3MHdkT2ZleHpqejE1ZGhNWTVwbkFWbkxRYUhuWFcrNis0V1YvYWFvUmVGOXptYVAxYWJ3aEpwbXA0UEpuMTZnd211bVIzUG5oZDMxaTEzRGZjQ1NHdzJFTDJnSG1ZTGR0TG5JdldxK3pGUzhpTWthcldwVzFLb3VpcW1zN0NaU1VvSll0emNJSE1wU2M1M21lNTFtZVpZenpacysrSTgzZ3hFc2tSTCtwS1ppV2pMclVzUlorTDdUNU96UjErZW4zdTkrRFlWSHhYRUNYMzF1OFFRSmdSaXRWbDdyOVpOaGNPS0RPemthYUZmZXpFN2JtanNRV1lrNzZxWDB6clhWelFNOGp6dFhNU1BmY2locjJMRXF1RWdhOVptbGtqSEdlQ3ltUnNkNnE5eTBKcVFzZUVTQkR5QVhMSlQrNU92MnY5eDc3azYrZi9QSmo1N1NtYkNCNUV6NUEvTlUvOUY5MlhmZTBxb1llZVVGaEh1RytoaUtwQ1cxK2VVMXZFbklEQVAvNE8vLzlIUWUvalhIWXYyK2ZrNXNpZ3J2eXVtSmJwMUVyYll4UlNsVzFVbG9wcGJUV2plYWdLemdlUldpYWovMENjb1pTeWt4bVdTYnlMR2VNT1JOZk1SRDFKWGhZRjBVeXBZUXRScmF1c1o0bWd3ZGlVWmNsYWdqT3ViQkx4K2tWUy85ZEJHalBmU3lWS2p0RVNLUkk4RnpxWFJIMTNkRWczM05QMnlqWlE3WllQK2RaNURuYUhxY1FWTjEzQ1FDUmM4Rmx4cmwweG0reDBmVUVJcGlta2h5R3VaeVU2dE5mUC9uSDl6M3o2UWRPaktlMXlDVW5qb2l2K3djZkcrNitZT3FoYXdZQ3crM0Z1TmxNeUlBQTF1L2UwQnVhT0syTk4vL1pPMy8xdFRlK2RYM3puQ0hJY2puS2g1d3p4aGxqekM0TUlsbGtOR1JJa3dIU3V2bjJhL3V2TVVUTlVabk5paUppc3FXTlZXcEdyOGhRQ3BsbE1wUHRsRThUbXRNdHc4eEJOb2RwZHF4RjFvbzZqT2dFUDdEdHNSa09VTS9MNU9iMFMwdFVaLytOakVVMEpyVEUrNDFDQkVKdGFsMVhTbFY5L0VkQ1dPWklwc3VieEM0Sy83WEZxQWlwSnJySzAxT1dxaVY0ZDVIU3BtSkx1cEtTZ0RsUExjTkhoSVRFbVJDQ001RzNINjhtdEhzRllsam9xM1ZMWVlqTEEwR0FYMzdpL0gvKzR0SGYvZkx4elduTnVOaHphUHpTZDMxcWFkODVYV2QyeG1jK2pmMEpNd3daYmR5em9jNFRaZ1NFNzMvZHYzalgzL3JBdEJnclhlbXFMSXVwMWpVUU1rUmt6VkdDYlhSUHczaGpZOUs4bFhYRU9iMXBxMjFsRkJsam1aVE4vMXh3S1VRbmk4N1NlaXpsUWNzOFRmQWoxQko0MjlEcjd3VnJGYXdEVW0rVFJ6aXZBN2FFTmtPN3hCUEpjOWh3UDZvdzZCSEh4M1kyOVZzTHBuVmRWek90RlpBTjJKMHZ4a2s5MmtZQTkwTDFBNzhUM0ZjV1FkbUMrd3RUQ0d2UE9xVmViNWJDT2M5a2xpRUtPK1VleEhkQ3pOSWdMR3VZQzhId3dhZFhQLzNBcVYvLzRoUEhUaFI4Q04vMXdZOE5kMTlXVlFhdFZiSm5ZcllibEFIN3VYb0Foa0E0KzhhMFBGWVI2bzNaNW9mZThiRzN2ZVR2YnBhck5pd2R0VEc2TEtxcVVIVmhqTzRDT0x2bStlZTNkY0lNTG5DMVRDQnFWaFdGRUlLelBNdGtsbkdPbklsRzVPenFUcUJhQVN2am0vYXZyVFNZcDNGb0FpZS8weU9leHdDOURvUWpTYnV6WXhHMGtsZU9UN0ROM1AreW85bVlsZ2pwRUFCSXEzYXM2UFRsUEwwSlU2Q0UyM3ZUUlRublRzS1RtRFA4VzF6c29qdmRmVWoxMmpiamFCZW1kdDhVRjBKa25FdGt0bk5EWDZVWFBodks1UFg5UUlwY3NwT1haNzkxejlGUGZmWFVveHRQZk50N1ByR3lzMGJnWkEvUndNNVQ2SlNHQ0JrQ2c4azltL3BpRFVJVDhmZCt4NGZlKytvUGp0c2RFazV6RVFGUXFhb3VaMVV4SmFPN0xjSFdhSU1yY00zOUJpRVJrSE9PRERNcHBaQlNDc0U1NXd5Uk5WeWtmdUloRU5sWXBoT2Q0ZXRBOE5kclJGUkNzdGY3U3QwVFpseGVCSE5DS1dxRG9tSXhXa0NlVzBoVWhaVi9vNnU2S0loTWQ5eDluR0pGOFBjVCt2TVp2ZnUzTUYyUjBpZDQwdDJQYld0QThYUFdzYWpudkNWS0wxdXpYWWd4TGpJaEpISUJRRDFIV2pGdTFxOUNCNmtYUHdESmNaanpTYUgvNEN0bmYvK3hlemRmOWk4a2x3TGF6MmdHT3l2QXdsang5WEYxdkFJSmsyTGpaOS94OFRmZStYMlRhaVBtVTk4T1JHMk1VVlU1bTZxcXRFZklzVzd1RndHdytkb3lZMUpLS2FRUVhFckJPV3ZtWXh5NGM3amorelJkZTEwdjBlRnJyS3NVbFJNWEc3WSt1cGt1eDlHM2VmWDZQZEYzYm1kS2d5cmNrbTMzOUNXbEdCSS9RbXgycjlUMXpHampiVUNMazMyeWpaMzFzR2hRdDNWeUtBK016NXpxZ3FOMUlHakkzSGUzUVUrc2hPNHJjY25VZmprSU9SY2lrd3dGTUc4Q0xSS0ZybVF2Z2hFUmxnZGlQT1pmWGJ2dnR6Wi85bUo5aVFHM01mNE5aeHBVSTVScytyVkovVlFKa29weThnL2UvSDkvNzdmL3lMUmNYMGg0ZjV2YW1PT3lMbWRrZE9OaFNpRmtsbkhPaGVDYzhXWXpJSG1qdTBCTUFUMndzVStkbTlTZnV3R09JSUlqcGhEZGpMZ1U1Z2x2T2x2ZUVrZXd4Wlk3cFhYendDMVFKMThKM2VWYWh5dXhWVXJNUWlFMmExRjFYZXE2TWtiSDRkNkJwRzNqdExVNWJ5N0lFQmlLR0dxOE5COERZYzViV3k0enpxZlFBZTNGU3R0Z1ZGOG1BUUF5d1hrbXNnejlZYnkvRXk4cGhRQkF5SERFQm8rUGovNzBNKzlXaEJ3ekw1SU5rVUJWanhUcWFFbUNxbnJ5UDcvcXB6L3cyZzlOcTdGTGxGZDZ0OGhKQ0FpTU1jRzR5Q1RuUWdxQnBGVTFNNnFtOWh3VUJEdHhPaytJbzBjSmVKbDcwNHRyZ1cwWEF0RWRGd2s5MUlyVklQSkJFbFNsRms0ODhReUc5SkVONm00bjJ4NVlrUDUrczdGWnFVclZKUkQ0V3pFV0ttR29TTnNSYmx1djkwcUs4L05VY1JFd1ltSkhYZ0t5dGpJUVY1d3Q2blJxV3dZTU9lTkNpS3dOU1NXSzlzdjVqVWV3endrSUJuejRONWNlL0JmbmYwaGhMV0JnelNjQVorVlhOOVhKaXFTcDYvS2Z2UDAvdnU2T2R4VDFGR3k5N1NETmJyaGdqQW5PR1dlU1owSUtLUVJqM1pZcnB5M0dsTVdrTGllcVZnRDlJY0lOZVluRmRDZThOVXF4bm9CLzBXVURqMzBKeElpdFFMcTYvcXR4WVVXQm5YZnZPd29VWTdVRGVNNVZFa2hqYlkrYjRISEdsdUJTQlFTbXJpcWptdTlTcE5vNVR3bVRkN1lweEg0TjgyM3V2TXhCUll0N3lxZHRzWjU3KzFBY2lVb2FtczZtUkNRMTJNY0U0ODBueDVBMVo5NUVuZGZMZzJjMUIyeHd6N2t2Zm1UMVp6Zk1obVFqQUVMR3lxK3ZxMmNVU3RDNi9xRlgvT3hiN3ZqK21aN0o0U2pMY213MlJERGtURWdoaEJTY0N5R0VZR3liVzl1SVNLdTZMS2FxbkdxakVCQVNYN3hKNm96N0ZFTEdPYThuNVQ2Q01xK2lhTVVpcVprTGZzWUE2R3RndXQ0azVZNE44bUx1d0Nrc2FRc0MvamowOUd4a2dBVEdWTVZVbVRxZVFFbTdvNHRoY0s0cWJrTmJ5RFp6c2V4MFdMUmQwUE5admJqOFhydnNqeXRvWmtkVk14dEhySjI4eVNRU2E2ZENZaWp3U3lXZ0FRNVBiWjc5SjZkLzRLSzVJRURVRDAvVWs2WGhlbHFYSDN6ZHYzN0w3WCtuMURQT0dSZGlPRmpLaDZQUjBvcVFHVzVQNVJZa1kzUlZUbFJaMXMxZVZXL3JiNkFiTGNHMkFkR091MjJZeGdoYndPRkpOTVBwcHZUK3JoaUJ5YytmZk1YZWQwTnlVMVY2VklkVnBMeUFoSkdOVEFrMjY1VEVrTTBtNi9iejFWNU4vcWJlVkpxSC9jOHVlWWlVZXRTbjV6QVhPbS9qMVFMaXI2aGRrV0lSSXVOTWNKbHp6aEM1djdDV2tFSUNITEw4cWVteEQ1LytxVFAzUG9VYkJuTWM1YVAzZk5zL2V2dUwzMXVxS1NLekVaeEVaSkJ4THJKOHNDU3l3WE5YUlFCUWRWbk5wblZka0ZZR2dIV0R4cGJFTkdoRUxXcjVtbEkyY0RQTW9TSjRHaDRiNi9BNXhKbjVtekRpR2hjcWtsUG1uSjhRdlJYMnJQTkNrbThJQUZxcmNqWjJsMjQ3U3FNakR5UFJUOXJ6cm9xazdNYU5TS1M0eStiWjFvWGE2RFdKd3BzSkdKcFBVUWUvNmZJWDFnZ1dJQm15NXRENFJvc01HVmVnM1ZrK0FoeHd1VjVPcHROMWxBaUFBem5jdTNSZ1dtMTIydEF0TUVENzJSYmtRbVNESlpFTlJIc3UvWE95aWtSVWxkT3FuSnE2TXFZRnh2bnc1ZjZFSGx0Qy96RGdrLy9VQXlJWER6M3h0VkpuaGJaWDl2YkZoWU0wejBiNE02dk9vMTZEWEJjZ2JySW5TazZQSVBWVEZxNnhTTkNBQUVVNU51Myt6SkN2aXpiMXhybmpORThFa2dLOTZLMVFvT2NhZ3Jrem5QT1ZNTlpKcjN5MENyUk5VcmQ0MnEzMlN5YUU0QUtSMmVDVUNFTVFPRERHbXRrVUpESjFPMllJbExhN1JpRFRBSy9JQnZsd1Njb2NrSVVrWEhuU3VxNW0wN3FjYXFQQiszSjRVcmc3emJBYzk1ZGtIUDdFVzVsQ3ZJcVUwSDBkdkdYWVdEM25BbUJDTFFrQS9Ya2FBbkJXSVpKalZJZzZPUVJETjFRaktxcE5xaTdxdXJDNDVjZmk0Snl3dFMyVUpLa3dXeUdiQ3lEUFpneVdyR0xMdEIzYkVhRDZnbG82eWlOdXRBZEh1K1VUQVNKamtzdU1jMkhuUWNJdWhONmhna0RXblV5eFREU0xEY2dFei9NbE9SZzl1NCtaaFd3eFJ0VmxXVXhWTmJWN21waGorMUl0bjhjcEFPaFhZTUttZFJybGFXQmlXMU1NVUlIK3g1YWkvNTJ5dllGaVExUklRQzBFbmVYZmgraVIvOVMyYURwZW56dUdRUGY3aEhQeWJKMFNockpQQk5GTWpJODhRZE03L0F4SmNwb1dyaE9tTURCK3RHWGFEaE1XMmFrNStRRUFFYVRJbWNnWUZ6WUlHdVowSHZpOUh0UUpzVzBqSUdSTTVzTXNHOG84QjdnS3dLaFVYVmV6dXB6cHVtN29iNnBldEJqalBVeHJpRzlRWW93TnRDNFp0QjJEWGtCTWNzVXlTWFAwWWtocTl6U0c5R1JuaGJJTVFBQk1xYUl1cHRBZklCTFJ2ZUNyVEF0Z2VNdTBJSll0THNHRm9FREI1c0ZqT3FRdUNGV2RieFFjcFYwVWM1Yzg1Q3FkTTltb1BxNnRLWXd6em1XendJak1tN3dKbHJPOGdjWThHd3l1RTBTa0FSZ1RZamhjNHRud3FnQWpFRlgxcko3TjZyb2tvd0hjTDU4QjJJTTlXL29RTUp4VjZZajBUT1o4VUkxUkJSd2RjSE5DNnNXZ0Z4SWFHRGdkSGc2bmxkTXl3bFh4ZEc4SHlkb0NoSEl5TnFRaDZRQTJlUUozRkJ4OUNGSlNKNU9QRnR6ME10aEpnQjc2NWdGWGFQM25scHhlQ1p4UDJGd2wzQkpDa3htaW15SFRpQUNBYzhGRjFremUrQ0RSblhYWWRGZ3c4bkZMRGFxMGt3UTJhalRMQjlsZ1NXVDVGWVQxems5YXE3cWFWYk9KVnMyS1A4RDhyejRGZ0RnbitCUEJHMFRHdXBUU1p5OG5lQm42QUc2WEVzQ2dQN3pJaEFDM3dTazJXUXZFOUdQcXZsdVhWblZWVHB0Y3liRU0wUlVkL3J0UUxnTTJ4MDhYek5Na0MvYzRFUnZOSkQwUkgrWjVDZkgxUEhvU0JNd2hJMEd3L1J0c0pXbitJaUxqR1JleS9mNG1kZDkrYzJuemY3WFg0RGNySVRTTlFBcVp5V3lRRFVic3FnQWpHRldWWlRsVFpkR3MrSHZuMFBURUo2bUNnTUlVVW5rR3hia2ZveFpzMVNXUTRvK3JoQkNWa3ppbnVUTUlYZVM2VzY5dkxKeDZFUUdnT2U5dk5sMjNYN3B2czhTcW1CZ1RocUk1SHhWak5qZS81NW1PNkVXZjF6YUgvNHBYZkZ6WG9yNXc2SEc0bm9LKzFKRVpiaXRnem1TU1cxMlFQMUMvOEpYR2tCSUNFQmVTQzhGNWhpeGVjWGI2dFErbGRNT3lvRnRhSTdDemY5Wk1OMmRTSUdJMldKTDVVTWpCMVZoaUJLTlZYVTdMcXRCMVphZFN3ZU54cUU1ZWM2SlEyejZseEpyY3Q3ejc0WVozNkY5MDJCVzRzcTR5TzEyV0JvNzJZcTdxQm5ZRWZRTDZ0Y0hRcHZ2Wmswb1lubVVZSTBaU3RaSTFKZEFtcGRYSjFyZlhUbjVQME1INWtUS093YnN3aDMvdFJkZG1sNUttcjdkQjh4V24zb0swTWRXTThjWkhaWXcxdTVCOE9XZ0ljQ1lWbzlsMjZBSkJYYUhwcXlBdXN5d2ZaWU1oWTFjRkdLbXV5Mm8yVmRXcytiNU1BNHhXa1FDYytIUzM1Y0YwUzQra2JVNk1Pc3JiT2VYWTdyaExYTzMyTDd3c0xwU0JvL2dCdUlEWDhRbFhOZ21oTFpGQVZFdzNuYjZibXhMdWFLQTJaQW00VWluc05STmFRRWlvRVBndDlZRXJSWDFDcWIyNnRrTmthclkyN3RaWWg4SEpFcHVuMkY2RXIvZGVUYnBKZG11LzRDTGpUUGJETHZCRk1XRjZPMlJ3bm5vNDBOVkF5TGpNOG15d0pMTTh5Y3dyVGNib3VweVY1VlJYWlZPdE8ySzBiUXcwSndBM3J5R3Q5bmhZRlNPZXkzaUlmdlljOEpVSzB2MGRiZFh2eWtMM0ZVZUJVOFlDb0ZkVHJLdXBycXY1Yk90ZjNYcXhQZ21neVp3TFVrSkFVOVFrU1FTZmkrajI2TUl5NTVJM1QxZTMxT0Z0eHU1MFArZm9jQ3hIOFU5RUlhUnNnSkVBSFdEMGpJRHpWdURjOVUvaU5XZ2lnd2hjNURJYjVNTWxaT0hlaTJlVENKUXE2NkxvUHVKblA4NlRCcmNJYzJJKzlBcTVjQm1kZWl6cTNJUytCQWdOb2MvcE9Zc3VmckZ1SVFqQlNreEFjSHVGT0p1c1U2VFlzVFlSYk9NWS9DUThiajhsWi9tRGVkRmt6bm4xcHBzeEx3T0V0Y3lsMDdPbEM5Y3RBdlNMSHkyczBYVVFZb3ZtRlU3SUdES2VNY0VGbDQ1SkNvNjlDZXg2cUtnQUVBMmZXa2sxWkJpVElwTjV2aVN6d1RiM1pDeE94cGk2bk5UbFROV1ZJY1A2Z0o0MGpIY1FrMXFzaHhTLzV5cHdWRXNLSHAycTUrU25GRkNEbjYxL1BkNXNnWWgxVlZUVnJKK29uNWRjSkZ5c1kxZWtnZHZSZ1hTQjI5T1d1VGxqVkl5eUxhQ3RmN1FOTXBJb2Q2WFdhcHRNYU9hNU9NOFlGMExJOWpPZ3JSNUJKRS9CZFN4dEVJRVNOQXNiUW1SeU9KSnkwTTdXUHVla1ZWVVdFMVhYdWk0QUVKeHZCME00VUhOVGJGazZGVzJldTJIZWpnNkhyL2QxcGU1N2p4TGhnUW5VRFhOR3BmWDNwcFBMaU13ckl5bUtDRURnRDlEbnlGOENpeXdFeFU5RDgwWCt6d1dKQUdKL2IvczY3RUNpVzNWN2lZQ3A0MlFDc3QzZlcremhpRURNcTNvTzhXNm5wSE5HalcwdzJlaGFxMG9yenBsQXdUbVRqSEZxeG5pOUlNNXJYYkpmR3hRaWhQYk1YNjFydGJsV01DYXpZVDVjNGlKL2pyaklSVFphenNpWXVpN3JZbEpYUmZBRk9IQW85MFcxbzlhNTJjMm1OQ3VvOXEyMjRWNm9RS0EvODNDc3I4WFZLMHowWk9kTkJPekZzRXdDQU5SMTJjY3QrUlM1N2g1QmU3MzFWcVpFc2hyWS9SdjdEUzc1RGcrc0g1cVV6bFNMY0U1Uld5dkpkdWozcmVUYzVZVEZ3Tmh0ZG85Y25zVGlMQUJja1djUnY5dnNOUWVVSXVNeVk1eTNOZlZscDk1UDdGWHRLQWxEV0JxUVlsemtnMkUyV0dIc0tzVEJBWUJXZFYxTnEyS21WVTN0NWltWFRrdFJ1OTdvb1ZBSzducC93TU9ndWE2QjkxYUFiOUYxejR5SXlDU0dlMlVXc3pIRm4wYWRMMGhXQ2YwY3Z1YjRBQktqZXJyZ05QTFBVMVNBL3JDK01QUU13bmZkTWRzaWdWNnNQMXRxbDBkZVdwM2ljbUxXUGJzVWVobEpiV1NjTVNiRWdBbUJya0I2NHVpOTZTQ1BTMlBzTUxYQk44aTR6SWRaUHBEeTZvd1lpWXlxeXFxYTFlV00yb1VOakJSam00b0VDeTlzUTRLcEYyL0RoMFhYY0J2RTNKdTJCSnNscElkVjVVVFhGZUFjZkVvcHd2Wk9XM01yUkhCQk9aYXpoT1E1eUJrWDd0SzJkVkh6eWV0TENhb0d2OW5iUUxZdVE3THdVRDM4ZDZQZG1JdGlhSU55NXJVM01OZmVLMFRRcUlyTUdNOFlROStXdDhZYy9WZ2M2Rld4THpzVkpBMEFwamtPWElnc0c0NWtObVQ4YWt5bEFtaXRxbUtxcXBtcTY5WUVoK29SVzRlMHBxVXNTNWZhYzNSUzBUek5GWUY5UFJvTmdzT0hGS1E0MTIySlJOUHhlbk1TK3ZaVHI0UmJTdnpXS2hHTDd6eUJkaDYxNWlKU2pPM3F6SHphS0dMaEluVU5hSU9JN1JEZDNIN3FHeHRpdUM5MDl2T0U3aDQxUjJuNm5VSHREenZIM0c2YjRwd0xJWE5IeFpMdVZuelRJU0h4dHltSkFBZ1p5N0toYlBZeFhxVlVWMFZkVE90cVpneEJpeDBkOVdnRGFHUEY4Q2dQVGpxMkt4YVI2KzIrMksrajlzeHlGQlc4elBhZGxLSjI1Ykc2bk5aMXVkMlcyd0tFZThlbDlObWtXRUNkb1pmWHBkaS9nY2tYSFV2bGtqc3ZKV2xHLzlvcndEY05vU0hvbjlvZThzeXVFNVRpdkVoQjEyQnZucHV3N0NhZ3BIZEMyajBKelovK1E3bmRobG9iRGRic1JFVG9OakUwdytyK3F4SklIYk82NHR0eEtsclppbFUrTnUwMmcyUGN1eExiSlhoRFpURXB5eWtYTXN0SDJXREVudk1hbzh3R01odG9yVlJkbHJPeHJtc0FEYzNwSHMyaUgvV2Nqd3hLM3dBM05YWU1QZUh4c2hDMG42dExHWHJYQUlXUWlIMEdweW9BQURCR0sxV0h6WFBBeGlYQ2FWZWpoQTRSU1dsZURDT3gzbEx3SzFvTzdYeTJlUW9mMnVIVTB5MlRGVDVicmVONXRQeHZneldSQVNCeTVLejVxSkV4bWdqQTdrSkNoclk3VzRVQklBRFdha1Q3SDZOV1Y5by83WHhkVTEzamJIVXhsczRrQXdNMERuSzVWNDZhTzA2dXEvb2VHNms5MWNzMkhlMng4SllKN2w5cjhaMHBqMzczbElPZkRwL1IwczRBU05mVnRDckwyYWJNaDFrK0VqSjdMdFlibXAwbFhHVDVTRlZsWFJkMU9UVmEyMVVORi9UQWw2K2tiZTUwTGdiUDhQQU9xNDNRSW1Pb2tlUSthZmU0aFB4czc2aTY2ajRQN2xXZStPVWpSSElyVTJRMis5WDFCU25JUUYwM0xnU3grTjIrdlMxTlpIR2g4eDdRR3BkZ3AyUW5LTzFQQkFER0VDMjh0Sjh3YW5hOU42VXltOCsxYllZSXFBOU43Z09qQ1dMQndBN2Z1Zzd1blVxeUxIRDRZQTg0Y0VpM0wzVWp0KzR1ZUQvQjc1dzBBLzNYQTVTTHB5VXdBRDF5OENHeW9ENVZiUXRSeUN3YmpMTEJFUEhxakJqSjZLcVlWdVZNcTRxNjNncGtwTDNvcUdvZitXenQ4bmQyRFoyN0xrUzZmUEJldEQzbzdwYUtBUm1MeVJxNUhiMDl5UWRZT0RIVGFWRVE4ZWhPKy9RdDhTZURQTmxCQUFCRExicDd3RzRmdCszb3VoRGRaNnpCRXNMVzFEZEtaWDA4c0dxSC9SNkN2bW9IS2hEc1BFYjNCUisvcFgzZGtlL2diMkp3OEJTYzZ3Vnh3OGxyVDUzc2ErQUxWdENOd1dKeC8yb1VReHdVa2lncTVZUzVoTGx0Qno5eklJS05MaExqUXNxODJjY0lWeWxwVlpXemFWMU5TTGRiSlJjdGw5c3VUcTA5K3MxRUs4ZHBicWRleDI3TVRnQmVUeU5pVlJXcUxPYkNWTnd6VHJlSVByeXRFVlJIbHhBQWdMWGZnV3oxQjRHQUlaTG5iQUZqRE5wNUFuSVFwbjNEQWxmanl0Z0JEcUFETU0ydDl2dFc5aTVhREhhZ0pXeVVZNTE2amdWbTB1YWxWbCtkNkVLTVhCZjN2dE1scmV2aE93ZHV0ellYL1pDc2U1RDJEK0wrOXViM3ZNaGo4UFAzUmJuU2h1MDBRL05tU3ljQnVhUkhzdXVKbW0rR2d0cURGOTJRVGtzd0FHbGQ2bWxaVEdRK2xObFE1b1BuUG1Ma0lodXRaTVlzcTZxcXlxbXFDaUtEWGlpY1p3czY2cVB6RThIcDY3WXIwY25YOGNHemtSMWplcVh0S3UxWnJiV3FxOEwxaVlKRTdweUlJMXpOZlNHRWhINUt0ZFVTNjVDZ25Wem8vYkxtdjBaajNHclFxbUFiYXV4OUxyRTd5WjVzS1VUQUd0WEMxbzN1VkpONnU5dGN0RE1MNURLMDQ3ZjlKR2JYQjloVzEzM1pxL3VXYzB1OTIxR2RMUXQ2TkxoT0pxZDN2Yms3OTJtc0FFNjk0VS9uWnIvWEpvaEw5QlRKaWs1MzI5RmtMN3pERlE2L29nUld4TVNTLzZ3VFVlK3RocmVOWnFweVZwVXpQaFVpRzJUNThMbHYxMkJNWkFPUkRVYXFycXB5V3BlRjBjM0NCa3ZnWVVPYk13Vm1DWGJiWVhONmIvdk1kQmMyV3FYMXpGT25rNnF1QXZVTG1CZzN2cytBZ0dzWFRyVkJHR1EvdE5oUVlLR21wekVsbjk2TWJlTHNWa2d0L2tDZkh3S1QzelV2dVVjN3FyNXZVNmU0UVF2UlRzTDBhdTB6cDU4R0IrOFZoM3Z1Y0NtTll3R0R1bmVTbUFOK0g4V3Q2NHFLRDFZQ2g0MnU3QVNUQ3AwQjdDdXlmZU5ONWZ2NW5SR3MvOWVIZFBMSzcwTTZBMmxHQWdKRHlKQ0pMQjhzeVN5L1Nodjh3UmhTOWJRcUMxWE83T1pLWHlvU0xyUkxONlRZM3ZFY01HUkZWMEFpSWRCMHV0bUJYU2R0a1RGTDNHbHVOdTZvM2ZYWjJkL09SSVQ0YlB2RWx1Z0NrQmZwWWluME40TUVyZXI2TDFiRm1BVWRzNEtmVnRXN2FVdHZKYWVwQkIyVWFESGIwaDZzcFFjMjFWNzRYZXlLb1orNTV4ZDJHZnRjU2JRRVgxMDczT3BNYjJ6bWswcnJxbjFnY01nbHo3MUlpb1ZQSVpML1NrODVlYlg3Z3pIN21ObXAxTHBrVGZETllDamtZRkdkMjBpTVlaWXZaZm1TVm5WZFRPdTZVSFhWVkc1TlRzK2ZrTDF4Qy8xMXlOUWNLZmdNN3dVVkdTdW40OWJ0c3FqVlh3U1dJVVVBQXVEcStSTVFTVlhTZWx1UjZQQU55SGR5Z25tbmVIWmludTF4R2VKcnZqVk96YmkvWGZkS2dsS3M1K1EyeDNtOGhTSDBPZDZSMXJNbCtneFR6MjFuS2QwcE5vMjB5V1hmb0haWE00TVpCQS94b21CTFNKWVQ3M01QaklTZnY3K2ZCRUdmWVBSeitjUjMxVFhBeUdVMldNb0d3K2MrWXJUa2tTcUxzcHlxc2lEU0ZoaGpneFV3eUQzVExnbDMySFBPbGNaK2NodUlhRFpkYXpkTWVDU2xRMUNTMXFCUlFzczczeE9iczIvTks0TzhOblQwT2dDMWFMYlFEY3p0LzFMblFQcW1vemQwam1iRm82YUFqM1Bjd3JBL3RsYUQ5cHA1R2F6V0JRc09mbnk3THdaWGJhSTEwRTlmTHoxV0JOM1IzL0c5eWJqaDNaVCtBaTBOL2pvdGpWSmJGaGtBWUZ6S2ZKaGxPYjlLUjhJQlFQT0ozSEkyTVZxQkhWNjVPSkdpdjduUmQyRGt1NFVYYm55ZnFvcXFMbXo4aE5maC92SnVSSUo3dTBGQ1R3cGRnUXhuSHZ2T2kzVEV6ZVFWNGR3dzdROEN4dEFBSlJFUEFaMGpqMkMrTytFdHY4ZjhEU2xKR0pDQVE3Q040Vis4bXVkV0NvN0d4RlJCcXN5a05NUmx1cU8rQUhBZ3FOY1hJNDl5MzY2SDU1bzFhcGNxUHhCSEh5NmFCOWhFcU1TUTRxR0pXMjd6ZFEyUlpWbStsT1ZEdkVyYk5RQk1WWmFxS3FweUdzV0l3eHdlOWhvSXJheTQ5ajJKYUFDa1o3T3hFNkFiWmd6TTB0dzduUkttZWk3bWVQZG1lejhFazliZkliL04wT0NiWGFVREFMUkw0U25FNjdBK292anF1WVZCNnh4ekNaN016T3VKT1VEcXREZWRJYW1FUWQxSnErYytqbk42WmJvME8vbkJ6eFl3SVQwSlJOZ3FGL3F2TzRZR25LYUZwZm5VZGlSNVhuRWpGWXdMbWVYWmNGbUlMS1RpMlNaalZGM015dGxVbTRxbysvSVV3QndYTEZMVVpHcWJob2pGZEd5TUNtemh2RmNEWVhaLzR1WHpKeHpQMXgxNUJFWEc5bzhTRjZFUzJvazF6eFNsaFhEK1JHdFFlM1A5N056Qy9oZzh6cGd4RkprZXIrMStSRVYzR2VOWTNLcTRRQWVsMnFkeGFWM3lXTDJOaVZZWGZRTUpBL0tHRlU1K0JOOWNCaTFLd04yY2RpWGgwU2tUd2JHcWtkNTJnMlF5aUNpeVhHYWpxN0xHMkJHc3Fxb3NKNm9zak5FQVlJOUxEZEppUGdRQ2oxcXJjcm9PakVNTUZUWnZDSS91ZlRlN0hSUE9Nd20ySFhNTmYzUEREL2dQYldHWHgydFlJRWZPKy9HY0t2aE5nYWJ6bnExYkdKbzlPMFZzbXBBSXV5WnBwUmtaTmk1eUgzOHpWNlQ4U2dQTlNiUStoYlQ5dTQ0NmdYZnBWUnJUUWdFcndweHpGY2FsTXFnbGJrNHlBQUJDbXAxeUNIbzNaeDdIN0lpUlova295NWU0dkRwbmJRQ0FjV0xFYlZUSmd1TXFYT0loZGhPTU1hb3VqVmFHREFhTm5wTTZxUTc3WS9YOFNaK0FuckZCRUoxdlFyMmNhY2x1STA3SlFWcW56NngxM0dxaWRZR3doa1JFb2d5UWRndXhpUTZGeGtrMnBJMGlvNEhJUm5zR2Roc1JFWkF4eGhqanlCZ2l0NElUR0VoNHprZzdyeHVUSjMraDAwVFhqd202eGkzRzViWXZaSjU2aFBSRXFoVUlFa1NqVm9kSXI5aTBsUStldGpFWUNGTGtjcmlVWllPck5tSWtVcXF1WnB1cXJyUlc3UmdsTVdVRkRvc1M5dFNHcFJpbDY3b3FnVXdnakFIY3BWdU1BQVQvSC9hYzdvSVdER2UxQUFBQUFFbEZUa1N1UW1DQyI=\"\n\n//# sourceURL=webpack:///./app/assets/routeChart.png?"); +"use strict"; +eval("__webpack_require__.r(__webpack_exports__);\n/* harmony import */ var react__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! react */ \"./node_modules/react/index.js\");\n/* harmony import */ var react__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(react__WEBPACK_IMPORTED_MODULE_0__);\n\n\nvar Header = function Header(props) {\n return /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_0___default.a.createElement(react__WEBPACK_IMPORTED_MODULE_0___default.a.Fragment, null, /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_0___default.a.createElement(\"div\", {\n className: \"logoAndTitle\"\n }, /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_0___default.a.createElement(\"img\", {\n alt: \"Chronos Logo\",\n src: \"app/assets/icon2Cropped.png\",\n id: \"serviceDashLogo\"\n })), /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_0___default.a.createElement(\"div\", {\n className: \"left-top\"\n }, /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_0___default.a.createElement(\"h2\", {\n className: \"dashboardHeader\"\n }, \"Databases\")));\n};\n\n/* harmony default export */ __webpack_exports__[\"default\"] = (Header);\n\n//# sourceURL=webpack:///./app/AComp/SidebarHeader.jsx?"); /***/ }), @@ -105,7 +142,7 @@ eval("module.exports = \"data:image/png;base64,bW9kdWxlLmV4cG9ydHMgPSAiZGF0YTppb /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; -eval("__webpack_require__.r(__webpack_exports__);\n/* harmony import */ var react__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! react */ \"./node_modules/react/index.js\");\n/* harmony import */ var react__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(react__WEBPACK_IMPORTED_MODULE_0__);\n/* harmony import */ var react_chartjs_2__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! react-chartjs-2 */ \"./node_modules/react-chartjs-2/es/index.js\");\n/* harmony import */ var _context_DetailsContext__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! ../context/DetailsContext */ \"./app/context/DetailsContext.js\");\n\n\n\n\nvar LatencyChart = function LatencyChart(props) {\n var xAxis = [];\n var yAxis = [];\n var healthData = Object(react__WEBPACK_IMPORTED_MODULE_0__[\"useContext\"])(_context_DetailsContext__WEBPACK_IMPORTED_MODULE_2__[\"default\"]).detailData;\n\n for (var i = 0; i < healthData.length; i++) {\n var element = healthData[i];\n\n if (element.currentmicroservice === props.service || element.currentMicroservice === props.service) {\n xAxis.push(i);\n yAxis.push(element.latency);\n }\n }\n\n var chartData = {\n datasets: [{\n label: \"CPU latency of \".concat(props.service),\n data: yAxis,\n backgroundColor: ['rgb(254, 255, 0)']\n }],\n options: {\n xAxisID: 'TBD',\n yAxisID: 'TBD'\n },\n labels: xAxis\n };\n return react__WEBPACK_IMPORTED_MODULE_0___default.a.createElement(\"div\", null, react__WEBPACK_IMPORTED_MODULE_0___default.a.createElement(react_chartjs_2__WEBPACK_IMPORTED_MODULE_1__[\"Line\"], {\n data: chartData\n }));\n};\n\n/* harmony default export */ __webpack_exports__[\"default\"] = (LatencyChart);\n\n//# sourceURL=webpack:///./app/charts/latency-chart.jsx?"); +eval("__webpack_require__.r(__webpack_exports__);\n/* harmony import */ var react__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! react */ \"./node_modules/react/index.js\");\n/* harmony import */ var react__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(react__WEBPACK_IMPORTED_MODULE_0__);\n/* harmony import */ var _context_DetailsContext__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ../context/DetailsContext */ \"./app/context/DetailsContext.js\");\n/* harmony import */ var react_plotly_js__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! react-plotly.js */ \"./node_modules/react-plotly.js/react-plotly.js\");\n/* harmony import */ var react_plotly_js__WEBPACK_IMPORTED_MODULE_2___default = /*#__PURE__*/__webpack_require__.n(react_plotly_js__WEBPACK_IMPORTED_MODULE_2__);\n\n\n\n\nvar LatencyChart = function LatencyChart(props) {\n var healthData = Object(react__WEBPACK_IMPORTED_MODULE_0__[\"useContext\"])(_context_DetailsContext__WEBPACK_IMPORTED_MODULE_1__[\"default\"]).detailData;\n\n var createChart = function createChart() {\n var xAxis = [];\n var yAxis = [];\n\n for (var i = 0; i < healthData.length; i++) {\n var element = healthData[i];\n\n if (element.currentmicroservice === props.service || element.currentMicroservice === props.service) {\n xAxis.push(i);\n yAxis.push(element.latency);\n }\n }\n\n return /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_0___default.a.createElement(react_plotly_js__WEBPACK_IMPORTED_MODULE_2___default.a, {\n data: [{\n type: 'scatter',\n x: xAxis,\n y: yAxis,\n mode: 'lines',\n rangemode: 'nonnegative',\n name: \"\".concat(props.service, \" CPU Latency\"),\n marker: {\n color: '#155263'\n }\n }],\n layout: {\n width: 500,\n height: 500,\n paper_bgcolor: '#fffbe0',\n plot_bgcolor: '#fffbe0',\n showlegend: true,\n legend: {\n orientation: 'h',\n xanchor: 'center',\n x: .5\n },\n xaxis: {\n tickmode: 'linear',\n tick0: 0,\n dtick: 200,\n rangemode: 'nonnegative'\n },\n yaxis: {\n rangemode: 'nonnegative'\n }\n }\n });\n };\n\n return /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_0___default.a.createElement(\"div\", null, createChart());\n};\n\n/* harmony default export */ __webpack_exports__[\"default\"] = (LatencyChart);\n\n//# sourceURL=webpack:///./app/charts/latency-chart.jsx?"); /***/ }), @@ -117,7 +154,7 @@ eval("__webpack_require__.r(__webpack_exports__);\n/* harmony import */ var reac /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; -eval("__webpack_require__.r(__webpack_exports__);\n/* harmony import */ var react__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! react */ \"./node_modules/react/index.js\");\n/* harmony import */ var react__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(react__WEBPACK_IMPORTED_MODULE_0__);\n/* harmony import */ var react_chartjs_2__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! react-chartjs-2 */ \"./node_modules/react-chartjs-2/es/index.js\");\n/* harmony import */ var _context_DetailsContext__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! ../context/DetailsContext */ \"./app/context/DetailsContext.js\");\n\n\n\n\nvar MemoryChart = function MemoryChart(props) {\n var healthData = Object(react__WEBPACK_IMPORTED_MODULE_0__[\"useContext\"])(_context_DetailsContext__WEBPACK_IMPORTED_MODULE_2__[\"default\"]).detailData;\n\n var createChart = function createChart() {\n var xAxis = [];\n var free = [];\n var used = [];\n var active = [];\n var total = [];\n\n for (var i = 0; i < healthData.length; i += 1) {\n xAxis.push(i); // If Mongo\n\n if (healthData[i].currentMicroservice === props.service) {\n free.push(healthData[i].freeMemory);\n active.push(healthData[i].activeMemory);\n used.push(healthData[i].usedMemory);\n total.push(healthData[i].totalMemory);\n } // If SQL\n\n\n if (healthData[i].currentmicroservice === props.service) {\n free.push(healthData[i].freememory);\n active.push(healthData[i].activememory);\n used.push(healthData[i].usedmemory);\n total.push(healthData[i].totalmemory);\n }\n }\n\n var chartData = {\n datasets: [{\n label: 'Free Memory',\n backgroundColor: 'rgb(2, 210, 249)',\n data: free // showLine: true,\n\n }, {\n label: 'Used Memory',\n backgroundColor: 'rgb(239, 91, 145)',\n data: used // showLine: true,\n\n }, {\n label: 'Active Memory',\n backgroundColor: 'rgb(182, 219, 26)',\n data: active // showLine: true,\n\n }, {\n label: 'Total Memory',\n backgroundColor: 'rgb(252, 170, 52)',\n data: total // showLine: true,\n\n }],\n labels: xAxis\n };\n return react__WEBPACK_IMPORTED_MODULE_0___default.a.createElement(react_chartjs_2__WEBPACK_IMPORTED_MODULE_1__[\"Bar\"], {\n data: chartData\n });\n };\n\n return react__WEBPACK_IMPORTED_MODULE_0___default.a.createElement(\"div\", null, createChart());\n};\n\n/* harmony default export */ __webpack_exports__[\"default\"] = (MemoryChart);\n\n//# sourceURL=webpack:///./app/charts/memory-chart.jsx?"); +eval("__webpack_require__.r(__webpack_exports__);\n/* harmony import */ var react__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! react */ \"./node_modules/react/index.js\");\n/* harmony import */ var react__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(react__WEBPACK_IMPORTED_MODULE_0__);\n/* harmony import */ var _context_DetailsContext__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ../context/DetailsContext */ \"./app/context/DetailsContext.js\");\n/* harmony import */ var react_plotly_js__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! react-plotly.js */ \"./node_modules/react-plotly.js/react-plotly.js\");\n/* harmony import */ var react_plotly_js__WEBPACK_IMPORTED_MODULE_2___default = /*#__PURE__*/__webpack_require__.n(react_plotly_js__WEBPACK_IMPORTED_MODULE_2__);\n\n\n\n\nvar MemoryChart = function MemoryChart(props) {\n var healthData = Object(react__WEBPACK_IMPORTED_MODULE_0__[\"useContext\"])(_context_DetailsContext__WEBPACK_IMPORTED_MODULE_1__[\"default\"]).detailData;\n\n var createChart = function createChart() {\n var xAxis = [];\n var free = [];\n var used = [];\n var active = [];\n var total = [];\n\n for (var i = 0; i < healthData.length; i += 1) {\n xAxis.push(i); // If Mongo\n\n if (healthData[i].currentMicroservice === props.service) {\n free.push(healthData[i].freeMemory);\n active.push(healthData[i].activeMemory);\n used.push(healthData[i].usedMemory);\n total.push(healthData[i].totalMemory);\n } // If SQL\n\n\n if (healthData[i].currentmicroservice === props.service) {\n free.push(healthData[i].freememory);\n active.push(healthData[i].activememory);\n used.push(healthData[i].usedmemory);\n total.push(healthData[i].totalmemory);\n }\n }\n\n return /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_0___default.a.createElement(react_plotly_js__WEBPACK_IMPORTED_MODULE_2___default.a, {\n data: [{\n type: 'scatter',\n fill: 'tozeroy',\n fillcolor: 'rgb(14, 49, 80)',\n mode: 'none',\n x: {\n autorange: true\n },\n y: free,\n name: 'Free Memory',\n rangemode: 'nonnegative'\n }, {\n type: 'scatter',\n fill: 'tozeroy',\n fillcolor: 'rgba(255, 64, 87, .3)',\n mode: 'none',\n x: {\n autorange: true\n },\n y: used,\n name: 'Used Memory',\n rangemode: 'nonnegative'\n }, {\n type: 'scatter',\n fill: 'tozeroy',\n fillcolor: 'rgba(144, 0, 72, .4)',\n mode: 'none',\n x: {\n autorange: true\n },\n y: active,\n name: 'Active Memory',\n rangemode: 'nonnegative'\n }, {\n label: xAxis\n }],\n layout: {\n width: 500,\n height: 500,\n paper_bgcolor: '#fffbe0',\n plot_bgcolor: '#fffbe0',\n legend: {\n itemsizing: 'constant',\n orientation: 'h',\n xanchor: 'center',\n x: .5\n },\n xaxis: {\n tickmode: 'linear',\n tick0: 0,\n dtick: 100\n }\n }\n });\n };\n\n return /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_0___default.a.createElement(\"div\", null, createChart());\n};\n\n/* harmony default export */ __webpack_exports__[\"default\"] = (MemoryChart);\n\n//# sourceURL=webpack:///./app/charts/memory-chart.jsx?"); /***/ }), @@ -129,7 +166,7 @@ eval("__webpack_require__.r(__webpack_exports__);\n/* harmony import */ var reac /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; -eval("__webpack_require__.r(__webpack_exports__);\n/* harmony import */ var react__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! react */ \"./node_modules/react/index.js\");\n/* harmony import */ var react__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(react__WEBPACK_IMPORTED_MODULE_0__);\n/* harmony import */ var react_chartjs_2__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! react-chartjs-2 */ \"./node_modules/react-chartjs-2/es/index.js\");\n/* harmony import */ var _context_OverviewContext__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! ../context/OverviewContext */ \"./app/context/OverviewContext.js\");\nfunction _toConsumableArray(arr) { return _arrayWithoutHoles(arr) || _iterableToArray(arr) || _nonIterableSpread(); }\n\nfunction _nonIterableSpread() { throw new TypeError(\"Invalid attempt to spread non-iterable instance\"); }\n\nfunction _iterableToArray(iter) { if (Symbol.iterator in Object(iter) || Object.prototype.toString.call(iter) === \"[object Arguments]\") return Array.from(iter); }\n\nfunction _arrayWithoutHoles(arr) { if (Array.isArray(arr)) { for (var i = 0, arr2 = new Array(arr.length); i < arr.length; i++) { arr2[i] = arr[i]; } return arr2; } }\n\n\n\n\n\nvar MicroServiceTraffic = function MicroServiceTraffic(props) {\n var communicationsData = Object(react__WEBPACK_IMPORTED_MODULE_0__[\"useContext\"])(_context_OverviewContext__WEBPACK_IMPORTED_MODULE_2__[\"default\"]).overviewData; //initialize an empty object resObj. This object will store the microservice names as values and its corresponding correlatingId or correlatingid as keys. The microservice names will be stored in array within the order it was to the database. \n\n var resObj = {};\n\n if (communicationsData.length > 0 && communicationsData[0][\"_id\"]) {\n //Sort the communication array from latest to earliest document\n communicationsData.sort(function (a, b) {\n if (new Date(a.timeSent) > new Date(b.timeSent)) return 1;\n if (new Date(a.timeSent) < new Date(b.timeSent)) return -1;\n return 0;\n }); //Iterate over sorted communicationsData array from the end to the beginning\n\n for (var i = 0; i < communicationsData.length; i += 1) {\n //declare a constant element and initialize it as the object at index i of the communicationsData array\n var element = communicationsData[i]; //Pushes the microservice name into the object \n\n if (resObj[element.correlatingId]) {\n resObj[element.correlatingId].push(element.currentMicroservice);\n } else resObj[element.correlatingId] = [element.currentMicroservice];\n }\n } else {\n for (var _i = communicationsData.length - 1; _i >= 0; _i--) {\n var _element = communicationsData[_i];\n if (resObj[_element.correlatingid]) resObj[_element.correlatingid].push(_element.currentmicroservice);else resObj[_element.correlatingid] = [_element.currentmicroservice]; // initializing the object with the first microservice\n }\n\n ;\n } //use object values to destructure locations\n\n\n var tracePoints = Object.values(resObj); // Declare Micro-server-count dictinary to capture the amount of times a particular server is hit\n\n var microServiceCountdictionary = {}; //array logging every ping present in communications table ---> flat used to flatten multidimensional array and return 1d array\n\n var tracePointLog = tracePoints.flat(Infinity); // iterate over Trace Points\n\n for (var _i2 = 0; _i2 < tracePointLog.length; _i2 += 1) {\n // populate Micro-count dictionary\n if (!microServiceCountdictionary[tracePointLog[_i2]]) {\n microServiceCountdictionary[tracePointLog[_i2]] = 1;\n } else {\n microServiceCountdictionary[tracePointLog[_i2]] += 1;\n }\n }\n\n ; // capture values of microServiceCountdictionary to use as data to populate chart object\n\n var serverPingCount = Object.values(microServiceCountdictionary); // variable 10 points higher than max number in microservicesDictionary aggregation --> variable allows for top level spacing on bar graph\n\n var yAxisHeadRoom = Math.max.apply(Math, _toConsumableArray(serverPingCount)) + 10; // Create chart object data to feed into bar component \n\n var myChart = {\n //spread dictionary keys inorder to properly label chart x axis \n labels: _toConsumableArray(Object.keys(microServiceCountdictionary)),\n datasets: [{\n label: 'Times server Pinged',\n backgroundColor: 'rgba(241, 207, 70,1)',\n borderColor: 'rgba(0,0,0,1)',\n borderWidth: 1,\n data: [].concat(_toConsumableArray(serverPingCount), [0, yAxisHeadRoom]) // spread ping count array into data array to have chart populate the Y axis\n\n }]\n }; // return div with Bar component and Trace Points data\n\n return react__WEBPACK_IMPORTED_MODULE_0___default.a.createElement(\"div\", null, react__WEBPACK_IMPORTED_MODULE_0___default.a.createElement(react_chartjs_2__WEBPACK_IMPORTED_MODULE_1__[\"Bar\"], {\n data: myChart,\n width: 100,\n height: 50,\n options: {\n title: {\n display: true,\n text: 'Microservices Overview',\n fontSize: 20\n },\n legend: {\n display: true,\n position: 'right'\n }\n }\n }));\n};\n\n/* harmony default export */ __webpack_exports__[\"default\"] = (MicroServiceTraffic);\n\n//# sourceURL=webpack:///./app/charts/microservice-traffic.jsx?"); +eval("__webpack_require__.r(__webpack_exports__);\n/* harmony import */ var react__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! react */ \"./node_modules/react/index.js\");\n/* harmony import */ var react__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(react__WEBPACK_IMPORTED_MODULE_0__);\n/* harmony import */ var react_plotly_js__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! react-plotly.js */ \"./node_modules/react-plotly.js/react-plotly.js\");\n/* harmony import */ var react_plotly_js__WEBPACK_IMPORTED_MODULE_1___default = /*#__PURE__*/__webpack_require__.n(react_plotly_js__WEBPACK_IMPORTED_MODULE_1__);\n/* harmony import */ var _context_OverviewContext__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! ../context/OverviewContext */ \"./app/context/OverviewContext.js\");\nfunction _toConsumableArray(arr) { return _arrayWithoutHoles(arr) || _iterableToArray(arr) || _unsupportedIterableToArray(arr) || _nonIterableSpread(); }\n\nfunction _nonIterableSpread() { throw new TypeError(\"Invalid attempt to spread non-iterable instance.\\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.\"); }\n\nfunction _unsupportedIterableToArray(o, minLen) { if (!o) return; if (typeof o === \"string\") return _arrayLikeToArray(o, minLen); var n = Object.prototype.toString.call(o).slice(8, -1); if (n === \"Object\" && o.constructor) n = o.constructor.name; if (n === \"Map\" || n === \"Set\") return Array.from(o); if (n === \"Arguments\" || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(n)) return _arrayLikeToArray(o, minLen); }\n\nfunction _iterableToArray(iter) { if (typeof Symbol !== \"undefined\" && Symbol.iterator in Object(iter)) return Array.from(iter); }\n\nfunction _arrayWithoutHoles(arr) { if (Array.isArray(arr)) return _arrayLikeToArray(arr); }\n\nfunction _arrayLikeToArray(arr, len) { if (len == null || len > arr.length) len = arr.length; for (var i = 0, arr2 = new Array(len); i < len; i++) { arr2[i] = arr[i]; } return arr2; }\n\n\n\n\n\nvar MicroServiceTraffic = function MicroServiceTraffic(props) {\n var communicationsData = Object(react__WEBPACK_IMPORTED_MODULE_0__[\"useContext\"])(_context_OverviewContext__WEBPACK_IMPORTED_MODULE_2__[\"default\"]).overviewData; //initialize an empty object resObj. This object will store the microservice names as values and its corresponding correlatingId or correlatingid as keys. The microservice names will be stored in array within the order it was to the database. \n\n var resObj = {};\n\n if (communicationsData.length > 0 && communicationsData[0][\"_id\"]) {\n //Sort the communication array from latest to earliest document\n communicationsData.sort(function (a, b) {\n if (new Date(a.timeSent) > new Date(b.timeSent)) return 1;\n if (new Date(a.timeSent) < new Date(b.timeSent)) return -1;\n return 0;\n }); //Iterate over sorted communicationsData array from the end to the beginning\n\n for (var i = 0; i < communicationsData.length; i += 1) {\n //declare a constant element and initialize it as the object at index i of the communicationsData array\n var element = communicationsData[i]; //Pushes the microservice name into the object \n\n if (resObj[element.correlatingId]) {\n resObj[element.correlatingId].push(element.currentMicroservice);\n } else resObj[element.correlatingId] = [element.currentMicroservice];\n }\n } else {\n for (var _i = communicationsData.length - 1; _i >= 0; _i--) {\n var _element = communicationsData[_i];\n if (resObj[_element.correlatingid]) resObj[_element.correlatingid].push(_element.currentmicroservice);else resObj[_element.correlatingid] = [_element.currentmicroservice]; // initializing the object with the first microservice\n }\n\n ;\n } //use object values to destructure locations\n\n\n var tracePoints = Object.values(resObj); // Declare Micro-server-count dictinary to capture the amount of times a particular server is hit\n\n var microServiceCountdictionary = {}; //array logging every ping present in communications table ---> flat used to flatten multidimensional array and return 1d array\n\n var tracePointLog = tracePoints.flat(Infinity); // iterate over Trace Points\n\n for (var _i2 = 0; _i2 < tracePointLog.length; _i2 += 1) {\n // populate Micro-count dictionary\n if (!microServiceCountdictionary[tracePointLog[_i2]]) {\n microServiceCountdictionary[tracePointLog[_i2]] = 1;\n } else {\n microServiceCountdictionary[tracePointLog[_i2]] += 1;\n }\n }\n\n ; // capture values of microServiceCountdictionary to use as data to populate chart object\n\n var serverPingCount = Object.values(microServiceCountdictionary); // variable 10 points higher than max number in microservicesDictionary aggregation --> variable allows for top level spacing on bar graph\n\n var yAxisHeadRoom = Math.max.apply(Math, _toConsumableArray(serverPingCount)) + 10; // Create chart object data to feed into bar component \n\n var myChart = {\n //spread dictionary keys inorder to properly label chart x axis \n labels: _toConsumableArray(Object.keys(microServiceCountdictionary)),\n datasets: [{\n label: 'Times server Pinged',\n backgroundColor: 'rgba(241, 207, 70,1)',\n borderColor: 'rgba(0,0,0,1)',\n borderWidth: 1,\n data: [].concat(_toConsumableArray(serverPingCount), [0, yAxisHeadRoom]) // spread ping count array into data array to have chart populate the Y axis\n\n }]\n };\n return /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_0___default.a.createElement(react_plotly_js__WEBPACK_IMPORTED_MODULE_1___default.a, {\n data: [{\n type: 'bar',\n x: ['Orders', 'Customers', 'Books', 'Reverse-Proxy', 'ReverseProxy'],\n y: [].concat(_toConsumableArray(serverPingCount), [0, yAxisHeadRoom]),\n fill: 'tozeroy',\n color: 'red',\n opacity: .4,\n mode: 'none',\n name: 'Times Server Pinged',\n showlegend: true\n }],\n layout: {\n width: 500,\n height: 500,\n paper_bgcolor: '#fffbe0',\n plot_bgcolor: '#fffbe0',\n legend: {\n orientation: 'h',\n xanchor: 'center',\n x: .5,\n y: 5\n },\n yaxis: {\n rangemode: 'nonnegative'\n }\n }\n });\n};\n\n/* harmony default export */ __webpack_exports__[\"default\"] = (MicroServiceTraffic);\n\n//# sourceURL=webpack:///./app/charts/microservice-traffic.jsx?"); /***/ }), @@ -141,7 +178,7 @@ eval("__webpack_require__.r(__webpack_exports__);\n/* harmony import */ var reac /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; -eval("__webpack_require__.r(__webpack_exports__);\n/* harmony import */ var react__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! react */ \"./node_modules/react/index.js\");\n/* harmony import */ var react__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(react__WEBPACK_IMPORTED_MODULE_0__);\n/* harmony import */ var react_chartjs_2__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! react-chartjs-2 */ \"./node_modules/react-chartjs-2/es/index.js\");\n/* harmony import */ var _context_DetailsContext__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! ../context/DetailsContext */ \"./app/context/DetailsContext.js\");\n\n\n\n\nvar ProcessesChart = function ProcessesChart(props) {\n var healthData = Object(react__WEBPACK_IMPORTED_MODULE_0__[\"useContext\"])(_context_DetailsContext__WEBPACK_IMPORTED_MODULE_2__[\"default\"]).detailData;\n\n var createChart = function createChart() {\n var communicationLabel = [];\n var totalProcesses = [];\n var runningProcesses = [];\n var blockedProcesses = [];\n var sleepingProcesses = [];\n\n for (var i = 0; i < healthData.length; i += 1) {\n var element = healthData[i]; // If using a SQL Database\n\n if (element.currentmicroservice === props.service) {\n communicationLabel.push(i);\n totalProcesses.push(element.totalnumprocesses);\n runningProcesses.push(element.numrunningprocesses);\n blockedProcesses.push(element.numblockedprocesses);\n sleepingProcesses.push(element.numsleepingprocesses);\n } // If using a Mongo Database\n\n\n if (element.currentMicroservice === props.service && element.cpuCurrentSpeed) {\n communicationLabel.push(i);\n totalProcesses.push(element.numTotalProcesses);\n runningProcesses.push(element.numRunningProcesses);\n blockedProcesses.push(element.numBlockedProcesses);\n sleepingProcesses.push(element.numSleepingProcesses);\n }\n }\n\n var chartData = {\n datasets: [{\n label: 'Blocked Processes',\n backgroundColor: 'rgb(198, 42, 177)',\n data: blockedProcesses\n }, {\n label: 'Sleeping Processes',\n backgroundColor: 'rgb(252, 170, 52)',\n data: sleepingProcesses\n }, {\n label: 'Running Processes',\n backgroundColor: 'rgb(239, 91, 145)',\n data: runningProcesses\n }, {\n label: 'Total Processes',\n backgroundColor: 'rgb(182, 219, 26)',\n data: totalProcesses\n }],\n labels: communicationLabel\n };\n return react__WEBPACK_IMPORTED_MODULE_0___default.a.createElement(react_chartjs_2__WEBPACK_IMPORTED_MODULE_1__[\"Bar\"], {\n data: chartData\n });\n };\n\n return react__WEBPACK_IMPORTED_MODULE_0___default.a.createElement(\"div\", null, createChart());\n};\n\n/* harmony default export */ __webpack_exports__[\"default\"] = (ProcessesChart);\n\n//# sourceURL=webpack:///./app/charts/processes-chart.jsx?"); +eval("__webpack_require__.r(__webpack_exports__);\n/* harmony import */ var react__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! react */ \"./node_modules/react/index.js\");\n/* harmony import */ var react__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(react__WEBPACK_IMPORTED_MODULE_0__);\n/* harmony import */ var react_plotly_js__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! react-plotly.js */ \"./node_modules/react-plotly.js/react-plotly.js\");\n/* harmony import */ var react_plotly_js__WEBPACK_IMPORTED_MODULE_1___default = /*#__PURE__*/__webpack_require__.n(react_plotly_js__WEBPACK_IMPORTED_MODULE_1__);\n/* harmony import */ var _context_DetailsContext__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! ../context/DetailsContext */ \"./app/context/DetailsContext.js\");\n\n\n\n\nvar ProcessesChart = function ProcessesChart(props) {\n var healthData = Object(react__WEBPACK_IMPORTED_MODULE_0__[\"useContext\"])(_context_DetailsContext__WEBPACK_IMPORTED_MODULE_2__[\"default\"]).detailData;\n\n var createChart = function createChart() {\n var communicationLabel = [];\n var totalProcesses = [];\n var runningProcesses = [];\n var blockedProcesses = [];\n var sleepingProcesses = [];\n\n for (var i = 0; i < healthData.length; i += 1) {\n var element = healthData[i]; // If using a SQL Database\n\n if (element.currentmicroservice === props.service) {\n communicationLabel.push(i);\n totalProcesses.push(element.totalnumprocesses);\n runningProcesses.push(element.numrunningprocesses);\n blockedProcesses.push(element.numblockedprocesses);\n sleepingProcesses.push(element.numsleepingprocesses);\n } // If using a Mongo Database\n\n\n if (element.currentMicroservice === props.service && element.cpuCurrentSpeed) {\n communicationLabel.push(i);\n totalProcesses.push(element.numTotalProcesses);\n runningProcesses.push(element.numRunningProcesses);\n blockedProcesses.push(element.numBlockedProcesses);\n sleepingProcesses.push(element.numSleepingProcesses);\n }\n }\n\n return /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_0___default.a.createElement(react_plotly_js__WEBPACK_IMPORTED_MODULE_1___default.a, {\n data: [{\n type: 'scatter',\n x: {\n autorange: true\n },\n y: runningProcesses,\n mode: 'markers',\n rangemode: 'nonnegative',\n name: 'Running Processes',\n marker: {\n color: '#3ec1d3',\n size: 3\n }\n }, {\n type: 'scatter',\n x: {\n autorange: true\n },\n y: blockedProcesses,\n mode: 'markers',\n rangemode: 'nonnegative',\n name: 'Blocked Processes',\n marker: {\n color: '#ff9a00',\n size: 3\n }\n }, {\n type: 'scatter',\n x: {\n autorange: true\n },\n y: sleepingProcesses,\n mode: 'markers',\n rangemode: 'nonnegative',\n name: 'Sleeping Processes',\n marker: {\n color: '#ff165d',\n size: 3\n }\n }, {\n label: communicationLabel\n }],\n layout: {\n width: 500,\n height: 500,\n paper_bgcolor: '#fffbe0',\n plot_bgcolor: '#fffbe0',\n legend: {\n itemsizing: 'constant',\n orientation: 'h',\n xanchor: 'center',\n x: .5\n }\n }\n });\n };\n\n return /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_0___default.a.createElement(\"div\", null, createChart());\n};\n\n/* harmony default export */ __webpack_exports__[\"default\"] = (ProcessesChart);\n\n//# sourceURL=webpack:///./app/charts/processes-chart.jsx?"); /***/ }), @@ -153,7 +190,7 @@ eval("__webpack_require__.r(__webpack_exports__);\n/* harmony import */ var reac /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; -eval("__webpack_require__.r(__webpack_exports__);\n/* harmony import */ var react__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! react */ \"./node_modules/react/index.js\");\n/* harmony import */ var react__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(react__WEBPACK_IMPORTED_MODULE_0__);\n/* harmony import */ var react_chartjs_2__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! react-chartjs-2 */ \"./node_modules/react-chartjs-2/es/index.js\");\n/* harmony import */ var _context_OverviewContext__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! ../context/OverviewContext */ \"./app/context/OverviewContext.js\");\n\n\n\n\nvar RequestTypesChart = function RequestTypesChart(props) {\n var communicationsData = Object(react__WEBPACK_IMPORTED_MODULE_0__[\"useContext\"])(_context_OverviewContext__WEBPACK_IMPORTED_MODULE_2__[\"default\"]).overviewData;\n\n var createRequestChart = function createRequestChart() {\n var requestObj = {\n DELETE: 0,\n GET: 0,\n PATCH: 0,\n POST: 0,\n PUSH: 0,\n PUT: 0\n };\n\n for (var i = 0; i < communicationsData.length; i += 1) {\n var element = communicationsData[i]; // if Mongo\n\n if (element.currentMicroservice === props.service && element.reqType in requestObj) requestObj[element.reqType] += 1; // if SQL\n else if (element.currentmicroservice === props.service && element.reqtype in requestObj) requestObj[element.reqtype] += 1;\n }\n\n var chartData = {\n datasets: [{\n data: Object.values(requestObj),\n backgroundColor: ['rgb(2, 210, 249)', 'rgb(198, 42, 177)', 'rgb(252, 170, 52)', 'rgb(239, 91, 145)', 'rgb(182, 219, 26)', 'rgb(254, 255, 0)']\n }],\n labels: ['DELETE', 'GET', 'PATCH', 'POST', 'PUSH', 'PUT']\n };\n return react__WEBPACK_IMPORTED_MODULE_0___default.a.createElement(react_chartjs_2__WEBPACK_IMPORTED_MODULE_1__[\"Doughnut\"], {\n data: chartData\n });\n };\n\n return react__WEBPACK_IMPORTED_MODULE_0___default.a.createElement(\"div\", null, createRequestChart());\n};\n\n/* harmony default export */ __webpack_exports__[\"default\"] = (RequestTypesChart);\n\n//# sourceURL=webpack:///./app/charts/request-type-chart.jsx?"); +eval("__webpack_require__.r(__webpack_exports__);\n/* harmony import */ var react__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! react */ \"./node_modules/react/index.js\");\n/* harmony import */ var react__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(react__WEBPACK_IMPORTED_MODULE_0__);\n/* harmony import */ var react_plotly_js__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! react-plotly.js */ \"./node_modules/react-plotly.js/react-plotly.js\");\n/* harmony import */ var react_plotly_js__WEBPACK_IMPORTED_MODULE_1___default = /*#__PURE__*/__webpack_require__.n(react_plotly_js__WEBPACK_IMPORTED_MODULE_1__);\n/* harmony import */ var _context_OverviewContext__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! ../context/OverviewContext */ \"./app/context/OverviewContext.js\");\n\n\n\n\nvar RequestTypesChart = function RequestTypesChart(props) {\n var communicationsData = Object(react__WEBPACK_IMPORTED_MODULE_0__[\"useContext\"])(_context_OverviewContext__WEBPACK_IMPORTED_MODULE_2__[\"default\"]).overviewData;\n\n var createRequestChart = function createRequestChart() {\n var requestObj = {\n DELETE: 0,\n GET: 0,\n PATCH: 0,\n POST: 0,\n PUSH: 0,\n PUT: 0\n };\n\n for (var i = 0; i < communicationsData.length; i += 1) {\n var element = communicationsData[i]; // if Mongo\n\n if (element.currentMicroservice === props.service && element.reqType in requestObj) requestObj[element.reqType] += 1; // if SQL\n else if (element.currentmicroservice === props.service && element.reqtype in requestObj) requestObj[element.reqtype] += 1;\n }\n\n return /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_0___default.a.createElement(react_plotly_js__WEBPACK_IMPORTED_MODULE_1___default.a, {\n data: [{\n values: Object.values(requestObj),\n labels: ['DELETE', 'GET', 'PATCH', 'POST', 'PUSH', 'PUT'],\n type: 'pie',\n marker: {\n 'colors': ['#95e1d3', '#fce38a', '#fcbad3', '#aa96da', '#a8d8ea', '#f38181']\n }\n }],\n layout: {\n height: 500,\n width: 500,\n displaylogo: false,\n paper_bgcolor: '#fffbe0',\n legend: {\n orientation: 'h',\n xanchor: 'center',\n x: .5\n }\n }\n });\n };\n\n return /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_0___default.a.createElement(\"div\", null, createRequestChart());\n};\n\n/* harmony default export */ __webpack_exports__[\"default\"] = (RequestTypesChart);\n\n//# sourceURL=webpack:///./app/charts/request-type-chart.jsx?"); /***/ }), @@ -165,7 +202,7 @@ eval("__webpack_require__.r(__webpack_exports__);\n/* harmony import */ var reac /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; -eval("__webpack_require__.r(__webpack_exports__);\n/* harmony import */ var react__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! react */ \"./node_modules/react/index.js\");\n/* harmony import */ var react__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(react__WEBPACK_IMPORTED_MODULE_0__);\n/* harmony import */ var react_chartjs_2__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! react-chartjs-2 */ \"./node_modules/react-chartjs-2/es/index.js\");\n/* harmony import */ var _context_OverviewContext__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! ../context/OverviewContext */ \"./app/context/OverviewContext.js\");\n\n\n\n\nvar ResponseCodeChart = function ResponseCodeChart(props) {\n var communicationsData = Object(react__WEBPACK_IMPORTED_MODULE_0__[\"useContext\"])(_context_OverviewContext__WEBPACK_IMPORTED_MODULE_2__[\"default\"]).overviewData;\n\n var createChart = function createChart() {\n var responseCodes = {\n '100-199': 0,\n '200-299': 0,\n '300-399': 0,\n '400-499': 0,\n '500-599': 0,\n 'NULL': 0\n };\n\n for (var i = 0; i < communicationsData.length; i += 1) {\n var element = communicationsData[i]; // If Mongo Else SQL\n\n if (element.currentMicroservice === props.service && element.resStatus) {\n var statusCode = element.resStatus;\n\n if (statusCode <= 199) {\n responseCodes['100-199'] += 1;\n } else if (statusCode <= 299) {\n responseCodes['200-299'] += 1;\n } else if (statusCode <= 399) {\n responseCodes['300-399'] += 1;\n } else if (statusCode <= 499) {\n responseCodes['400-499'] += 1;\n } else if (statusCode <= 599) {\n responseCodes['500-599'] += 1;\n } else {\n responseCodes['NULL'] += 1;\n }\n } else if (element.currentmicroservice === props.service && element.resstatus) {\n var _statusCode = element.resstatus;\n\n if (_statusCode <= 199) {\n responseCodes['100-199'] += 1;\n } else if (_statusCode <= 299) {\n responseCodes['200-299'] += 1;\n } else if (_statusCode <= 399) {\n responseCodes['300-399'] += 1;\n } else if (_statusCode <= 499) {\n responseCodes['400-499'] += 1;\n } else if (_statusCode <= 599) {\n responseCodes['500-599'] += 1;\n } else {\n responseCodes['NULL'] += 1;\n }\n }\n }\n\n var chartData = {\n datasets: [{\n data: Object.values(responseCodes),\n backgroundColor: ['rgb(2, 210, 249)', 'rgb(198, 42, 177)', 'rgb(252, 170, 52)', 'rgb(239, 91, 145)', 'rgb(182, 219, 26)', 'rgb(254, 255, 0)']\n }],\n labels: ['100-199', '200-299', '300-399', '400-499', '500-599', 'Null']\n };\n return react__WEBPACK_IMPORTED_MODULE_0___default.a.createElement(react_chartjs_2__WEBPACK_IMPORTED_MODULE_1__[\"Doughnut\"], {\n data: chartData\n });\n };\n\n return react__WEBPACK_IMPORTED_MODULE_0___default.a.createElement(\"div\", null, createChart());\n};\n\n/* harmony default export */ __webpack_exports__[\"default\"] = (ResponseCodeChart);\n\n//# sourceURL=webpack:///./app/charts/response-code-chart.jsx?"); +eval("__webpack_require__.r(__webpack_exports__);\n/* harmony import */ var react__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! react */ \"./node_modules/react/index.js\");\n/* harmony import */ var react__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(react__WEBPACK_IMPORTED_MODULE_0__);\n/* harmony import */ var react_plotly_js__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! react-plotly.js */ \"./node_modules/react-plotly.js/react-plotly.js\");\n/* harmony import */ var react_plotly_js__WEBPACK_IMPORTED_MODULE_1___default = /*#__PURE__*/__webpack_require__.n(react_plotly_js__WEBPACK_IMPORTED_MODULE_1__);\n/* harmony import */ var _context_OverviewContext__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! ../context/OverviewContext */ \"./app/context/OverviewContext.js\");\n\n\n\n\nvar ResponseCodeChart = function ResponseCodeChart(props) {\n var communicationsData = Object(react__WEBPACK_IMPORTED_MODULE_0__[\"useContext\"])(_context_OverviewContext__WEBPACK_IMPORTED_MODULE_2__[\"default\"]).overviewData;\n\n var createChart = function createChart() {\n var responseCodes = {\n '100-199': 0,\n '200-299': 0,\n '300-399': 0,\n '400-499': 0,\n '500-599': 0,\n 'NULL': 0\n };\n\n for (var i = 0; i < communicationsData.length; i += 1) {\n var element = communicationsData[i]; // If Mongo Else SQL\n\n if (element.currentMicroservice === props.service && element.resStatus) {\n var statusCode = element.resStatus;\n\n if (statusCode <= 199) {\n responseCodes['100-199'] += 1;\n } else if (statusCode <= 299) {\n responseCodes['200-299'] += 1;\n } else if (statusCode <= 399) {\n responseCodes['300-399'] += 1;\n } else if (statusCode <= 499) {\n responseCodes['400-499'] += 1;\n } else if (statusCode <= 599) {\n responseCodes['500-599'] += 1;\n } else {\n responseCodes['NULL'] += 1;\n }\n } else if (element.currentmicroservice === props.service && element.resstatus) {\n var _statusCode = element.resstatus;\n\n if (_statusCode <= 199) {\n responseCodes['100-199'] += 1;\n } else if (_statusCode <= 299) {\n responseCodes['200-299'] += 1;\n } else if (_statusCode <= 399) {\n responseCodes['300-399'] += 1;\n } else if (_statusCode <= 499) {\n responseCodes['400-499'] += 1;\n } else if (_statusCode <= 599) {\n responseCodes['500-599'] += 1;\n } else {\n responseCodes['NULL'] += 1;\n }\n }\n }\n\n return /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_0___default.a.createElement(react_plotly_js__WEBPACK_IMPORTED_MODULE_1___default.a, {\n data: [{\n values: Object.values(responseCodes),\n labels: ['Informational 1xx', 'Successful 2xx', 'Redirection 3xx', 'Client Error 4xx', 'Server Error 5xx'],\n type: 'pie',\n domain: {\n y: [0, 2]\n },\n marker: {\n 'colors': ['#f38181', '#fce38a', '#fcbad3', '#95e1d3', '#a8d8ea', '#aa96da']\n }\n }],\n layout: {\n height: 500,\n width: 500,\n displaylogo: false,\n paper_bgcolor: '#fffbe0',\n legend: {\n orientation: 'h',\n xanchor: 'center',\n x: .5,\n y: 5\n }\n }\n });\n };\n\n return /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_0___default.a.createElement(\"div\", null, createChart());\n};\n\n/* harmony default export */ __webpack_exports__[\"default\"] = (ResponseCodeChart);\n\n//# sourceURL=webpack:///./app/charts/response-code-chart.jsx?"); /***/ }), @@ -177,7 +214,7 @@ eval("__webpack_require__.r(__webpack_exports__);\n/* harmony import */ var reac /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; -eval("__webpack_require__.r(__webpack_exports__);\n/* harmony import */ var react__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! react */ \"./node_modules/react/index.js\");\n/* harmony import */ var react__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(react__WEBPACK_IMPORTED_MODULE_0__);\n/* harmony import */ var react_chartjs_2__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! react-chartjs-2 */ \"./node_modules/react-chartjs-2/es/index.js\");\n/* harmony import */ var _context_OverviewContext__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! ../context/OverviewContext */ \"./app/context/OverviewContext.js\");\n/* eslint-disable react/jsx-one-expression-per-line */\n\n\n\n\nvar RouteLocations = function RouteLocations(props) {\n var communicationsData = Object(react__WEBPACK_IMPORTED_MODULE_0__[\"useContext\"])(_context_OverviewContext__WEBPACK_IMPORTED_MODULE_2__[\"default\"]).overviewData; // initialize an empty object resObj. This object will store the microservice names as values and its corresponding correlatingId or correlatingid as keys. The microservice names will be stored in array within the order it was to the database.\n\n var resObj = {};\n\n if (communicationsData.length > 0 && communicationsData[0]._id) {\n // Sort the communication array from latest to earliest document\n communicationsData.sort(function (a, b) {\n if (new Date(a.timeSent) > new Date(b.timeSent)) return 1;\n if (new Date(a.timeSent) < new Date(b.timeSent)) return -1;\n return 0;\n }); // Iterate over sorted communicationsData array from the end to the beginning\n\n for (var i = 0; i < communicationsData.length; i += 1) {\n // declare a constant element and initialize it as the object at index i of the communicationsData array\n var element = communicationsData[i]; // Pushes the microservice name into the object\n\n if (resObj[element.correlatingId]) {\n resObj[element.correlatingId].push(element.currentMicroservice);\n } else resObj[element.correlatingId] = [element.currentMicroservice];\n }\n } else {\n for (var _i = communicationsData.length - 1; _i >= 0; _i--) {\n var _element = communicationsData[_i];\n if (resObj[_element.correlatingid]) resObj[_element.correlatingid].push(_element.currentmicroservice);else resObj[_element.correlatingid] = [_element.currentmicroservice]; // initializing the object with the first microservice\n }\n } // use object values to destructure locations\n\n\n var tracePoints = Object.values(resObj);\n var position = communicationsData[0].correlatingid ? 0 : tracePoints.length - 1;\n var resArray = []; // iterate over Trace Points\n\n for (var _i2 = 0; _i2 < tracePoints[position].length; _i2 += 1) {\n // push into resulting array current tracepoint as div\n resArray.push(react__WEBPACK_IMPORTED_MODULE_0___default.a.createElement(\"div\", {\n className: \"RouteCircle\",\n key: _i2\n }, react__WEBPACK_IMPORTED_MODULE_0___default.a.createElement(\"p\", {\n id: \"routeText\"\n }, \"Point \", _i2 + 1, \": \", tracePoints[position][_i2])));\n }\n\n console.log('resArray: ', resArray); // return div with Trace Points data\n\n return react__WEBPACK_IMPORTED_MODULE_0___default.a.createElement(\"div\", null, resArray);\n};\n\n/* harmony default export */ __webpack_exports__[\"default\"] = (RouteLocations);\n\n//# sourceURL=webpack:///./app/charts/route-trace.jsx?"); +eval("__webpack_require__.r(__webpack_exports__);\n/* harmony import */ var react__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! react */ \"./node_modules/react/index.js\");\n/* harmony import */ var react__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(react__WEBPACK_IMPORTED_MODULE_0__);\n/* harmony import */ var _context_OverviewContext__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ../context/OverviewContext */ \"./app/context/OverviewContext.js\");\n/* harmony import */ var _stylesheets_routes_css__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! ../stylesheets/routes.css */ \"./app/stylesheets/routes.css\");\n/* harmony import */ var _stylesheets_routes_css__WEBPACK_IMPORTED_MODULE_2___default = /*#__PURE__*/__webpack_require__.n(_stylesheets_routes_css__WEBPACK_IMPORTED_MODULE_2__);\n/* eslint-disable react/jsx-one-expression-per-line */\n // import { Bar } from 'react-chartjs-2';\n\n\n\n\nvar RouteLocations = function RouteLocations(props) {\n var communicationsData = Object(react__WEBPACK_IMPORTED_MODULE_0__[\"useContext\"])(_context_OverviewContext__WEBPACK_IMPORTED_MODULE_1__[\"default\"]).overviewData; // console.log('commData (from overviewContxt):', communicationsData);\n // initialize an empty object resObj. \n // This object will store the microservice names as values and its corresponding correlatingId or correlatingid as keys. \n // The microservice names will be stored in array within the order it was to the database.\n\n var resObj = {};\n\n if (communicationsData.length > 0 && communicationsData[0]._id) {\n // Sort the communication array from OLDEST to NEWEST documents.\n communicationsData.sort(function (a, b) {\n // Note that a newer date obj IS GREATER THAN an older date obj.\n if (new Date(a.timeSent) > new Date(b.timeSent)) return 1;\n if (new Date(a.timeSent) < new Date(b.timeSent)) return -1;\n return 0;\n }); // console.log('commData (AFTER sorting):', communicationsData);\n // Iterate over sorted array to build up resObj.\n\n for (var _i = 0; _i < communicationsData.length; _i += 1) {\n // declare a constant element and initialize it as the object at index i of the communicationsData array\n var element = communicationsData[_i]; // Pushes the microservice name & timeSent into the resObj.\n // Data objects w/ same corrId will be grouped in a same array.\n\n if (resObj[element.correlatingId]) {\n resObj[element.correlatingId].push({\n microservice_name: element.currentMicroservice,\n timeSent: element.timeSent\n });\n } else {\n // The value that corresp. to the correlationId key is an array of obj containing name and time data.\n // Each obj is a data point.\n resObj[element.correlatingId] = [{\n microservice_name: element.currentMicroservice,\n timeSent: element.timeSent\n }];\n }\n }\n } else {\n for (var _i2 = communicationsData.length - 1; _i2 >= 0; _i2--) {\n var _element = communicationsData[_i2];\n\n if (resObj[_element.correlatingId]) {\n resObj[_element.correlatingId].push({\n microservice_name: _element.currentMicroservice,\n timeSent: _element.timeSent\n });\n } else {\n // The value that corresp. to the correlationId key is an array of obj containing name and time data.\n // Each obj is a data point.\n resObj[_element.correlatingId] = [{\n microservice_name: _element.currentMicroservice,\n timeSent: _element.timeSent\n }];\n } // initializing the object with the first microservice\n\n }\n } // use Object.values to destructure locations\n // Each elem in tracePoints is an array of arrays, which contain objects (each of which is a data point).\n // Filter the array so that only subarrays w/ len > 1 are kept.\n // (len == 1 means there's only one point in the route. There's no meaningful data to be gained from those.)\n\n\n var tracePoints = Object.values(resObj).filter(function (subArray) {\n return subArray.length > 1;\n }); // console.log('tracePoints arr:', tracePoints);\n // Construct an obj that stores data necessary for calculating avg speed of requests btw 2 pts.\n\n var avgDataObj = {};\n /****** Build the object here w/ nested loops ************/\n\n /****** WARNING: tracePoints arr can be very long (100+) ************/\n\n for (var _i3 = 0; _i3 < tracePoints.length; _i3 += 1) {\n var subArr = tracePoints[_i3];\n\n for (var j = 0; j < subArr.length; j += 1) {\n var currDataObj = subArr[j];\n\n if (j < subArr.length - 1) {\n var nextDataObj = subArr[j + 1];\n var routeName = \"\".concat(currDataObj.microservice_name, \"-\").concat(nextDataObj.microservice_name); // Key/value pair that keeps COUNT of two-point routes\n\n if (!avgDataObj[\"\".concat(routeName, \"Count\")]) avgDataObj[\"\".concat(routeName, \"Count\")] = 1;else avgDataObj[\"\".concat(routeName, \"Count\")] += 1; // Key/value that accumulates TOTAL TIME a req travels btw 2 certain points\n\n var timeDiff = new Date(nextDataObj.timeSent) - new Date(currDataObj.timeSent);\n\n if (!avgDataObj[\"\".concat(routeName, \"TotalTime\")]) {\n avgDataObj[\"\".concat(routeName, \"TotalTime\")] = timeDiff;\n } else avgDataObj[\"\".concat(routeName, \"TotalTime\")] += timeDiff; // Key/value that calculates AVG TIME of travel (dividing the 2 values above)\n\n\n var _avgTime = avgDataObj[\"\".concat(routeName, \"TotalTime\")] / avgDataObj[\"\".concat(routeName, \"Count\")];\n\n avgDataObj[\"\".concat(routeName, \"AvgTime\")] = _avgTime;\n }\n }\n }\n /** End of nested loops */\n\n\n console.log('avgDataObj:', avgDataObj);\n /****************************************/\n // Array of to be rendered. Each
contains route name and time difference.\n\n var resArray = [];\n var position = communicationsData[0].correlatingid ? 0 : tracePoints.length - 1;\n console.log('position for tracePoints:', position); // iterate over ONE elem in tracePoints, creating a
for every data obj.\n\n for (var _i4 = 0; _i4 < tracePoints[position].length; _i4 += 1) {\n if (_i4 !== tracePoints[position].length - 1) {\n // Calc time difference (when not at the end of array):\n // Convert time str to Date obj w/ new Date(), then get the time difference.\n var _timeDiff = new Date(tracePoints[position][_i4 + 1].timeSent) - new Date(tracePoints[position][_i4].timeSent);\n\n resArray.push( /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_0___default.a.createElement(\"div\", {\n className: \"RouteCircle\",\n key: _i4\n }, /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_0___default.a.createElement(\"p\", {\n id: \"routeText\"\n }, \"Point \", _i4 + 1, \": \", tracePoints[position][_i4].microservice_name), /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_0___default.a.createElement(\"p\", {\n id: \"routeTimeDiff\"\n }, \"Time elapsed: \", _timeDiff, \" ms\")));\n } else {\n // If at the end of array, don't push the timeDiff

to resArray (only push a

w/ name).\n resArray.push( /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_0___default.a.createElement(\"div\", {\n className: \"RouteCircle\",\n key: _i4\n }, /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_0___default.a.createElement(\"p\", {\n id: \"routeText\"\n }, \"Point \", _i4 + 1, \": \", tracePoints[position][_i4].microservice_name)));\n }\n } // console.log('resArray: ', resArray);\n\n /**** Making a list of avg speed-related data. ********/\n // const avgData = [];\n // Object.entries(avgDataObj).forEach((el, i) => {\n // avgData.push(\n // \n // {el[0]}: {el[1]}\n // \n // )\n // })\n // console.log('avgData (array):', avgData);\n\n /**** Making CATEGORIZED lists of avg speed-related data. ********/\n\n\n var avgTime = [],\n totalTime = [],\n count = [];\n var i = 0; // For unique keys for each //\n\n for (var key in avgDataObj) {\n i += 1;\n\n if (key.endsWith('AvgTime')) {\n avgTime.push( /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_0___default.a.createElement(\"span\", {\n className: \"avgDataDetails\",\n key: i\n }, key.slice(0, -7), \": \", avgDataObj[key], \" ms\"));\n }\n\n if (key.endsWith('TotalTime')) {\n totalTime.push( /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_0___default.a.createElement(\"span\", {\n className: \"avgDataDetails\",\n key: i\n }, key.slice(0, -9), \": \", avgDataObj[key], \" ms\"));\n }\n\n if (key.endsWith('Count')) {\n count.push( /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_0___default.a.createElement(\"span\", {\n className: \"avgDataDetails\",\n key: i\n }, key.slice(0, -5), \": \", avgDataObj[key]));\n }\n } // console.log('avgTime:', avgTime);\n // console.log('totalTime:', totalTime);\n // console.log('count:', count);\n\n /****************/\n\n\n return /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_0___default.a.createElement(\"div\", {\n id: \"routeDataArea\"\n }, resArray, /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_0___default.a.createElement(\"div\", {\n id: \"avgData\"\n }, /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_0___default.a.createElement(\"span\", {\n className: \"avgData-titles\"\n }, \"Average time between points:\"), avgTime, /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_0___default.a.createElement(\"span\", {\n className: \"avgData-titles\"\n }, \"Total time between points:\"), totalTime, /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_0___default.a.createElement(\"span\", {\n className: \"avgData-titles\"\n }, \"Number of trips between points:\"), count));\n};\n\n/* harmony default export */ __webpack_exports__[\"default\"] = (RouteLocations);\n\n//# sourceURL=webpack:///./app/charts/route-trace.jsx?"); /***/ }), @@ -189,7 +226,7 @@ eval("__webpack_require__.r(__webpack_exports__);\n/* harmony import */ var reac /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; -eval("__webpack_require__.r(__webpack_exports__);\n/* harmony import */ var react__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! react */ \"./node_modules/react/index.js\");\n/* harmony import */ var react__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(react__WEBPACK_IMPORTED_MODULE_0__);\n/* harmony import */ var react_chartjs_2__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! react-chartjs-2 */ \"./node_modules/react-chartjs-2/es/index.js\");\n/* harmony import */ var _context_DetailsContext__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! ../context/DetailsContext */ \"./app/context/DetailsContext.js\");\n\n\n\n\nvar SpeedChart = function SpeedChart(props) {\n var healthData = Object(react__WEBPACK_IMPORTED_MODULE_0__[\"useContext\"])(_context_DetailsContext__WEBPACK_IMPORTED_MODULE_2__[\"default\"]).detailData;\n\n var createChart = function createChart() {\n var xAxis = [];\n var yAxis = [];\n\n for (var i = 0; i < healthData.length; i += 1) {\n var element = healthData[i]; // If using a SQL Database\n\n if (element.currentmicroservice === props.service && element.cpucurrentspeed) {\n xAxis.push(i);\n yAxis.push(element.cpucurrentspeed);\n } // If using a Mongo Database\n\n\n if (element.currentMicroservice === props.service && element.cpuCurrentSpeed) {\n xAxis.push(i);\n yAxis.push(element.cpuCurrentSpeed);\n }\n }\n\n var chartData = {\n datasets: [{\n label: \"CPU Speed of \".concat(props.service),\n data: yAxis,\n backgroundColor: ['rgb(2, 210, 249)']\n }],\n options: {},\n xAxisID: 'Speed',\n yAxisID: 'Communicaton',\n labels: xAxis\n };\n return react__WEBPACK_IMPORTED_MODULE_0___default.a.createElement(react_chartjs_2__WEBPACK_IMPORTED_MODULE_1__[\"Line\"], {\n data: chartData\n });\n };\n\n return react__WEBPACK_IMPORTED_MODULE_0___default.a.createElement(\"div\", null, createChart());\n};\n\n/* harmony default export */ __webpack_exports__[\"default\"] = (SpeedChart);\n\n//# sourceURL=webpack:///./app/charts/speed-chart.jsx?"); +eval("__webpack_require__.r(__webpack_exports__);\n/* harmony import */ var react__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! react */ \"./node_modules/react/index.js\");\n/* harmony import */ var react__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(react__WEBPACK_IMPORTED_MODULE_0__);\n/* harmony import */ var react_plotly_js__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! react-plotly.js */ \"./node_modules/react-plotly.js/react-plotly.js\");\n/* harmony import */ var react_plotly_js__WEBPACK_IMPORTED_MODULE_1___default = /*#__PURE__*/__webpack_require__.n(react_plotly_js__WEBPACK_IMPORTED_MODULE_1__);\n/* harmony import */ var _context_DetailsContext__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! ../context/DetailsContext */ \"./app/context/DetailsContext.js\");\n\n\n\n\nvar SpeedChart = function SpeedChart(props) {\n var healthData = Object(react__WEBPACK_IMPORTED_MODULE_0__[\"useContext\"])(_context_DetailsContext__WEBPACK_IMPORTED_MODULE_2__[\"default\"]).detailData;\n\n var createChart = function createChart() {\n var xAxis = [];\n var yAxis = [];\n\n for (var i = 0; i < healthData.length; i += 1) {\n var element = healthData[i]; // If using a SQL Database\n\n if (element.currentmicroservice === props.service && element.cpucurrentspeed) {\n xAxis.push(i);\n yAxis.push(element.cpucurrentspeed);\n } // If using a Mongo Database\n\n\n if (element.currentMicroservice === props.service && element.cpuCurrentSpeed) {\n xAxis.push(i);\n yAxis.push(element.cpuCurrentSpeed);\n }\n }\n\n return /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_0___default.a.createElement(react_plotly_js__WEBPACK_IMPORTED_MODULE_1___default.a, {\n data: [{\n domain: {\n x: [0, 1],\n y: [0, 1]\n },\n type: 'indicator',\n value: yAxis[yAxis.length - 1],\n title: {\n 'text': \"Speed Chart\"\n },\n delta: {\n 'reference': 3.5,\n 'increasing': {\n 'color': \"mistyrose\"\n }\n },\n mode: \"gauge+number+delta\",\n gauge: {\n axis: {\n range: [null, 8]\n },\n 'tickwidth': 1,\n 'tickcolor': \"#fce38a\",\n 'bar': {\n 'color': \"#6eb6ff\"\n },\n 'bordercolor': \"#a3de83\",\n 'steps': [{\n 'range': [0, 4],\n 'color': '#edf798'\n }, {\n 'range': [4, 6],\n 'color': '#fab57a'\n }],\n 'threshold': {\n 'line': {\n 'color': \"red\",\n 'width': 3.5\n },\n 'thickness': 0.75,\n 'value': 7.5\n }\n }\n }],\n layout: {\n height: 500,\n width: 500,\n paper_bgcolor: '#fffbe0',\n legend: {\n orientation: 'h',\n xanchor: 'center',\n x: .5\n }\n }\n });\n };\n\n return /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_0___default.a.createElement(\"div\", null, createChart());\n};\n\n/* harmony default export */ __webpack_exports__[\"default\"] = (SpeedChart);\n\n//# sourceURL=webpack:///./app/charts/speed-chart.jsx?"); /***/ }), @@ -201,7 +238,7 @@ eval("__webpack_require__.r(__webpack_exports__);\n/* harmony import */ var reac /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; -eval("__webpack_require__.r(__webpack_exports__);\n/* harmony import */ var react__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! react */ \"./node_modules/react/index.js\");\n/* harmony import */ var react__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(react__WEBPACK_IMPORTED_MODULE_0__);\n/* harmony import */ var react_chartjs_2__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! react-chartjs-2 */ \"./node_modules/react-chartjs-2/es/index.js\");\n/* harmony import */ var _context_DetailsContext__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! ../context/DetailsContext */ \"./app/context/DetailsContext.js\");\n\n\n\n\nvar TemperatureChart = function TemperatureChart(props) {\n var healthData = Object(react__WEBPACK_IMPORTED_MODULE_0__[\"useContext\"])(_context_DetailsContext__WEBPACK_IMPORTED_MODULE_2__[\"default\"]).detailData;\n\n var createChart = function createChart() {\n var yAxis = [];\n var xAxis = [];\n\n for (var i = 0; i < healthData.length; i += 1) {\n var element = healthData[i]; // If Mongo\n\n if (element.currentMicroservice === props.service && element.cpuTemperature) {\n yAxis.push(i);\n xAxis.push(element.cpuTemperature);\n } // If SQL\n\n\n if (element.currentmicroservice === props.service && element.cputemperature) {\n yAxis.push(i);\n xAxis.push(element.cputemperature);\n }\n }\n\n var chartData = {\n datasets: [{\n label: 'Temperature Data',\n data: xAxis,\n backgroundColor: ['rgb(2, 210, 249)']\n }],\n labels: yAxis\n };\n return react__WEBPACK_IMPORTED_MODULE_0___default.a.createElement(react_chartjs_2__WEBPACK_IMPORTED_MODULE_1__[\"Line\"], {\n data: chartData\n });\n };\n\n return react__WEBPACK_IMPORTED_MODULE_0___default.a.createElement(\"div\", null, createChart());\n};\n\n/* harmony default export */ __webpack_exports__[\"default\"] = (TemperatureChart);\n\n//# sourceURL=webpack:///./app/charts/temperature-chart.jsx?"); +eval("__webpack_require__.r(__webpack_exports__);\n/* harmony import */ var react__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! react */ \"./node_modules/react/index.js\");\n/* harmony import */ var react__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(react__WEBPACK_IMPORTED_MODULE_0__);\n/* harmony import */ var react_plotly_js__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! react-plotly.js */ \"./node_modules/react-plotly.js/react-plotly.js\");\n/* harmony import */ var react_plotly_js__WEBPACK_IMPORTED_MODULE_1___default = /*#__PURE__*/__webpack_require__.n(react_plotly_js__WEBPACK_IMPORTED_MODULE_1__);\n/* harmony import */ var _context_DetailsContext__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! ../context/DetailsContext */ \"./app/context/DetailsContext.js\");\n\n\n\n\nvar TemperatureChart = function TemperatureChart(props) {\n var healthData = Object(react__WEBPACK_IMPORTED_MODULE_0__[\"useContext\"])(_context_DetailsContext__WEBPACK_IMPORTED_MODULE_2__[\"default\"]).detailData;\n\n var createChart = function createChart() {\n var yAxis = [];\n var xAxis = [];\n\n for (var i = 0; i < healthData.length; i += 1) {\n var element = healthData[i]; // If Mongo\n\n if (element.currentMicroservice === props.service && element.cpuTemperature) {\n yAxis.push(i);\n xAxis.push(element.cpuTemperature);\n } // If SQL\n\n\n if (element.currentmicroservice === props.service && element.cputemperature) {\n yAxis.push(i);\n xAxis.push(element.cputemperature);\n }\n }\n\n return /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_0___default.a.createElement(react_plotly_js__WEBPACK_IMPORTED_MODULE_1___default.a, {\n data: [{\n type: 'scatter',\n fill: 'tozeroy',\n fillcolor: 'rgba(224, 62, 54, .6)',\n mode: 'none',\n x: yAxis,\n y: xAxis,\n name: 'CPU Temperature',\n showlegend: true\n }],\n layout: {\n width: 500,\n height: 500,\n paper_bgcolor: '#fffbe0',\n plot_bgcolor: '#fffbe0',\n legend: {\n orientation: 'h',\n xanchor: 'center',\n x: .5\n },\n yaxis: {\n rangemode: 'nonnegative'\n }\n }\n });\n };\n\n return /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_0___default.a.createElement(\"div\", null, createChart());\n};\n\n/* harmony default export */ __webpack_exports__[\"default\"] = (TemperatureChart);\n\n//# sourceURL=webpack:///./app/charts/temperature-chart.jsx?"); /***/ }), @@ -213,7 +250,7 @@ eval("__webpack_require__.r(__webpack_exports__);\n/* harmony import */ var reac /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; -eval("__webpack_require__.r(__webpack_exports__);\n/* harmony import */ var react__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! react */ \"./node_modules/react/index.js\");\n/* harmony import */ var react__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(react__WEBPACK_IMPORTED_MODULE_0__);\n/* harmony import */ var _context_SetupContext__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ../context/SetupContext */ \"./app/context/SetupContext.js\");\n/* harmony import */ var _ServicesDashboard_jsx__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! ./ServicesDashboard.jsx */ \"./app/components/ServicesDashboard.jsx\");\nfunction _slicedToArray(arr, i) { return _arrayWithHoles(arr) || _iterableToArrayLimit(arr, i) || _nonIterableRest(); }\n\nfunction _nonIterableRest() { throw new TypeError(\"Invalid attempt to destructure non-iterable instance\"); }\n\nfunction _iterableToArrayLimit(arr, i) { if (!(Symbol.iterator in Object(arr) || Object.prototype.toString.call(arr) === \"[object Arguments]\")) { return; } var _arr = []; var _n = true; var _d = false; var _e = undefined; try { for (var _i = arr[Symbol.iterator](), _s; !(_n = (_s = _i.next()).done); _n = true) { _arr.push(_s.value); if (i && _arr.length === i) break; } } catch (err) { _d = true; _e = err; } finally { try { if (!_n && _i[\"return\"] != null) _i[\"return\"](); } finally { if (_d) throw _e; } } return _arr; }\n\nfunction _arrayWithHoles(arr) { if (Array.isArray(arr)) return arr; }\n\n // import logo from 'app/assets/logo2.png';\n\n\n\n\nvar _window$require = window.require('electron'),\n ipcRenderer = _window$require.ipcRenderer;\n\nvar AddService = function AddService() {\n // Context used to ensure that that this page is only seen when the setup is required. Updated when user adds a database.\n var ChronosSetup = Object(react__WEBPACK_IMPORTED_MODULE_0__[\"useContext\"])(_context_SetupContext__WEBPACK_IMPORTED_MODULE_1__[\"default\"]); // Local state created for form entries ONLY.\n\n var _useState = Object(react__WEBPACK_IMPORTED_MODULE_0__[\"useState\"])(''),\n _useState2 = _slicedToArray(_useState, 2),\n dbState = _useState2[0],\n setDbType = _useState2[1];\n\n var _useState3 = Object(react__WEBPACK_IMPORTED_MODULE_0__[\"useState\"])(''),\n _useState4 = _slicedToArray(_useState3, 2),\n uriState = _useState4[0],\n setUri = _useState4[1];\n\n var _useState5 = Object(react__WEBPACK_IMPORTED_MODULE_0__[\"useState\"])(''),\n _useState6 = _slicedToArray(_useState5, 2),\n labelState = _useState6[0],\n setLabel = _useState6[1]; // Submits data provided by the user to added to the setting file.\n\n\n var onSubmit = function onSubmit() {\n var userSettings = [labelState, dbState, uriState]; // IPC communication used to update settings JSON with user input.\n\n ipcRenderer.send('submit', JSON.stringify(userSettings));\n ChronosSetup.setupRequired = ChronosSetup.toggleSetup(true); // Refresh window after submit.\n\n document.location.reload();\n }; // it is setting the dbState\n\n\n Object(react__WEBPACK_IMPORTED_MODULE_0__[\"useEffect\"])(function () {\n setDbType(document.getElementById('dbType').value);\n }, [dbState, setDbType]);\n var tooltipWriteup = \"Chronos utilizes user-owned databases to store communications and system health data.\\n Please enter a valid connection string to a SQL or noSQL database to begin monitoring.\";\n return react__WEBPACK_IMPORTED_MODULE_0___default.a.createElement(\"div\", {\n className: \"mainContainer\"\n }, react__WEBPACK_IMPORTED_MODULE_0___default.a.createElement(\"img\", {\n src: \"app/assets/logo2.png\",\n alt: \"logo\",\n id: \"addServiceLogo\"\n }), react__WEBPACK_IMPORTED_MODULE_0___default.a.createElement(\"h2\", {\n className: \"signUpHeader\"\n }, \"Enter Your Database Information\", react__WEBPACK_IMPORTED_MODULE_0___default.a.createElement(\"sup\", {\n className: \"tooltip\"\n }, \"\\u24D8\", react__WEBPACK_IMPORTED_MODULE_0___default.a.createElement(\"div\", {\n className: \"tooltiptext\"\n }, tooltipWriteup))), react__WEBPACK_IMPORTED_MODULE_0___default.a.createElement(\"form\", null, \"Database Type:\", react__WEBPACK_IMPORTED_MODULE_0___default.a.createElement(\"select\", {\n id: \"dbType\",\n onChange: function onChange() {\n return setDbType(document.getElementById('dbType').value);\n }\n }, react__WEBPACK_IMPORTED_MODULE_0___default.a.createElement(\"option\", {\n value: \"SQL\"\n }, \"SQL\"), react__WEBPACK_IMPORTED_MODULE_0___default.a.createElement(\"option\", {\n value: \"MongoDB\"\n }, \"MongoDB\")), \"Database URI:\", react__WEBPACK_IMPORTED_MODULE_0___default.a.createElement(\"input\", {\n className: \"userInput\",\n id: \"dburi\",\n onChange: function onChange(e) {\n return setUri(e.target.value);\n },\n placeholder: \"Database URI\"\n }), \"Database Name:\", react__WEBPACK_IMPORTED_MODULE_0___default.a.createElement(\"input\", {\n className: \"userInput\",\n id: \"dbname\",\n onChange: function onChange(e) {\n return setLabel(e.target.value);\n },\n type: \"text\",\n placeholder: \"Database Name\"\n }), react__WEBPACK_IMPORTED_MODULE_0___default.a.createElement(\"button\", {\n className: \"submitBtn\",\n type: \"submit\" // Error Handling.\n ,\n onClick: function onClick() {\n if (document.getElementById('dburi').value === '' || document.getElementById('dbname').value === '') {\n alert('Required field missing. Please verify you provided both required items and resubmit form');\n } else {\n onSubmit();\n }\n }\n }, \"Submit\")));\n};\n\n/* harmony default export */ __webpack_exports__[\"default\"] = (AddService);\n\n//# sourceURL=webpack:///./app/components/AddService.jsx?"); +eval("__webpack_require__.r(__webpack_exports__);\n/* harmony import */ var react__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! react */ \"./node_modules/react/index.js\");\n/* harmony import */ var react__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(react__WEBPACK_IMPORTED_MODULE_0__);\n/* harmony import */ var _context_SetupContext__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ../context/SetupContext */ \"./app/context/SetupContext.js\");\n/* harmony import */ var _stylesheets_AddService_css__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! ../stylesheets/AddService.css */ \"./app/stylesheets/AddService.css\");\n/* harmony import */ var _stylesheets_AddService_css__WEBPACK_IMPORTED_MODULE_2___default = /*#__PURE__*/__webpack_require__.n(_stylesheets_AddService_css__WEBPACK_IMPORTED_MODULE_2__);\nfunction _slicedToArray(arr, i) { return _arrayWithHoles(arr) || _iterableToArrayLimit(arr, i) || _unsupportedIterableToArray(arr, i) || _nonIterableRest(); }\n\nfunction _nonIterableRest() { throw new TypeError(\"Invalid attempt to destructure non-iterable instance.\\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.\"); }\n\nfunction _unsupportedIterableToArray(o, minLen) { if (!o) return; if (typeof o === \"string\") return _arrayLikeToArray(o, minLen); var n = Object.prototype.toString.call(o).slice(8, -1); if (n === \"Object\" && o.constructor) n = o.constructor.name; if (n === \"Map\" || n === \"Set\") return Array.from(o); if (n === \"Arguments\" || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(n)) return _arrayLikeToArray(o, minLen); }\n\nfunction _arrayLikeToArray(arr, len) { if (len == null || len > arr.length) len = arr.length; for (var i = 0, arr2 = new Array(len); i < len; i++) { arr2[i] = arr[i]; } return arr2; }\n\nfunction _iterableToArrayLimit(arr, i) { if (typeof Symbol === \"undefined\" || !(Symbol.iterator in Object(arr))) return; var _arr = []; var _n = true; var _d = false; var _e = undefined; try { for (var _i = arr[Symbol.iterator](), _s; !(_n = (_s = _i.next()).done); _n = true) { _arr.push(_s.value); if (i && _arr.length === i) break; } } catch (err) { _d = true; _e = err; } finally { try { if (!_n && _i[\"return\"] != null) _i[\"return\"](); } finally { if (_d) throw _e; } } return _arr; }\n\nfunction _arrayWithHoles(arr) { if (Array.isArray(arr)) return arr; }\n\n // import logo from 'app/assets/logo2.png';\n\n\n\n\nvar _window$require = window.require('electron'),\n ipcRenderer = _window$require.ipcRenderer;\n\nvar AddService = function AddService() {\n // Context used to ensure that that this page is only seen when the setup is required. Updated when user adds a database.\n var ChronosSetup = Object(react__WEBPACK_IMPORTED_MODULE_0__[\"useContext\"])(_context_SetupContext__WEBPACK_IMPORTED_MODULE_1__[\"default\"]); // Local state created for form entries ONLY.\n\n var _useState = Object(react__WEBPACK_IMPORTED_MODULE_0__[\"useState\"])(''),\n _useState2 = _slicedToArray(_useState, 2),\n dbState = _useState2[0],\n setDbType = _useState2[1];\n\n var _useState3 = Object(react__WEBPACK_IMPORTED_MODULE_0__[\"useState\"])(''),\n _useState4 = _slicedToArray(_useState3, 2),\n uriState = _useState4[0],\n setUri = _useState4[1];\n\n var _useState5 = Object(react__WEBPACK_IMPORTED_MODULE_0__[\"useState\"])(''),\n _useState6 = _slicedToArray(_useState5, 2),\n labelState = _useState6[0],\n setLabel = _useState6[1]; // Submits data provided by the user to added to the setting file.\n\n\n var onSubmit = function onSubmit() {\n var userSettings = [labelState, dbState, uriState]; // IPC communication used to update settings JSON with user input.\n\n ipcRenderer.send('submit', JSON.stringify(userSettings));\n ChronosSetup.setupRequired = ChronosSetup.toggleSetup(true); // Refresh window after submit.\n\n document.location.reload();\n }; // it is setting the dbState\n\n\n Object(react__WEBPACK_IMPORTED_MODULE_0__[\"useEffect\"])(function () {\n setDbType(document.getElementById('dbType').value);\n }, [dbState, setDbType]);\n var tooltipWriteup = \"Chronos utilizes user-owned databases to store communications and system health data.\\n Please enter a valid connection string to a SQL or noSQL database to begin monitoring.\";\n return /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_0___default.a.createElement(\"div\", {\n className: \"mainContainer\"\n }, /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_0___default.a.createElement(\"img\", {\n src: \"app/assets/logo2.png\",\n alt: \"logo\",\n id: \"addServiceLogo\"\n }), /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_0___default.a.createElement(\"h2\", {\n className: \"signUpHeader\"\n }, \"Enter Your Database Information\"), /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_0___default.a.createElement(\"form\", null, \"Database Type:\", /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_0___default.a.createElement(\"select\", {\n id: \"dbType\",\n onChange: function onChange() {\n return setDbType(document.getElementById('dbType').value);\n }\n }, /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_0___default.a.createElement(\"option\", {\n value: \"SQL\"\n }, \"SQL\"), /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_0___default.a.createElement(\"option\", {\n value: \"MongoDB\"\n }, \"MongoDB\")), \"Database URI:\", /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_0___default.a.createElement(\"input\", {\n className: \"userInput\",\n id: \"dburi\",\n onChange: function onChange(e) {\n return setUri(e.target.value);\n },\n placeholder: \"Database URI\"\n }), \"Database Name:\", /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_0___default.a.createElement(\"input\", {\n className: \"userInput\",\n id: \"dbname\",\n onChange: function onChange(e) {\n return setLabel(e.target.value);\n },\n type: \"text\",\n placeholder: \"Database Name\"\n }), /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_0___default.a.createElement(\"button\", {\n className: \"submitBtn\",\n type: \"submit\" // Error Handling.\n ,\n onClick: function onClick() {\n if (document.getElementById('dburi').value === '' || document.getElementById('dbname').value === '') {\n alert('Required field missing. Please verify you provided both required items and resubmit form');\n } else {\n onSubmit();\n }\n }\n }, \"Submit\")));\n};\n\n/* harmony default export */ __webpack_exports__[\"default\"] = (AddService);\n\n//# sourceURL=webpack:///./app/components/AddService.jsx?"); /***/ }), @@ -225,67 +262,83 @@ eval("__webpack_require__.r(__webpack_exports__);\n/* harmony import */ var reac /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; -eval("__webpack_require__.r(__webpack_exports__);\n/* harmony import */ var react__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! react */ \"./node_modules/react/index.js\");\n/* harmony import */ var react__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(react__WEBPACK_IMPORTED_MODULE_0__);\n/* harmony import */ var _context_DashboardContext__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ../context/DashboardContext */ \"./app/context/DashboardContext.js\");\n/* harmony import */ var _context_SetupContext__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! ../context/SetupContext */ \"./app/context/SetupContext.js\");\n\n\n // Declare a constant ipcRenderer by deconstructing window.require('electron') so that the onDelete function can initialize an IPC Communication \n\nvar _window$require = window.require('electron'),\n ipcRenderer = _window$require.ipcRenderer; // Deletes a Service\n\n\nvar DeleteService = function DeleteService(props) {\n // Declares a constant setup and initialize it to the SetupContext. \n // SetupContext indicates whether or not an initial setup is necessary.\n // An initial setup is necessary when the user has not saved any database to chronos frontend application. \n var setup = Object(react__WEBPACK_IMPORTED_MODULE_0__[\"useContext\"])(_context_SetupContext__WEBPACK_IMPORTED_MODULE_2__[\"default\"]); // Declares a variable serviceList and initialize it to the DashboardContext. \n // DashboardContext lists the databases saved by users. \n\n var serviceList = Object(react__WEBPACK_IMPORTED_MODULE_0__[\"useContext\"])(_context_DashboardContext__WEBPACK_IMPORTED_MODULE_1__[\"default\"]);\n /* \n Sends a deleteService request with an index to an ipcMain.on function within the Main.js \n On return, the function reassigns the serviceList variable to the updated services provided within the deleteResponse. If the serviceList equals [\"hard\",\"coded\",\"in\"], then the user has no database saved to the chronos frontend application. If this is true, the function reassigns setup.setupRequired to true by invoking setup.toggleSetup with the argument 'false'. Then the function reloads the application to show the changes made to the services.\n */\n\n var onDelete = function onDelete(index) {\n ipcRenderer.send('deleteService', index);\n ipcRenderer.on('deleteResponse', function (event, services) {\n serviceList = services;\n\n if (serviceList === [\"hard\", \"coded\", \"in\"]) {\n setup.setupRequired = setup.toggleSetup(false);\n }\n\n document.location.reload();\n });\n }; // Declares a constant databaseButtons and initialize it an empty array\n\n\n var databaseButtons = [];\n /* Iterates over the serviceList to create a button for each service. Each button is pushed into the databaseButtons array as the button is created. Each button has an onclick function that invokes the window confirm function with a warning message (ex:'Are you sure you want to delete this service?') and stores the result of invoking confirm into a constant moveForward. If the moveForward is true, then onDelete function is invoked with the index of where the service is stored within the serviceList*/\n\n var _loop = function _loop(i) {\n databaseButtons.push(react__WEBPACK_IMPORTED_MODULE_0___default.a.createElement(\"button\", {\n className: \"microserviceBtn deleteMicroservice\",\n key: \"delete\" + i,\n onClick: function onClick() {\n var moveForward = confirm(\"Are you sure you want to delete \".concat(serviceList[i], \"? \\n If \\\"YES\\\" press the \\\"OK\\\" button, else press the \\\"Cancel\\\" button\"));\n\n if (moveForward) {\n onDelete(i);\n }\n }\n }, serviceList[i]));\n };\n\n for (var i = 0; i < serviceList.length; i++) {\n _loop(i);\n } // returns the title of the page with all of the services that can be deleted as buttons \n\n\n return react__WEBPACK_IMPORTED_MODULE_0___default.a.createElement(\"div\", {\n className: \"mainContainer\"\n }, react__WEBPACK_IMPORTED_MODULE_0___default.a.createElement(\"h1\", {\n className: \"overviewTitle\"\n }, \"Press on the Database You Want to Delete\"), react__WEBPACK_IMPORTED_MODULE_0___default.a.createElement(\"div\", {\n className: \"servicesList\"\n }, databaseButtons));\n}; // export the DeleteService function so that it can imported anywhere\n\n\n/* harmony default export */ __webpack_exports__[\"default\"] = (DeleteService);\n\n//# sourceURL=webpack:///./app/components/DeleteService.jsx?"); +eval("__webpack_require__.r(__webpack_exports__);\n/* harmony import */ var react__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! react */ \"./node_modules/react/index.js\");\n/* harmony import */ var react__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(react__WEBPACK_IMPORTED_MODULE_0__);\n/* harmony import */ var _context_DashboardContext__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ../context/DashboardContext */ \"./app/context/DashboardContext.js\");\n/* harmony import */ var _context_SetupContext__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! ../context/SetupContext */ \"./app/context/SetupContext.js\");\n/* harmony import */ var _stylesheets_DeleteServices_css__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(/*! ../stylesheets/DeleteServices.css */ \"./app/stylesheets/DeleteServices.css\");\n/* harmony import */ var _stylesheets_DeleteServices_css__WEBPACK_IMPORTED_MODULE_3___default = /*#__PURE__*/__webpack_require__.n(_stylesheets_DeleteServices_css__WEBPACK_IMPORTED_MODULE_3__);\n\n\n\n // Declare a constant ipcRenderer by deconstructing window.require('electron') so that the onDelete function can initialize an IPC Communication \n\nvar _window$require = window.require('electron'),\n ipcRenderer = _window$require.ipcRenderer; // Deletes a Service\n\n\nvar DeleteService = function DeleteService(props) {\n // Declares a constant setup and initialize it to the SetupContext. \n // SetupContext indicates whether or not an initial setup is necessary.\n // An initial setup is necessary when the user has not saved any database to chronos frontend application. \n var setup = Object(react__WEBPACK_IMPORTED_MODULE_0__[\"useContext\"])(_context_SetupContext__WEBPACK_IMPORTED_MODULE_2__[\"default\"]); // Declares a variable serviceList and initialize it to the DashboardContext. \n // DashboardContext lists the databases saved by users. \n\n var serviceList = Object(react__WEBPACK_IMPORTED_MODULE_0__[\"useContext\"])(_context_DashboardContext__WEBPACK_IMPORTED_MODULE_1__[\"default\"]);\n /* \n Sends a deleteService request with an index to an ipcMain.on function within the Main.js \n On return, the function reassigns the serviceList variable to the updated services provided within the deleteResponse. If the serviceList equals [\"hard\",\"coded\",\"in\"], then the user has no database saved to the chronos frontend application. If this is true, the function reassigns setup.setupRequired to true by invoking setup.toggleSetup with the argument 'false'. Then the function reloads the application to show the changes made to the services.\n */\n\n var onDelete = function onDelete(index) {\n ipcRenderer.send('deleteService', index);\n ipcRenderer.on('deleteResponse', function (event, services) {\n serviceList = services;\n\n if (serviceList === [\"hard\", \"coded\", \"in\"]) {\n setup.setupRequired = setup.toggleSetup(false);\n }\n\n document.location.reload();\n });\n }; // Declares a constant databaseButtons and initialize it an empty array\n\n\n var databaseButtons = [];\n /* Iterates over the serviceList to create a button for each service. Each button is pushed into the databaseButtons array as the button is created. Each button has an onclick function that invokes the window confirm function with a warning message (ex:'Are you sure you want to delete this service?') and stores the result of invoking confirm into a constant moveForward. If the moveForward is true, then onDelete function is invoked with the index of where the service is stored within the serviceList*/\n\n var _loop = function _loop(i) {\n databaseButtons.push( /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_0___default.a.createElement(\"button\", {\n className: \"microserviceBtn deleteMicroservice\",\n key: \"delete\" + i,\n onClick: function onClick() {\n var moveForward = confirm(\"Are you sure you want to delete \".concat(serviceList[i], \"? \\n If \\\"YES\\\" press the \\\"OK\\\" button, else press the \\\"Cancel\\\" button\"));\n\n if (moveForward) {\n onDelete(i);\n }\n }\n }, serviceList[i]));\n };\n\n for (var i = 0; i < serviceList.length; i++) {\n _loop(i);\n } // returns the title of the page with all of the services that can be deleted as buttons \n\n\n return /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_0___default.a.createElement(\"div\", {\n className: \"mainContainer\"\n }, /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_0___default.a.createElement(\"h1\", {\n className: \"overviewTitle\"\n }, \"Press on the Database You Want to Delete\"), /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_0___default.a.createElement(\"div\", {\n className: \"servicesList\"\n }, databaseButtons));\n}; // export the DeleteService function so that it can imported anywhere\n\n\n/* harmony default export */ __webpack_exports__[\"default\"] = (DeleteService);\n\n//# sourceURL=webpack:///./app/components/DeleteService.jsx?"); /***/ }), -/***/ "./app/components/Modal.jsx": -/*!**********************************!*\ - !*** ./app/components/Modal.jsx ***! - \**********************************/ +/***/ "./app/components/ServiceDetails.jsx": +/*!*******************************************!*\ + !*** ./app/components/ServiceDetails.jsx ***! + \*******************************************/ /*! exports provided: default */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; -eval("__webpack_require__.r(__webpack_exports__);\n/* harmony import */ var react__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! react */ \"./node_modules/react/index.js\");\n/* harmony import */ var react__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(react__WEBPACK_IMPORTED_MODULE_0__);\n/* harmony import */ var _charts_request_type_chart_jsx__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ../charts/request-type-chart.jsx */ \"./app/charts/request-type-chart.jsx\");\n/* harmony import */ var _charts_response_code_chart_jsx__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! ../charts/response-code-chart.jsx */ \"./app/charts/response-code-chart.jsx\");\n/* harmony import */ var _charts_speed_chart_jsx__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(/*! ../charts/speed-chart.jsx */ \"./app/charts/speed-chart.jsx\");\n/* harmony import */ var _charts_processes_chart_jsx__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(/*! ../charts/processes-chart.jsx */ \"./app/charts/processes-chart.jsx\");\n/* harmony import */ var _charts_temperature_chart_jsx__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(/*! ../charts/temperature-chart.jsx */ \"./app/charts/temperature-chart.jsx\");\n/* harmony import */ var _charts_latency_chart_jsx__WEBPACK_IMPORTED_MODULE_6__ = __webpack_require__(/*! ../charts/latency-chart.jsx */ \"./app/charts/latency-chart.jsx\");\n/* harmony import */ var _charts_memory_chart_jsx__WEBPACK_IMPORTED_MODULE_7__ = __webpack_require__(/*! ../charts/memory-chart.jsx */ \"./app/charts/memory-chart.jsx\");\n/* harmony import */ var _charts_route_trace_jsx__WEBPACK_IMPORTED_MODULE_8__ = __webpack_require__(/*! ../charts/route-trace.jsx */ \"./app/charts/route-trace.jsx\");\n/* harmony import */ var _charts_microservice_traffic_jsx__WEBPACK_IMPORTED_MODULE_9__ = __webpack_require__(/*! ../charts/microservice-traffic.jsx */ \"./app/charts/microservice-traffic.jsx\");\n\n\n\n\n\n\n\n\n\n\n\nvar Modal = function Modal(props) {\n // Destructuring props to make linter happy\n var modalChart = props.modalChart,\n service = props.service,\n toggleModalDisplay = props.toggleModalDisplay,\n chartTitle = props.chartTitle; // Dictionary used to render proper data chart within Modal component upon rendering\n\n var dict = {\n Request: react__WEBPACK_IMPORTED_MODULE_0___default.a.createElement(_charts_request_type_chart_jsx__WEBPACK_IMPORTED_MODULE_1__[\"default\"], {\n service: service\n }),\n routesImage: react__WEBPACK_IMPORTED_MODULE_0___default.a.createElement(_charts_route_trace_jsx__WEBPACK_IMPORTED_MODULE_8__[\"default\"], {\n service: service\n }),\n Response: react__WEBPACK_IMPORTED_MODULE_0___default.a.createElement(_charts_response_code_chart_jsx__WEBPACK_IMPORTED_MODULE_2__[\"default\"], {\n service: service\n }),\n Speed: react__WEBPACK_IMPORTED_MODULE_0___default.a.createElement(_charts_speed_chart_jsx__WEBPACK_IMPORTED_MODULE_3__[\"default\"], {\n service: service\n }),\n Processes: react__WEBPACK_IMPORTED_MODULE_0___default.a.createElement(_charts_processes_chart_jsx__WEBPACK_IMPORTED_MODULE_4__[\"default\"], {\n service: service\n }),\n Latency: react__WEBPACK_IMPORTED_MODULE_0___default.a.createElement(_charts_latency_chart_jsx__WEBPACK_IMPORTED_MODULE_6__[\"default\"], {\n service: service\n }),\n Traffic: react__WEBPACK_IMPORTED_MODULE_0___default.a.createElement(_charts_microservice_traffic_jsx__WEBPACK_IMPORTED_MODULE_9__[\"default\"], {\n service: service\n }),\n Temperature: react__WEBPACK_IMPORTED_MODULE_0___default.a.createElement(_charts_temperature_chart_jsx__WEBPACK_IMPORTED_MODULE_5__[\"default\"], {\n service: service\n }),\n Memory: react__WEBPACK_IMPORTED_MODULE_0___default.a.createElement(_charts_memory_chart_jsx__WEBPACK_IMPORTED_MODULE_7__[\"default\"], {\n service: service\n })\n }; // event.stopPropogation allows the user to interact with the chart as opposed to a click on the\n // chart bubbling out and closing the Modal.\n\n return react__WEBPACK_IMPORTED_MODULE_0___default.a.createElement(\"div\", {\n id: \"modalWindow\",\n onClick: function onClick() {\n return toggleModalDisplay(!toggleModalDisplay);\n }\n }, react__WEBPACK_IMPORTED_MODULE_0___default.a.createElement(\"div\", {\n id: \"modalContent\",\n onClick: function onClick(event) {\n return event.stopPropagation();\n }\n }, react__WEBPACK_IMPORTED_MODULE_0___default.a.createElement(\"h3\", {\n id: \"chartTitle\"\n }, \"\".concat(service, \" - \").concat(chartTitle)), react__WEBPACK_IMPORTED_MODULE_0___default.a.createElement(\"button\", {\n id: \"modalCloseButton\",\n onClick: function onClick() {\n return toggleModalDisplay(!toggleModalDisplay);\n }\n }, \"\\xD7\"), dict[modalChart]));\n};\n\n/* harmony default export */ __webpack_exports__[\"default\"] = (Modal);\n\n//# sourceURL=webpack:///./app/components/Modal.jsx?"); +eval("__webpack_require__.r(__webpack_exports__);\n/* harmony import */ var react__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! react */ \"./node_modules/react/index.js\");\n/* harmony import */ var react__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(react__WEBPACK_IMPORTED_MODULE_0__);\n/* harmony import */ var _containers_GraphsContainer_jsx__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ../containers/GraphsContainer.jsx */ \"./app/containers/GraphsContainer.jsx\");\n/* eslint-disable react/jsx-one-expression-per-line */\n // import Modal from './Modal.jsx';\n\n // Renders charts created with health and communication data for a selected database.\n\nvar ServiceDetails = function ServiceDetails(props) {\n // Renders health info detail buttons\n var service = props.service;\n return /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_0___default.a.createElement(\"div\", {\n id: \"serviceDetailsContainer\"\n }, /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_0___default.a.createElement(\"h3\", {\n id: \"microserviceHealthTitle\"\n }, \"Microservice Health - \", service), /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_0___default.a.createElement(_containers_GraphsContainer_jsx__WEBPACK_IMPORTED_MODULE_1__[\"default\"], {\n service: service\n }));\n};\n\n/* harmony default export */ __webpack_exports__[\"default\"] = (ServiceDetails);\n\n//# sourceURL=webpack:///./app/components/ServiceDetails.jsx?"); /***/ }), -/***/ "./app/components/ServiceDetails.jsx": -/*!*******************************************!*\ - !*** ./app/components/ServiceDetails.jsx ***! - \*******************************************/ +/***/ "./app/components/Splash.jsx": +/*!***********************************!*\ + !*** ./app/components/Splash.jsx ***! + \***********************************/ +/*! exports provided: default */ +/***/ (function(module, __webpack_exports__, __webpack_require__) { + +"use strict"; +eval("__webpack_require__.r(__webpack_exports__);\n/* harmony import */ var react__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! react */ \"./node_modules/react/index.js\");\n/* harmony import */ var react__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(react__WEBPACK_IMPORTED_MODULE_0__);\n/* harmony import */ var _stylesheets_splash_css__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ../stylesheets/splash.css */ \"./app/stylesheets/splash.css\");\n/* harmony import */ var _stylesheets_splash_css__WEBPACK_IMPORTED_MODULE_1___default = /*#__PURE__*/__webpack_require__.n(_stylesheets_splash_css__WEBPACK_IMPORTED_MODULE_1__);\n\n\n\nvar _window$require = window.require('electron'),\n ipcRenderer = _window$require.ipcRenderer;\n\nvar Splash = function Splash(props) {\n var toggleSplash = props.toggleSplash; // I know this line is dense. Bear with me...useEffect hook that fires whenever splash\n // component is rendered, which, after 3 seconds fires toggleSplash passing in what ipcMain\n // returns when it receives the 'toggleSplash' message (which will be false boolean). This then\n // toggles the splash state-property inside of the App component which rerenders app and no longer\n // displays the splash page.\n\n Object(react__WEBPACK_IMPORTED_MODULE_0__[\"useEffect\"])(function () {\n setTimeout(function () {\n return toggleSplash(ipcRenderer.sendSync('toggleSplash'));\n }, 3000);\n });\n return /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_0___default.a.createElement(\"div\", {\n id: \"splash\"\n }, /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_0___default.a.createElement(\"img\", {\n alt: \"Chronos Logo\",\n src: \"app/assets/icons/icon.png\",\n id: \"splashLogo\"\n }), /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_0___default.a.createElement(\"span\", {\n id: \"splashText\",\n style: {\n fontFamily: 'Baloo Bhaijaan'\n }\n }, \"chronos\"));\n};\n\n/* harmony default export */ __webpack_exports__[\"default\"] = (Splash);\n\n//# sourceURL=webpack:///./app/components/Splash.jsx?"); + +/***/ }), + +/***/ "./app/containers/DashboardContainer.jsx": +/*!***********************************************!*\ + !*** ./app/containers/DashboardContainer.jsx ***! + \***********************************************/ /*! exports provided: default */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; -eval("__webpack_require__.r(__webpack_exports__);\n/* harmony import */ var react__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! react */ \"./node_modules/react/index.js\");\n/* harmony import */ var react__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(react__WEBPACK_IMPORTED_MODULE_0__);\n/* harmony import */ var _Modal_jsx__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ./Modal.jsx */ \"./app/components/Modal.jsx\");\nfunction _slicedToArray(arr, i) { return _arrayWithHoles(arr) || _iterableToArrayLimit(arr, i) || _nonIterableRest(); }\n\nfunction _nonIterableRest() { throw new TypeError(\"Invalid attempt to destructure non-iterable instance\"); }\n\nfunction _iterableToArrayLimit(arr, i) { if (!(Symbol.iterator in Object(arr) || Object.prototype.toString.call(arr) === \"[object Arguments]\")) { return; } var _arr = []; var _n = true; var _d = false; var _e = undefined; try { for (var _i = arr[Symbol.iterator](), _s; !(_n = (_s = _i.next()).done); _n = true) { _arr.push(_s.value); if (i && _arr.length === i) break; } } catch (err) { _d = true; _e = err; } finally { try { if (!_n && _i[\"return\"] != null) _i[\"return\"](); } finally { if (_d) throw _e; } } return _arr; }\n\nfunction _arrayWithHoles(arr) { if (Array.isArray(arr)) return arr; }\n\n/* eslint-disable react/jsx-one-expression-per-line */\n\n // Renders charts created with health and communication data for a selected database.\n\nvar ServiceDetails = function ServiceDetails(props) {\n // Renders health info detail buttons\n var service = props.service,\n setDetails = props.setDetails; // Hook used to toggle whether or not the Modal component renders\n\n var _useState = Object(react__WEBPACK_IMPORTED_MODULE_0__[\"useState\"])(false),\n _useState2 = _slicedToArray(_useState, 2),\n modalDisplay = _useState2[0],\n toggleModalDisplay = _useState2[1]; // Hook used to set the chart that the Modal displays. The\n // modalDisplay state is drilled into the Modal component.\n\n\n var _useState3 = Object(react__WEBPACK_IMPORTED_MODULE_0__[\"useState\"])(),\n _useState4 = _slicedToArray(_useState3, 2),\n modalChart = _useState4[0],\n setModalChart = _useState4[1]; // Hook used to set the Modal Component title. The \"alt\" attribute\n // is grabbed from the onClick event via event.path[0].alt\n\n\n var _useState5 = Object(react__WEBPACK_IMPORTED_MODULE_0__[\"useState\"])(),\n _useState6 = _slicedToArray(_useState5, 2),\n chartTitle = _useState6[0],\n setChartTitle = _useState6[1]; // Dictionary used by the healthInfoButtons loop below\n\n\n var buttonProperties = [{\n id: 'Request',\n alt: 'Request Data',\n src: 'app/assets/pieChart.png'\n }, {\n id: 'Response',\n alt: 'Response Data',\n src: 'app/assets/pieChart.png'\n }, {\n id: 'Speed',\n alt: 'Speed Data',\n src: 'app/assets/speedChart.png'\n }, {\n id: 'Processes',\n alt: 'Processes Data',\n src: 'app/assets/processingChart.png'\n }, {\n id: 'Latency',\n alt: 'Latency Data',\n src: 'app/assets/latencyChart.png'\n }, {\n id: 'Temperature',\n alt: 'Temperature Data',\n src: 'app/assets/tempChart.png'\n }, {\n id: 'Memory',\n alt: 'Memory Data',\n src: 'app/assets/memoryChart.png'\n }]; // Create the Health Info buttons and their associated properties. Each time a button is clicked,\n // setChartTitle will grab a title to render in the Modal component, setModalChart will grab the\n // correct chart to render, and toggleModalDisplay will actually render the Modal display\n\n var healthInfoButtons = [];\n\n for (var i = 0; i < buttonProperties.length; i += 1) {\n healthInfoButtons.push(react__WEBPACK_IMPORTED_MODULE_0___default.a.createElement(\"div\", null, react__WEBPACK_IMPORTED_MODULE_0___default.a.createElement(\"div\", {\n className: \"healthChartContainer\"\n }, react__WEBPACK_IMPORTED_MODULE_0___default.a.createElement(\"input\", {\n onClick: function onClick() {\n setChartTitle(event.path[0].alt);\n setModalChart(event.path[0].id);\n toggleModalDisplay(!modalDisplay);\n },\n service: service,\n type: \"image\",\n id: buttonProperties[i].id,\n src: buttonProperties[i].src,\n width: \"60px\",\n alt: buttonProperties[i].alt\n }), react__WEBPACK_IMPORTED_MODULE_0___default.a.createElement(\"br\", null), react__WEBPACK_IMPORTED_MODULE_0___default.a.createElement(\"div\", null, buttonProperties[i].id))));\n }\n\n return react__WEBPACK_IMPORTED_MODULE_0___default.a.createElement(\"div\", {\n id: \"serviceDetailsContainer\"\n }, modalDisplay ? react__WEBPACK_IMPORTED_MODULE_0___default.a.createElement(_Modal_jsx__WEBPACK_IMPORTED_MODULE_1__[\"default\"], {\n chartTitle: chartTitle,\n service: service,\n modalChart: modalChart,\n toggleModalDisplay: toggleModalDisplay,\n onClick: function onClick() {\n toggleModalDisplay(!modalDisplay);\n }\n }) : null, react__WEBPACK_IMPORTED_MODULE_0___default.a.createElement(\"h3\", {\n id: \"microserviceHealthTitle\"\n }, \"Microservice Health - \", service), react__WEBPACK_IMPORTED_MODULE_0___default.a.createElement(\"div\", {\n id: \"healthGrid\"\n }, healthInfoButtons), react__WEBPACK_IMPORTED_MODULE_0___default.a.createElement(\"button\", {\n className: \"backButton\",\n type: \"button\",\n onClick: function onClick() {\n // document.location.reload()\n setDetails(null);\n }\n }, \"Clear Health Data\"));\n};\n\n/* harmony default export */ __webpack_exports__[\"default\"] = (ServiceDetails);\n\n//# sourceURL=webpack:///./app/components/ServiceDetails.jsx?"); +eval("__webpack_require__.r(__webpack_exports__);\n/* harmony import */ var react__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! react */ \"./node_modules/react/index.js\");\n/* harmony import */ var react__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(react__WEBPACK_IMPORTED_MODULE_0__);\n/* harmony import */ var _MonitoringContainer_jsx__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ./MonitoringContainer.jsx */ \"./app/containers/MonitoringContainer.jsx\");\n/* harmony import */ var _SideBarContainer_jsx__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! ./SideBarContainer.jsx */ \"./app/containers/SideBarContainer.jsx\");\n/* harmony import */ var _stylesheets_dashboard_css__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(/*! ../stylesheets/dashboard.css */ \"./app/stylesheets/dashboard.css\");\n/* harmony import */ var _stylesheets_dashboard_css__WEBPACK_IMPORTED_MODULE_3___default = /*#__PURE__*/__webpack_require__.n(_stylesheets_dashboard_css__WEBPACK_IMPORTED_MODULE_3__);\nfunction _slicedToArray(arr, i) { return _arrayWithHoles(arr) || _iterableToArrayLimit(arr, i) || _unsupportedIterableToArray(arr, i) || _nonIterableRest(); }\n\nfunction _nonIterableRest() { throw new TypeError(\"Invalid attempt to destructure non-iterable instance.\\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.\"); }\n\nfunction _unsupportedIterableToArray(o, minLen) { if (!o) return; if (typeof o === \"string\") return _arrayLikeToArray(o, minLen); var n = Object.prototype.toString.call(o).slice(8, -1); if (n === \"Object\" && o.constructor) n = o.constructor.name; if (n === \"Map\" || n === \"Set\") return Array.from(o); if (n === \"Arguments\" || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(n)) return _arrayLikeToArray(o, minLen); }\n\nfunction _arrayLikeToArray(arr, len) { if (len == null || len > arr.length) len = arr.length; for (var i = 0, arr2 = new Array(len); i < len; i++) { arr2[i] = arr[i]; } return arr2; }\n\nfunction _iterableToArrayLimit(arr, i) { if (typeof Symbol === \"undefined\" || !(Symbol.iterator in Object(arr))) return; var _arr = []; var _n = true; var _d = false; var _e = undefined; try { for (var _i = arr[Symbol.iterator](), _s; !(_n = (_s = _i.next()).done); _n = true) { _arr.push(_s.value); if (i && _arr.length === i) break; } } catch (err) { _d = true; _e = err; } finally { try { if (!_n && _i[\"return\"] != null) _i[\"return\"](); } finally { if (_d) throw _e; } } return _arr; }\n\nfunction _arrayWithHoles(arr) { if (Array.isArray(arr)) return arr; }\n\n\n\n\n\n\nvar DashboardContainer = function DashboardContainer() {\n var _useState = Object(react__WEBPACK_IMPORTED_MODULE_0__[\"useState\"])(),\n _useState2 = _slicedToArray(_useState, 2),\n detailsSelected = _useState2[0],\n setDetails = _useState2[1];\n\n return /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_0___default.a.createElement(\"div\", {\n className: \"servicesDashboardContainer\"\n }, /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_0___default.a.createElement(_SideBarContainer_jsx__WEBPACK_IMPORTED_MODULE_2__[\"default\"], {\n setDetails: setDetails\n }), /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_0___default.a.createElement(\"div\", {\n className: \"databsaseList\"\n }, /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_0___default.a.createElement(_MonitoringContainer_jsx__WEBPACK_IMPORTED_MODULE_1__[\"default\"], {\n detailsSelected: detailsSelected\n })));\n};\n\n/* harmony default export */ __webpack_exports__[\"default\"] = (DashboardContainer);\n\n//# sourceURL=webpack:///./app/containers/DashboardContainer.jsx?"); /***/ }), -/***/ "./app/components/ServiceOverview.jsx": +/***/ "./app/containers/GraphsContainer.jsx": /*!********************************************!*\ - !*** ./app/components/ServiceOverview.jsx ***! + !*** ./app/containers/GraphsContainer.jsx ***! \********************************************/ /*! exports provided: default */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; -eval("__webpack_require__.r(__webpack_exports__);\n/* harmony import */ var react__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! react */ \"./node_modules/react/index.js\");\n/* harmony import */ var react__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(react__WEBPACK_IMPORTED_MODULE_0__);\n/* harmony import */ var _assets_routeChart_png__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ../assets/routeChart.png */ \"./app/assets/routeChart.png\");\n/* harmony import */ var _assets_routeChart_png__WEBPACK_IMPORTED_MODULE_1___default = /*#__PURE__*/__webpack_require__.n(_assets_routeChart_png__WEBPACK_IMPORTED_MODULE_1__);\n/* harmony import */ var _context_OverviewContext__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! ../context/OverviewContext */ \"./app/context/OverviewContext.js\");\n/* harmony import */ var _context_DetailsContext__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(/*! ../context/DetailsContext */ \"./app/context/DetailsContext.js\");\n/* harmony import */ var _ServiceDetails_jsx__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(/*! ./ServiceDetails.jsx */ \"./app/components/ServiceDetails.jsx\");\n/* harmony import */ var _Modal_jsx__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(/*! ./Modal.jsx */ \"./app/components/Modal.jsx\");\nfunction _slicedToArray(arr, i) { return _arrayWithHoles(arr) || _iterableToArrayLimit(arr, i) || _nonIterableRest(); }\n\nfunction _nonIterableRest() { throw new TypeError(\"Invalid attempt to destructure non-iterable instance\"); }\n\nfunction _iterableToArrayLimit(arr, i) { if (!(Symbol.iterator in Object(arr) || Object.prototype.toString.call(arr) === \"[object Arguments]\")) { return; } var _arr = []; var _n = true; var _d = false; var _e = undefined; try { for (var _i = arr[Symbol.iterator](), _s; !(_n = (_s = _i.next()).done); _n = true) { _arr.push(_s.value); if (i && _arr.length === i) break; } } catch (err) { _d = true; _e = err; } finally { try { if (!_n && _i[\"return\"] != null) _i[\"return\"](); } finally { if (_d) throw _e; } } return _arr; }\n\nfunction _arrayWithHoles(arr) { if (Array.isArray(arr)) return arr; }\n\n\n\n\n\n\n\n\nvar _window$require = window.require('electron'),\n ipcRenderer = _window$require.ipcRenderer;\n\nvar ServiceOverview = function ServiceOverview(props) {\n // Overview state used to create service buttons\n var _useState = Object(react__WEBPACK_IMPORTED_MODULE_0__[\"useState\"])([]),\n _useState2 = _slicedToArray(_useState, 2),\n overviewState = _useState2[0],\n setOverviewState = _useState2[1]; // Details state used to cause rerender on user selection.\n\n\n var _useState3 = Object(react__WEBPACK_IMPORTED_MODULE_0__[\"useState\"])(),\n _useState4 = _slicedToArray(_useState3, 2),\n detailsSelected = _useState4[0],\n setDetails = _useState4[1]; // Contexts have data added to them following successful IPC return. Data is later used to create charts.\n\n\n var serviceComponents = Object(react__WEBPACK_IMPORTED_MODULE_0__[\"useContext\"])(_context_OverviewContext__WEBPACK_IMPORTED_MODULE_2__[\"default\"]);\n var healthdata = Object(react__WEBPACK_IMPORTED_MODULE_0__[\"useContext\"])(_context_DetailsContext__WEBPACK_IMPORTED_MODULE_3__[\"default\"]); // Only happens when the component mounts.\n\n Object(react__WEBPACK_IMPORTED_MODULE_0__[\"useEffect\"])(function () {\n // IPC communication used to initiate query for information on microservices.\n ipcRenderer.send('overviewRequest', props.index); // IPC listener responsible for retrieving infomation from asynchronous main process message.\n\n ipcRenderer.on('overviewResponse', function (event, data) {\n // Adds to state and context.\n console.log(JSON.parse(data));\n setOverviewState(Object.values(JSON.parse(data)));\n serviceComponents.overviewData = JSON.parse(data);\n });\n }, []);\n console.log('overviewstate: ', overviewState); // Add routes to the display\n // Hook used to toggle whether or not the Modal component renders\n\n var _useState5 = Object(react__WEBPACK_IMPORTED_MODULE_0__[\"useState\"])(false),\n _useState6 = _slicedToArray(_useState5, 2),\n modalDisplay = _useState6[0],\n toggleModalDisplay = _useState6[1]; // Hook used to set the chart that the Modal displays. The\n // modalDisplay state is drilled into the Modal component.\n\n\n var _useState7 = Object(react__WEBPACK_IMPORTED_MODULE_0__[\"useState\"])(),\n _useState8 = _slicedToArray(_useState7, 2),\n modalChart = _useState8[0],\n setModalChart = _useState8[1]; // Hook used to set the Modal Component title. The \"alt\" attribute\n // is grabbed from the onClick event via event.path[0].alt\n\n\n var _useState9 = Object(react__WEBPACK_IMPORTED_MODULE_0__[\"useState\"])(),\n _useState10 = _slicedToArray(_useState9, 2),\n chartTitle = _useState10[0],\n setChartTitle = _useState10[1]; // route button AND traffic button property\n\n\n var routeButtonProperty = {\n traffic: {\n id: 'Traffic',\n alt: 'Microservice Traffic',\n src: 'app/assets/chartModal.png'\n },\n routes: {\n id: 'routesImage',\n alt: 'Route Trace',\n src: _assets_routeChart_png__WEBPACK_IMPORTED_MODULE_1___default.a\n }\n }; // declare routes array to display routes when modal is toggled\n\n var routes = []; // declare traffic array to display traffic when modal is toggled\n\n var traffic = []; // push traffic component logic traffic\n\n traffic.push(react__WEBPACK_IMPORTED_MODULE_0___default.a.createElement(\"div\", {\n className: \"healthChartContainer\"\n }, react__WEBPACK_IMPORTED_MODULE_0___default.a.createElement(\"input\", {\n onClick: function onClick() {\n setChartTitle(event.path[0].alt);\n setModalChart(event.path[0].id);\n toggleModalDisplay(!modalDisplay);\n },\n type: \"image\",\n id: routeButtonProperty.traffic.id,\n src: routeButtonProperty.traffic.src,\n width: \"60px\",\n alt: routeButtonProperty.traffic.alt\n }), react__WEBPACK_IMPORTED_MODULE_0___default.a.createElement(\"br\", null), react__WEBPACK_IMPORTED_MODULE_0___default.a.createElement(\"div\", {\n style: {\n color: 'white',\n paddingLeft: '7px'\n }\n }, routeButtonProperty.traffic.id))); // push routes component logic traffic\n\n routes.push(react__WEBPACK_IMPORTED_MODULE_0___default.a.createElement(\"div\", {\n className: \"healthChartContainer\"\n }, react__WEBPACK_IMPORTED_MODULE_0___default.a.createElement(\"input\", {\n onClick: function onClick() {\n setChartTitle(event.path[0].alt);\n setModalChart(event.path[0].id);\n toggleModalDisplay(!modalDisplay);\n },\n type: \"image\",\n id: routeButtonProperty.routes.id,\n src: \"app/assets/routeChart.png\",\n width: \"60px\",\n alt: routeButtonProperty.routes.alt\n }), react__WEBPACK_IMPORTED_MODULE_0___default.a.createElement(\"br\", null), react__WEBPACK_IMPORTED_MODULE_0___default.a.createElement(\"div\", {\n style: {\n color: 'white',\n paddingLeft: '7px'\n }\n }, \"Routes\"))); // Filters data received from IPC to the communications database to create a list of the services tracked in the provided database,\n\n var serviceList = function serviceList() {\n // Holds the buttons generated for unique services.\n var componentButtons = []; // Tracks which services already have button created.\n\n var serviceCache = {};\n\n var _loop = function _loop(i) {\n var element = overviewState[i]; // If SQL\n\n if (element.currentmicroservice) {\n if (!(element.currentmicroservice in serviceCache)) {\n var button = react__WEBPACK_IMPORTED_MODULE_0___default.a.createElement(\"button\", {\n className: \"servicesBtn\",\n currentMicroservice: element.currentmicroservice,\n type: \"button\",\n key: \"serviceItem\".concat(props.index).concat(i),\n onClick: function onClick() {\n // IPC communication used to initiate query for information on microservice health information.\n ipcRenderer.send('detailsRequest', props.index); // IPC listener responsible for retrieving infomation from asynchronous main process message.\n\n ipcRenderer.on('detailsResponse', function (event, data) {\n // Adds returned data to context\n healthdata.detailData = Object.values(JSON.parse(data)); // Updates state. Triggers rerender.\n\n setDetails(react__WEBPACK_IMPORTED_MODULE_0___default.a.createElement(_ServiceDetails_jsx__WEBPACK_IMPORTED_MODULE_4__[\"default\"], {\n service: element.currentmicroservice,\n setDetails: setDetails\n }));\n console.log('details selected is: ', detailsSelected);\n });\n }\n }, element.currentmicroservice);\n componentButtons.push(button);\n serviceCache[element.currentmicroservice] = true;\n }\n } else if (element.currentMicroservice) {\n // If Mongo\n if (element.currentMicroservice) {\n if (!(element.currentMicroservice in serviceCache)) {\n var _button = react__WEBPACK_IMPORTED_MODULE_0___default.a.createElement(\"button\", {\n className: \"servicesBtn\",\n type: \"button\",\n key: \"serviceItem\".concat(props.index).concat(i),\n onClick: function onClick() {\n ipcRenderer.send('detailsRequest', props.index); // IPC listener responsible for retrieving infomation from asynchronous main process message.\n\n ipcRenderer.on('detailsResponse', function (event, data) {\n // Adds returned data to context.\n healthdata.detailData = Object.values(JSON.parse(data)); // Updates state. Triggers rerender.\n\n setDetails(react__WEBPACK_IMPORTED_MODULE_0___default.a.createElement(_ServiceDetails_jsx__WEBPACK_IMPORTED_MODULE_4__[\"default\"], {\n service: element.currentMicroservice\n }));\n });\n }\n }, element.currentMicroservice);\n\n componentButtons.push(_button);\n serviceCache[element.currentMicroservice] = true;\n }\n }\n }\n };\n\n for (var i = 0; i < overviewState.length; i += 1) {\n _loop(i);\n } // If there's no data, return 'No data present', else return microservices button\n\n\n if (componentButtons.length === 0) {\n return react__WEBPACK_IMPORTED_MODULE_0___default.a.createElement(\"p\", null, \"No data present\");\n }\n\n return componentButtons;\n };\n\n var tooltipWriteup = \"Communications data - Routes and Traffic - is not specific to a single microservice,\\n but combines data from all microservices within a single application network.\";\n var tooltipWriteup2 = 'View and toggle between health data for individual services within your microservice network.';\n return react__WEBPACK_IMPORTED_MODULE_0___default.a.createElement(\"div\", {\n className: \"mainContainer\"\n }, react__WEBPACK_IMPORTED_MODULE_0___default.a.createElement(\"h1\", {\n className: \"overviewTitle\"\n }, \"Microservices Overview\"), react__WEBPACK_IMPORTED_MODULE_0___default.a.createElement(\"h2\", null, \"Communications Data\", react__WEBPACK_IMPORTED_MODULE_0___default.a.createElement(\"sup\", {\n className: \"tooltip\"\n }, \"\\u24D8\", react__WEBPACK_IMPORTED_MODULE_0___default.a.createElement(\"div\", {\n className: \"tooltiptext\"\n }, tooltipWriteup))), modalDisplay ? react__WEBPACK_IMPORTED_MODULE_0___default.a.createElement(_Modal_jsx__WEBPACK_IMPORTED_MODULE_5__[\"default\"], {\n chartTitle: chartTitle,\n modalChart: modalChart,\n service: \"\",\n toggleModalDisplay: toggleModalDisplay,\n onClick: function onClick() {\n toggleModalDisplay(!modalDisplay);\n }\n }) : null, react__WEBPACK_IMPORTED_MODULE_0___default.a.createElement(\"div\", {\n id: \"routeAndTrafficDisplay\"\n }, routes, traffic), react__WEBPACK_IMPORTED_MODULE_0___default.a.createElement(\"div\", {\n className: \"servicesList\"\n }, serviceList(), react__WEBPACK_IMPORTED_MODULE_0___default.a.createElement(\"sup\", {\n className: \"tooltip\"\n }, \"\\u24D8\", react__WEBPACK_IMPORTED_MODULE_0___default.a.createElement(\"div\", {\n className: \"tooltiptext\"\n }, tooltipWriteup2))), react__WEBPACK_IMPORTED_MODULE_0___default.a.createElement(\"br\", null), detailsSelected || null);\n};\n\n/* harmony default export */ __webpack_exports__[\"default\"] = (ServiceOverview);\n\n//# sourceURL=webpack:///./app/components/ServiceOverview.jsx?"); +<<<<<<< HEAD +eval("__webpack_require__.r(__webpack_exports__);\n/* harmony import */ var react__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! react */ \"./node_modules/react/index.js\");\n/* harmony import */ var react__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(react__WEBPACK_IMPORTED_MODULE_0__);\n/* harmony import */ var _charts_request_type_chart_jsx__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ../charts/request-type-chart.jsx */ \"./app/charts/request-type-chart.jsx\");\n/* harmony import */ var _charts_response_code_chart_jsx__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! ../charts/response-code-chart.jsx */ \"./app/charts/response-code-chart.jsx\");\n/* harmony import */ var _charts_speed_chart_jsx__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(/*! ../charts/speed-chart.jsx */ \"./app/charts/speed-chart.jsx\");\n/* harmony import */ var _charts_processes_chart_jsx__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(/*! ../charts/processes-chart.jsx */ \"./app/charts/processes-chart.jsx\");\n/* harmony import */ var _charts_temperature_chart_jsx__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(/*! ../charts/temperature-chart.jsx */ \"./app/charts/temperature-chart.jsx\");\n/* harmony import */ var _charts_latency_chart_jsx__WEBPACK_IMPORTED_MODULE_6__ = __webpack_require__(/*! ../charts/latency-chart.jsx */ \"./app/charts/latency-chart.jsx\");\n/* harmony import */ var _charts_memory_chart_jsx__WEBPACK_IMPORTED_MODULE_7__ = __webpack_require__(/*! ../charts/memory-chart.jsx */ \"./app/charts/memory-chart.jsx\");\n/* harmony import */ var _charts_route_trace_jsx__WEBPACK_IMPORTED_MODULE_8__ = __webpack_require__(/*! ../charts/route-trace.jsx */ \"./app/charts/route-trace.jsx\");\n/* harmony import */ var _charts_microservice_traffic_jsx__WEBPACK_IMPORTED_MODULE_9__ = __webpack_require__(/*! ../charts/microservice-traffic.jsx */ \"./app/charts/microservice-traffic.jsx\");\n/* harmony import */ var _stylesheets_graphs_css__WEBPACK_IMPORTED_MODULE_10__ = __webpack_require__(/*! ../stylesheets/graphs.css */ \"./app/stylesheets/graphs.css\");\n/* harmony import */ var _stylesheets_graphs_css__WEBPACK_IMPORTED_MODULE_10___default = /*#__PURE__*/__webpack_require__.n(_stylesheets_graphs_css__WEBPACK_IMPORTED_MODULE_10__);\n\n\n\n\n\n\n\n\n\n\n\n\nvar GraphsContainer = function GraphsContainer(props) {\n var service = props.service;\n return /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_0___default.a.createElement(\"div\", {\n className: \"graphsGrid\"\n }, /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_0___default.a.createElement(_charts_request_type_chart_jsx__WEBPACK_IMPORTED_MODULE_1__[\"default\"], {\n service: service\n }), /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_0___default.a.createElement(_charts_route_trace_jsx__WEBPACK_IMPORTED_MODULE_8__[\"default\"], {\n service: service\n }), /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_0___default.a.createElement(_charts_response_code_chart_jsx__WEBPACK_IMPORTED_MODULE_2__[\"default\"], {\n service: service\n }), /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_0___default.a.createElement(_charts_speed_chart_jsx__WEBPACK_IMPORTED_MODULE_3__[\"default\"], {\n service: service\n }), /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_0___default.a.createElement(_charts_processes_chart_jsx__WEBPACK_IMPORTED_MODULE_4__[\"default\"], {\n service: service\n }), /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_0___default.a.createElement(_charts_latency_chart_jsx__WEBPACK_IMPORTED_MODULE_6__[\"default\"], {\n service: service\n }), /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_0___default.a.createElement(_charts_microservice_traffic_jsx__WEBPACK_IMPORTED_MODULE_9__[\"default\"], {\n service: service\n }), /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_0___default.a.createElement(_charts_temperature_chart_jsx__WEBPACK_IMPORTED_MODULE_5__[\"default\"], {\n service: service\n }), /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_0___default.a.createElement(_charts_memory_chart_jsx__WEBPACK_IMPORTED_MODULE_7__[\"default\"], {\n service: service\n }));\n};\n\n/* harmony default export */ __webpack_exports__[\"default\"] = (GraphsContainer);\n\n//# sourceURL=webpack:///./app/containers/GraphsContainer.jsx?"); +======= +eval("__webpack_require__.r(__webpack_exports__);\n/* harmony import */ var react__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! react */ \"./node_modules/react/index.js\");\n/* harmony import */ var react__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(react__WEBPACK_IMPORTED_MODULE_0__);\n/* harmony import */ var _assets_routeChart_png__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ../assets/routeChart.png */ \"./app/assets/routeChart.png\");\n/* harmony import */ var _assets_routeChart_png__WEBPACK_IMPORTED_MODULE_1___default = /*#__PURE__*/__webpack_require__.n(_assets_routeChart_png__WEBPACK_IMPORTED_MODULE_1__);\n/* harmony import */ var _context_OverviewContext__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! ../context/OverviewContext */ \"./app/context/OverviewContext.js\");\n/* harmony import */ var _context_DetailsContext__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(/*! ../context/DetailsContext */ \"./app/context/DetailsContext.js\");\n/* harmony import */ var _ServiceDetails_jsx__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(/*! ./ServiceDetails.jsx */ \"./app/components/ServiceDetails.jsx\");\n/* harmony import */ var _Modal_jsx__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(/*! ./Modal.jsx */ \"./app/components/Modal.jsx\");\nfunction _slicedToArray(arr, i) { return _arrayWithHoles(arr) || _iterableToArrayLimit(arr, i) || _nonIterableRest(); }\n\nfunction _nonIterableRest() { throw new TypeError(\"Invalid attempt to destructure non-iterable instance\"); }\n\nfunction _iterableToArrayLimit(arr, i) { if (!(Symbol.iterator in Object(arr) || Object.prototype.toString.call(arr) === \"[object Arguments]\")) { return; } var _arr = []; var _n = true; var _d = false; var _e = undefined; try { for (var _i = arr[Symbol.iterator](), _s; !(_n = (_s = _i.next()).done); _n = true) { _arr.push(_s.value); if (i && _arr.length === i) break; } } catch (err) { _d = true; _e = err; } finally { try { if (!_n && _i[\"return\"] != null) _i[\"return\"](); } finally { if (_d) throw _e; } } return _arr; }\n\nfunction _arrayWithHoles(arr) { if (Array.isArray(arr)) return arr; }\n\n\n\n\n\n\n\n\nvar _window$require = window.require('electron'),\n ipcRenderer = _window$require.ipcRenderer;\n\nvar ServiceOverview = function ServiceOverview(props) {\n // Overview state used to create service buttons\n var _useState = Object(react__WEBPACK_IMPORTED_MODULE_0__[\"useState\"])([]),\n _useState2 = _slicedToArray(_useState, 2),\n overviewState = _useState2[0],\n setOverviewState = _useState2[1]; // Details state used to cause rerender on user selection.\n\n\n var _useState3 = Object(react__WEBPACK_IMPORTED_MODULE_0__[\"useState\"])(),\n _useState4 = _slicedToArray(_useState3, 2),\n detailsSelected = _useState4[0],\n setDetails = _useState4[1]; // Contexts have data added to them following successful IPC return. Data is later used to create charts.\n\n\n var serviceComponents = Object(react__WEBPACK_IMPORTED_MODULE_0__[\"useContext\"])(_context_OverviewContext__WEBPACK_IMPORTED_MODULE_2__[\"default\"]);\n var healthdata = Object(react__WEBPACK_IMPORTED_MODULE_0__[\"useContext\"])(_context_DetailsContext__WEBPACK_IMPORTED_MODULE_3__[\"default\"]); // Only happens when the component mounts.\n\n Object(react__WEBPACK_IMPORTED_MODULE_0__[\"useEffect\"])(function () {\n // IPC communication used to initiate query for information on microservices.\n ipcRenderer.send('overviewRequest', props.index); // IPC listener responsible for retrieving infomation from asynchronous main process message.\n\n ipcRenderer.on('overviewResponse', function (event, data) {\n // Adds to state and context.\n console.log(JSON.parse(data));\n setOverviewState(Object.values(JSON.parse(data)));\n serviceComponents.overviewData = JSON.parse(data);\n });\n }, []);\n console.log('overviewstate: ', overviewState); // Add routes to the display\n // Hook used to toggle whether or not the Modal component renders\n\n var _useState5 = Object(react__WEBPACK_IMPORTED_MODULE_0__[\"useState\"])(false),\n _useState6 = _slicedToArray(_useState5, 2),\n modalDisplay = _useState6[0],\n toggleModalDisplay = _useState6[1]; // Hook used to set the chart that the Modal displays. The\n // modalDisplay state is drilled into the Modal component.\n\n\n var _useState7 = Object(react__WEBPACK_IMPORTED_MODULE_0__[\"useState\"])(),\n _useState8 = _slicedToArray(_useState7, 2),\n modalChart = _useState8[0],\n setModalChart = _useState8[1]; // Hook used to set the Modal Component title. The \"alt\" attribute\n // is grabbed from the onClick event via event.path[0].alt\n\n\n var _useState9 = Object(react__WEBPACK_IMPORTED_MODULE_0__[\"useState\"])(),\n _useState10 = _slicedToArray(_useState9, 2),\n chartTitle = _useState10[0],\n setChartTitle = _useState10[1]; // route button AND traffic button property\n\n\n var routeButtonProperty = {\n traffic: {\n id: 'Traffic',\n alt: 'Microservice Traffic',\n src: 'app/assets/chartModal.png'\n },\n routes: {\n id: 'routesImage',\n alt: 'Route Trace',\n src: _assets_routeChart_png__WEBPACK_IMPORTED_MODULE_1___default.a\n }\n }; // declare routes array to display routes when modal is toggled\n\n var routes = []; // declare traffic array to display traffic when modal is toggled\n\n var traffic = []; // push traffic component logic traffic\n\n traffic.push(react__WEBPACK_IMPORTED_MODULE_0___default.a.createElement(\"div\", {\n className: \"healthChartContainer\"\n }, react__WEBPACK_IMPORTED_MODULE_0___default.a.createElement(\"input\", {\n onClick: function onClick() {\n setChartTitle(event.path[0].alt);\n setModalChart(event.path[0].id);\n toggleModalDisplay(!modalDisplay);\n },\n type: \"image\",\n id: routeButtonProperty.traffic.id,\n src: routeButtonProperty.traffic.src,\n width: \"60px\",\n alt: routeButtonProperty.traffic.alt\n }), react__WEBPACK_IMPORTED_MODULE_0___default.a.createElement(\"br\", null), react__WEBPACK_IMPORTED_MODULE_0___default.a.createElement(\"div\", {\n style: {\n color: 'black',\n paddingLeft: '7px'\n }\n }, routeButtonProperty.traffic.id))); // push routes component logic traffic\n\n routes.push(react__WEBPACK_IMPORTED_MODULE_0___default.a.createElement(\"div\", {\n className: \"healthChartContainer\"\n }, react__WEBPACK_IMPORTED_MODULE_0___default.a.createElement(\"input\", {\n onClick: function onClick() {\n setChartTitle(event.path[0].alt);\n setModalChart(event.path[0].id);\n toggleModalDisplay(!modalDisplay);\n },\n type: \"image\",\n id: routeButtonProperty.routes.id,\n src: \"app/assets/routeChart.png\",\n width: \"60px\",\n alt: routeButtonProperty.routes.alt\n }), react__WEBPACK_IMPORTED_MODULE_0___default.a.createElement(\"br\", null), react__WEBPACK_IMPORTED_MODULE_0___default.a.createElement(\"div\", {\n style: {\n color: 'black',\n paddingLeft: '7px'\n }\n }, \"Routes\"))); // Filters data received from IPC to the communications database to create a list of the services tracked in the provided database,\n\n var serviceList = function serviceList() {\n // Holds the buttons generated for unique services.\n var componentButtons = []; // Tracks which services already have button created.\n\n var serviceCache = {};\n\n var _loop = function _loop(i) {\n var element = overviewState[i]; // If SQL\n\n if (element.currentmicroservice) {\n if (!(element.currentmicroservice in serviceCache)) {\n var button = react__WEBPACK_IMPORTED_MODULE_0___default.a.createElement(\"button\", {\n className: \"servicesBtn\",\n currentMicroservice: element.currentmicroservice,\n type: \"button\",\n key: \"serviceItem\".concat(props.index).concat(i),\n onClick: function onClick() {\n // IPC communication used to initiate query for information on microservice health information.\n ipcRenderer.send('detailsRequest', props.index); // IPC listener responsible for retrieving infomation from asynchronous main process message.\n\n ipcRenderer.on('detailsResponse', function (event, data) {\n // Adds returned data to context\n healthdata.detailData = Object.values(JSON.parse(data)); // Updates state. Triggers rerender.\n\n setDetails(react__WEBPACK_IMPORTED_MODULE_0___default.a.createElement(_ServiceDetails_jsx__WEBPACK_IMPORTED_MODULE_4__[\"default\"], {\n service: element.currentmicroservice,\n setDetails: setDetails\n }));\n console.log('details selected is: ', detailsSelected);\n });\n }\n }, element.currentmicroservice);\n componentButtons.push(button);\n serviceCache[element.currentmicroservice] = true;\n }\n } else if (element.currentMicroservice) {\n // If Mongo\n if (element.currentMicroservice) {\n if (!(element.currentMicroservice in serviceCache)) {\n var _button = react__WEBPACK_IMPORTED_MODULE_0___default.a.createElement(\"button\", {\n className: \"servicesBtn\",\n type: \"button\",\n key: \"serviceItem\".concat(props.index).concat(i),\n onClick: function onClick() {\n ipcRenderer.send('detailsRequest', props.index); // IPC listener responsible for retrieving infomation from asynchronous main process message.\n\n ipcRenderer.on('detailsResponse', function (event, data) {\n // Adds returned data to context.\n healthdata.detailData = Object.values(JSON.parse(data)); // Updates state. Triggers rerender.\n\n setDetails(react__WEBPACK_IMPORTED_MODULE_0___default.a.createElement(_ServiceDetails_jsx__WEBPACK_IMPORTED_MODULE_4__[\"default\"], {\n service: element.currentMicroservice\n }));\n });\n }\n }, element.currentMicroservice);\n\n componentButtons.push(_button);\n serviceCache[element.currentMicroservice] = true;\n }\n }\n }\n };\n\n for (var i = 0; i < overviewState.length; i += 1) {\n _loop(i);\n } // If there's no data, return 'No data present', else return microservices button\n\n\n if (componentButtons.length === 0) {\n return react__WEBPACK_IMPORTED_MODULE_0___default.a.createElement(\"p\", null, \"No data present\");\n }\n\n return componentButtons;\n };\n\n var tooltipWriteup = \"Communications data - Routes and Traffic - is not specific to a single microservice,\\n but combines data from all microservices within a single application network.\";\n var tooltipWriteup2 = 'View and toggle between health data for individual services within your microservice network.';\n return react__WEBPACK_IMPORTED_MODULE_0___default.a.createElement(\"div\", {\n className: \"mainContainer\"\n }, react__WEBPACK_IMPORTED_MODULE_0___default.a.createElement(\"h1\", {\n className: \"overviewTitle\"\n }, \"Microservices Overview\"), react__WEBPACK_IMPORTED_MODULE_0___default.a.createElement(\"h2\", null, \"Communications Data\", react__WEBPACK_IMPORTED_MODULE_0___default.a.createElement(\"sup\", {\n className: \"tooltip\"\n }, \"\\u24D8\", react__WEBPACK_IMPORTED_MODULE_0___default.a.createElement(\"div\", {\n className: \"tooltiptext\"\n }, tooltipWriteup))), modalDisplay ? react__WEBPACK_IMPORTED_MODULE_0___default.a.createElement(_Modal_jsx__WEBPACK_IMPORTED_MODULE_5__[\"default\"], {\n chartTitle: chartTitle,\n modalChart: modalChart,\n service: \"\",\n toggleModalDisplay: toggleModalDisplay,\n onClick: function onClick() {\n toggleModalDisplay(!modalDisplay);\n }\n }) : null, react__WEBPACK_IMPORTED_MODULE_0___default.a.createElement(\"div\", {\n id: \"routeAndTrafficDisplay\"\n }, routes, traffic), react__WEBPACK_IMPORTED_MODULE_0___default.a.createElement(\"div\", {\n className: \"servicesList\"\n }, serviceList(), react__WEBPACK_IMPORTED_MODULE_0___default.a.createElement(\"sup\", {\n className: \"tooltip\"\n }, \"\\u24D8\", react__WEBPACK_IMPORTED_MODULE_0___default.a.createElement(\"div\", {\n className: \"tooltiptext\"\n }, tooltipWriteup2))), react__WEBPACK_IMPORTED_MODULE_0___default.a.createElement(\"br\", null), detailsSelected || null);\n};\n\n/* harmony default export */ __webpack_exports__[\"default\"] = (ServiceOverview);\n\n//# sourceURL=webpack:///./app/components/ServiceOverview.jsx?"); +>>>>>>> f44452fa8acda19c6131565d833c41404df7bd4d /***/ }), -/***/ "./app/components/ServicesDashboard.jsx": -/*!**********************************************!*\ - !*** ./app/components/ServicesDashboard.jsx ***! - \**********************************************/ +/***/ "./app/containers/MonitoringContainer.jsx": +/*!************************************************!*\ + !*** ./app/containers/MonitoringContainer.jsx ***! + \************************************************/ /*! exports provided: default */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; -eval("__webpack_require__.r(__webpack_exports__);\n/* harmony import */ var react__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! react */ \"./node_modules/react/index.js\");\n/* harmony import */ var react__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(react__WEBPACK_IMPORTED_MODULE_0__);\n/* harmony import */ var _ServiceOverview_jsx__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ./ServiceOverview.jsx */ \"./app/components/ServiceOverview.jsx\");\n/* harmony import */ var _context_DashboardContext__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! ../context/DashboardContext */ \"./app/context/DashboardContext.js\");\n/* harmony import */ var _context_SetupContext__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(/*! ../context/SetupContext */ \"./app/context/SetupContext.js\");\n/* harmony import */ var _AddService_jsx__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(/*! ./AddService.jsx */ \"./app/components/AddService.jsx\");\n/* harmony import */ var _DeleteService_jsx__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(/*! ./DeleteService.jsx */ \"./app/components/DeleteService.jsx\");\nfunction _slicedToArray(arr, i) { return _arrayWithHoles(arr) || _iterableToArrayLimit(arr, i) || _nonIterableRest(); }\n\nfunction _nonIterableRest() { throw new TypeError(\"Invalid attempt to destructure non-iterable instance\"); }\n\nfunction _iterableToArrayLimit(arr, i) { if (!(Symbol.iterator in Object(arr) || Object.prototype.toString.call(arr) === \"[object Arguments]\")) { return; } var _arr = []; var _n = true; var _d = false; var _e = undefined; try { for (var _i = arr[Symbol.iterator](), _s; !(_n = (_s = _i.next()).done); _n = true) { _arr.push(_s.value); if (i && _arr.length === i) break; } } catch (err) { _d = true; _e = err; } finally { try { if (!_n && _i[\"return\"] != null) _i[\"return\"](); } finally { if (_d) throw _e; } } return _arr; }\n\nfunction _arrayWithHoles(arr) { if (Array.isArray(arr)) return arr; }\n\n/* eslint-disable react/jsx-one-expression-per-line */\n\n\n\n\n\n\n\nvar ServicesDashboard = function ServicesDashboard(props) {\n // Used to toggle setup required if user wants to add a new database.\n var setup = Object(react__WEBPACK_IMPORTED_MODULE_0__[\"useContext\"])(_context_SetupContext__WEBPACK_IMPORTED_MODULE_3__[\"default\"]); // List of the databases saved by users to track microservices.\n\n var serviceList = Object(react__WEBPACK_IMPORTED_MODULE_0__[\"useContext\"])(_context_DashboardContext__WEBPACK_IMPORTED_MODULE_2__[\"default\"]); // Used to hold the buttons created for each database found in context.\n\n var _useState = Object(react__WEBPACK_IMPORTED_MODULE_0__[\"useState\"])(),\n _useState2 = _slicedToArray(_useState, 2),\n serviceSelected = _useState2[0],\n setSelection = _useState2[1]; // Creates button for each database in dashboard context.\n\n\n var renderServiceList = function renderServiceList(context) {\n var buttonStore = [];\n\n var _loop = function _loop(i) {\n buttonStore.push(react__WEBPACK_IMPORTED_MODULE_0___default.a.createElement(\"button\", {\n className: \"microserviceBtn\",\n type: \"button\",\n key: \"\".concat(i).concat(context[i]),\n onClick: function onClick() {\n setSelection(react__WEBPACK_IMPORTED_MODULE_0___default.a.createElement(_ServiceOverview_jsx__WEBPACK_IMPORTED_MODULE_1__[\"default\"], {\n index: i\n }));\n }\n }, context[i]));\n };\n\n for (var i = 0; i < context.length; i += 1) {\n _loop(i);\n }\n\n return buttonStore;\n };\n\n return react__WEBPACK_IMPORTED_MODULE_0___default.a.createElement(\"div\", {\n className: \"servicesDashboardContainer\"\n }, react__WEBPACK_IMPORTED_MODULE_0___default.a.createElement(\"div\", {\n className: \"left\"\n }, react__WEBPACK_IMPORTED_MODULE_0___default.a.createElement(\"div\", {\n className: \"leftTopContainer\"\n }, react__WEBPACK_IMPORTED_MODULE_0___default.a.createElement(\"img\", {\n alt: \"Chronos Logo\",\n src: \"app/assets/icon2Cropped.png\",\n id: \"serviceDashLogo\"\n }), react__WEBPACK_IMPORTED_MODULE_0___default.a.createElement(\"div\", {\n className: \"left-top\"\n }, react__WEBPACK_IMPORTED_MODULE_0___default.a.createElement(\"h2\", {\n className: \"dashboardHeader\"\n }, \"Your Databases\"), renderServiceList(serviceList)), react__WEBPACK_IMPORTED_MODULE_0___default.a.createElement(\"div\", {\n className: \"left-bottom\"\n }, react__WEBPACK_IMPORTED_MODULE_0___default.a.createElement(\"button\", {\n className: \"overviewSubmitBtn\",\n type: \"submit\",\n key: \"BackToStart\",\n onClick: function onClick() {\n setup.setupRequired = setup.toggleSetup(false);\n setSelection(react__WEBPACK_IMPORTED_MODULE_0___default.a.createElement(_AddService_jsx__WEBPACK_IMPORTED_MODULE_4__[\"default\"], null));\n }\n }, \"Add Database\"), react__WEBPACK_IMPORTED_MODULE_0___default.a.createElement(\"button\", {\n className: \"overviewSubmitBtn\",\n type: \"submit\",\n key: \"goToDeletePage\",\n onClick: function onClick() {\n setSelection(react__WEBPACK_IMPORTED_MODULE_0___default.a.createElement(_DeleteService_jsx__WEBPACK_IMPORTED_MODULE_5__[\"default\"], null));\n }\n }, \"Delete Database\")), react__WEBPACK_IMPORTED_MODULE_0___default.a.createElement(\"div\", {\n className: \"left-bottom\"\n }, react__WEBPACK_IMPORTED_MODULE_0___default.a.createElement(\"button\", {\n className: \"overviewSubmitBtn\",\n type: \"submit\",\n onClick: function onClick() {\n location.reload();\n }\n }, \"Refresh overview\")))), react__WEBPACK_IMPORTED_MODULE_0___default.a.createElement(\"div\", {\n className: \"databsaseList\"\n }, serviceSelected));\n};\n\n/* harmony default export */ __webpack_exports__[\"default\"] = (ServicesDashboard);\n\n//# sourceURL=webpack:///./app/components/ServicesDashboard.jsx?"); +eval("__webpack_require__.r(__webpack_exports__);\n/* harmony import */ var react__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! react */ \"./node_modules/react/index.js\");\n/* harmony import */ var react__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(react__WEBPACK_IMPORTED_MODULE_0__);\n\n\nvar MonitoringContainer = function MonitoringContainer(props) {\n var detailsSelected = props.detailsSelected;\n return /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_0___default.a.createElement(\"div\", {\n className: \"mainContainer\"\n }, detailsSelected || null);\n};\n\n/* harmony default export */ __webpack_exports__[\"default\"] = (MonitoringContainer);\n\n//# sourceURL=webpack:///./app/containers/MonitoringContainer.jsx?"); /***/ }), -/***/ "./app/components/Splash.jsx": -/*!***********************************!*\ - !*** ./app/components/Splash.jsx ***! - \***********************************/ +/***/ "./app/containers/SideBarContainer.jsx": +/*!*********************************************!*\ + !*** ./app/containers/SideBarContainer.jsx ***! + \*********************************************/ /*! exports provided: default */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; -eval("__webpack_require__.r(__webpack_exports__);\n/* harmony import */ var react__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! react */ \"./node_modules/react/index.js\");\n/* harmony import */ var react__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(react__WEBPACK_IMPORTED_MODULE_0__);\n\n\nvar _window$require = window.require('electron'),\n ipcRenderer = _window$require.ipcRenderer;\n\nvar Splash = function Splash(props) {\n var toggleSplash = props.toggleSplash; // I know this line is dense. Bear with me...useEffect hook that fires whenever splash\n // component is rendered, which, after 3 seconds fires toggleSplash passing in what ipcMain\n // returns when it receives the 'toggleSplash' message (which will be false boolean). This then\n // toggles the splash state-property inside of the App component which rerenders app and no longer\n // displays the splash page.\n\n Object(react__WEBPACK_IMPORTED_MODULE_0__[\"useEffect\"])(function () {\n setTimeout(function () {\n return toggleSplash(ipcRenderer.sendSync('toggleSplash'));\n }, 3000);\n });\n return react__WEBPACK_IMPORTED_MODULE_0___default.a.createElement(\"div\", {\n id: \"splash\"\n }, react__WEBPACK_IMPORTED_MODULE_0___default.a.createElement(\"img\", {\n alt: \"Chronos Logo\",\n src: \"app/assets/icons/icon.png\",\n id: \"splashLogo\"\n }), react__WEBPACK_IMPORTED_MODULE_0___default.a.createElement(\"span\", {\n id: \"splashText\",\n style: {\n fontFamily: 'Baloo Bhaijaan'\n }\n }, \"chronos\"));\n};\n\n/* harmony default export */ __webpack_exports__[\"default\"] = (Splash);\n\n//# sourceURL=webpack:///./app/components/Splash.jsx?"); +eval("__webpack_require__.r(__webpack_exports__);\n/* harmony import */ var react__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! react */ \"./node_modules/react/index.js\");\n/* harmony import */ var react__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(react__WEBPACK_IMPORTED_MODULE_0__);\n/* harmony import */ var _AComp_SidebarHeader_jsx__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ../AComp/SidebarHeader.jsx */ \"./app/AComp/SidebarHeader.jsx\");\n/* harmony import */ var _AComp_Extras_jsx__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! ../AComp/Extras.jsx */ \"./app/AComp/Extras.jsx\");\n/* harmony import */ var _context_SetupContext__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(/*! ../context/SetupContext */ \"./app/context/SetupContext.js\");\n/* harmony import */ var _context_DashboardContext__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(/*! ../context/DashboardContext */ \"./app/context/DashboardContext.js\");\n/* harmony import */ var _AComp_Microservices_jsx__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(/*! ../AComp/Microservices.jsx */ \"./app/AComp/Microservices.jsx\");\n/* harmony import */ var _AComp_ServicesList_jsx__WEBPACK_IMPORTED_MODULE_6__ = __webpack_require__(/*! ../AComp/ServicesList.jsx */ \"./app/AComp/ServicesList.jsx\");\n/* harmony import */ var _components_AddService_jsx__WEBPACK_IMPORTED_MODULE_7__ = __webpack_require__(/*! ../components/AddService.jsx */ \"./app/components/AddService.jsx\");\n/* harmony import */ var _components_DeleteService_jsx__WEBPACK_IMPORTED_MODULE_8__ = __webpack_require__(/*! ../components/DeleteService.jsx */ \"./app/components/DeleteService.jsx\");\n/* harmony import */ var _stylesheets_sidebar_css__WEBPACK_IMPORTED_MODULE_9__ = __webpack_require__(/*! ../stylesheets/sidebar.css */ \"./app/stylesheets/sidebar.css\");\n/* harmony import */ var _stylesheets_sidebar_css__WEBPACK_IMPORTED_MODULE_9___default = /*#__PURE__*/__webpack_require__.n(_stylesheets_sidebar_css__WEBPACK_IMPORTED_MODULE_9__);\nfunction _slicedToArray(arr, i) { return _arrayWithHoles(arr) || _iterableToArrayLimit(arr, i) || _unsupportedIterableToArray(arr, i) || _nonIterableRest(); }\n\nfunction _nonIterableRest() { throw new TypeError(\"Invalid attempt to destructure non-iterable instance.\\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.\"); }\n\nfunction _unsupportedIterableToArray(o, minLen) { if (!o) return; if (typeof o === \"string\") return _arrayLikeToArray(o, minLen); var n = Object.prototype.toString.call(o).slice(8, -1); if (n === \"Object\" && o.constructor) n = o.constructor.name; if (n === \"Map\" || n === \"Set\") return Array.from(o); if (n === \"Arguments\" || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(n)) return _arrayLikeToArray(o, minLen); }\n\nfunction _arrayLikeToArray(arr, len) { if (len == null || len > arr.length) len = arr.length; for (var i = 0, arr2 = new Array(len); i < len; i++) { arr2[i] = arr[i]; } return arr2; }\n\nfunction _iterableToArrayLimit(arr, i) { if (typeof Symbol === \"undefined\" || !(Symbol.iterator in Object(arr))) return; var _arr = []; var _n = true; var _d = false; var _e = undefined; try { for (var _i = arr[Symbol.iterator](), _s; !(_n = (_s = _i.next()).done); _n = true) { _arr.push(_s.value); if (i && _arr.length === i) break; } } catch (err) { _d = true; _e = err; } finally { try { if (!_n && _i[\"return\"] != null) _i[\"return\"](); } finally { if (_d) throw _e; } } return _arr; }\n\nfunction _arrayWithHoles(arr) { if (Array.isArray(arr)) return arr; }\n\n\n\n\n\n\n\n\n\n\n\n\nvar SidebarContainer = function SidebarContainer(props) {\n var setDetails = props.setDetails; // Used to toggle setup required if user wants to add a new database.\n\n var setup = Object(react__WEBPACK_IMPORTED_MODULE_0__[\"useContext\"])(_context_SetupContext__WEBPACK_IMPORTED_MODULE_3__[\"default\"]); // List of the databases saved by users to track microservices.\n\n var serviceList = Object(react__WEBPACK_IMPORTED_MODULE_0__[\"useContext\"])(_context_DashboardContext__WEBPACK_IMPORTED_MODULE_4__[\"default\"]); // Setting index of service selected\n\n var _useState = Object(react__WEBPACK_IMPORTED_MODULE_0__[\"useState\"])(),\n _useState2 = _slicedToArray(_useState, 2),\n index = _useState2[0],\n setIndex = _useState2[1]; // Checking to see if service clicked to display Microservices\n\n\n var _useState3 = Object(react__WEBPACK_IMPORTED_MODULE_0__[\"useState\"])('false'),\n _useState4 = _slicedToArray(_useState3, 2),\n isclicked = _useState4[0],\n setClicked = _useState4[1]; // Helper function to check if Clicked toggles\n\n\n var clickToggle = function clickToggle() {\n if (isclicked === 'true') setClicked('false');else setClicked('true');\n }; // Click function for Services\n\n\n var ServicesClick = function ServicesClick(e) {\n clickToggle(e);\n setIndex(e.target.id);\n setDetails(null); // setSelection();\n }; // Click function for AddService\n\n\n var AddClick = function AddClick() {\n setup.setupRequired = setup.toggleSetup(false);\n setDetails( /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_0___default.a.createElement(_components_AddService_jsx__WEBPACK_IMPORTED_MODULE_7__[\"default\"], null));\n }; // Click fn for Delete Service\n\n\n var DeleteClick = function DeleteClick() {\n setDetails( /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_0___default.a.createElement(_components_DeleteService_jsx__WEBPACK_IMPORTED_MODULE_8__[\"default\"], null));\n }; // Click fn for Refresh\n\n\n var RefreshClick = function RefreshClick() {\n location.reload();\n };\n\n return /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_0___default.a.createElement(\"div\", {\n className: \"left\"\n }, /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_0___default.a.createElement(\"div\", {\n className: \"leftTopContainer\"\n }, /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_0___default.a.createElement(_AComp_SidebarHeader_jsx__WEBPACK_IMPORTED_MODULE_1__[\"default\"], null), /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_0___default.a.createElement(_AComp_ServicesList_jsx__WEBPACK_IMPORTED_MODULE_6__[\"default\"], {\n context: serviceList,\n Click: ServicesClick,\n isclicked: isclicked\n }), isclicked === 'true' ? /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_0___default.a.createElement(_AComp_Microservices_jsx__WEBPACK_IMPORTED_MODULE_5__[\"default\"], {\n index: index,\n setDetails: setDetails\n }) : null, /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_0___default.a.createElement(_AComp_Extras_jsx__WEBPACK_IMPORTED_MODULE_2__[\"default\"], {\n AddClick: AddClick,\n DeleteClick: DeleteClick,\n RefreshClick: RefreshClick\n })));\n};\n\n/* harmony default export */ __webpack_exports__[\"default\"] = (SidebarContainer);\n\n//# sourceURL=webpack:///./app/containers/SideBarContainer.jsx?"); /***/ }), @@ -337,17 +390,6 @@ eval("__webpack_require__.r(__webpack_exports__);\n/* harmony export (binding) * /***/ }), -/***/ "./app/index.css": -/*!***********************!*\ - !*** ./app/index.css ***! - \***********************/ -/*! no static exports found */ -/***/ (function(module, exports, __webpack_require__) { - -eval("var content = __webpack_require__(/*! !../node_modules/css-loader/dist/cjs.js!./index.css */ \"./node_modules/css-loader/dist/cjs.js!./app/index.css\");\n\nif (typeof content === 'string') {\n content = [[module.i, content, '']];\n}\n\nvar options = {}\n\noptions.insert = \"head\";\noptions.singleton = false;\n\nvar update = __webpack_require__(/*! ../node_modules/style-loader/dist/runtime/injectStylesIntoStyleTag.js */ \"./node_modules/style-loader/dist/runtime/injectStylesIntoStyleTag.js\")(content, options);\n\nif (content.locals) {\n module.exports = content.locals;\n}\n\n\n//# sourceURL=webpack:///./app/index.css?"); - -/***/ }), - /***/ "./app/index.js": /*!**********************!*\ !*** ./app/index.js ***! @@ -356,3024 +398,385 @@ eval("var content = __webpack_require__(/*! !../node_modules/css-loader/dist/cjs /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; -eval("__webpack_require__.r(__webpack_exports__);\n/* harmony import */ var react__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! react */ \"./node_modules/react/index.js\");\n/* harmony import */ var react__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(react__WEBPACK_IMPORTED_MODULE_0__);\n/* harmony import */ var react_dom__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! react-dom */ \"./node_modules/react-dom/index.js\");\n/* harmony import */ var react_dom__WEBPACK_IMPORTED_MODULE_1___default = /*#__PURE__*/__webpack_require__.n(react_dom__WEBPACK_IMPORTED_MODULE_1__);\n/* harmony import */ var _context_SetupContext_js__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! ./context/SetupContext.js */ \"./app/context/SetupContext.js\");\n/* harmony import */ var _index_css__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(/*! ./index.css */ \"./app/index.css\");\n/* harmony import */ var _index_css__WEBPACK_IMPORTED_MODULE_3___default = /*#__PURE__*/__webpack_require__.n(_index_css__WEBPACK_IMPORTED_MODULE_3__);\n/* harmony import */ var _components_AddService_jsx__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(/*! ./components/AddService.jsx */ \"./app/components/AddService.jsx\");\n/* harmony import */ var _components_ServicesDashboard_jsx__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(/*! ./components/ServicesDashboard.jsx */ \"./app/components/ServicesDashboard.jsx\");\n/* harmony import */ var _components_Splash_jsx__WEBPACK_IMPORTED_MODULE_6__ = __webpack_require__(/*! ./components/Splash.jsx */ \"./app/components/Splash.jsx\");\nfunction _slicedToArray(arr, i) { return _arrayWithHoles(arr) || _iterableToArrayLimit(arr, i) || _nonIterableRest(); }\n\nfunction _nonIterableRest() { throw new TypeError(\"Invalid attempt to destructure non-iterable instance\"); }\n\nfunction _iterableToArrayLimit(arr, i) { if (!(Symbol.iterator in Object(arr) || Object.prototype.toString.call(arr) === \"[object Arguments]\")) { return; } var _arr = []; var _n = true; var _d = false; var _e = undefined; try { for (var _i = arr[Symbol.iterator](), _s; !(_n = (_s = _i.next()).done); _n = true) { _arr.push(_s.value); if (i && _arr.length === i) break; } } catch (err) { _d = true; _e = err; } finally { try { if (!_n && _i[\"return\"] != null) _i[\"return\"](); } finally { if (_d) throw _e; } } return _arr; }\n\nfunction _arrayWithHoles(arr) { if (Array.isArray(arr)) return arr; }\n\n\n\n\n\n\n\n\n\nvar App = function App() {\n var chronosSetup = Object(react__WEBPACK_IMPORTED_MODULE_0__[\"useContext\"])(_context_SetupContext_js__WEBPACK_IMPORTED_MODULE_2__[\"default\"]); // useState hook to conditionally render the splash page only once per session\n\n var _useState = Object(react__WEBPACK_IMPORTED_MODULE_0__[\"useState\"])(chronosSetup.splash),\n _useState2 = _slicedToArray(_useState, 2),\n splash = _useState2[0],\n toggleSplash = _useState2[1];\n\n if (splash) return react__WEBPACK_IMPORTED_MODULE_0___default.a.createElement(_components_Splash_jsx__WEBPACK_IMPORTED_MODULE_6__[\"default\"], {\n toggleSplash: toggleSplash\n });\n return chronosSetup.setupRequired ? react__WEBPACK_IMPORTED_MODULE_0___default.a.createElement(_components_AddService_jsx__WEBPACK_IMPORTED_MODULE_4__[\"default\"], null) : react__WEBPACK_IMPORTED_MODULE_0___default.a.createElement(_components_ServicesDashboard_jsx__WEBPACK_IMPORTED_MODULE_5__[\"default\"], null);\n};\n\nreact_dom__WEBPACK_IMPORTED_MODULE_1___default.a.render(react__WEBPACK_IMPORTED_MODULE_0___default.a.createElement(App, null), document.getElementById('app'));\n\n//# sourceURL=webpack:///./app/index.js?"); +eval("__webpack_require__.r(__webpack_exports__);\n/* harmony import */ var react__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! react */ \"./node_modules/react/index.js\");\n/* harmony import */ var react__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(react__WEBPACK_IMPORTED_MODULE_0__);\n/* harmony import */ var react_dom__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! react-dom */ \"./node_modules/react-dom/index.js\");\n/* harmony import */ var react_dom__WEBPACK_IMPORTED_MODULE_1___default = /*#__PURE__*/__webpack_require__.n(react_dom__WEBPACK_IMPORTED_MODULE_1__);\n/* harmony import */ var _context_SetupContext_js__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! ./context/SetupContext.js */ \"./app/context/SetupContext.js\");\n/* harmony import */ var _components_AddService_jsx__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(/*! ./components/AddService.jsx */ \"./app/components/AddService.jsx\");\n/* harmony import */ var _components_Splash_jsx__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(/*! ./components/Splash.jsx */ \"./app/components/Splash.jsx\");\n/* harmony import */ var _containers_DashboardContainer_jsx__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(/*! ./containers/DashboardContainer.jsx */ \"./app/containers/DashboardContainer.jsx\");\n/* harmony import */ var _stylesheets_index_css__WEBPACK_IMPORTED_MODULE_6__ = __webpack_require__(/*! ./stylesheets/index.css */ \"./app/stylesheets/index.css\");\n/* harmony import */ var _stylesheets_index_css__WEBPACK_IMPORTED_MODULE_6___default = /*#__PURE__*/__webpack_require__.n(_stylesheets_index_css__WEBPACK_IMPORTED_MODULE_6__);\nfunction _slicedToArray(arr, i) { return _arrayWithHoles(arr) || _iterableToArrayLimit(arr, i) || _unsupportedIterableToArray(arr, i) || _nonIterableRest(); }\n\nfunction _nonIterableRest() { throw new TypeError(\"Invalid attempt to destructure non-iterable instance.\\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.\"); }\n\nfunction _unsupportedIterableToArray(o, minLen) { if (!o) return; if (typeof o === \"string\") return _arrayLikeToArray(o, minLen); var n = Object.prototype.toString.call(o).slice(8, -1); if (n === \"Object\" && o.constructor) n = o.constructor.name; if (n === \"Map\" || n === \"Set\") return Array.from(o); if (n === \"Arguments\" || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(n)) return _arrayLikeToArray(o, minLen); }\n\nfunction _arrayLikeToArray(arr, len) { if (len == null || len > arr.length) len = arr.length; for (var i = 0, arr2 = new Array(len); i < len; i++) { arr2[i] = arr[i]; } return arr2; }\n\nfunction _iterableToArrayLimit(arr, i) { if (typeof Symbol === \"undefined\" || !(Symbol.iterator in Object(arr))) return; var _arr = []; var _n = true; var _d = false; var _e = undefined; try { for (var _i = arr[Symbol.iterator](), _s; !(_n = (_s = _i.next()).done); _n = true) { _arr.push(_s.value); if (i && _arr.length === i) break; } } catch (err) { _d = true; _e = err; } finally { try { if (!_n && _i[\"return\"] != null) _i[\"return\"](); } finally { if (_d) throw _e; } } return _arr; }\n\nfunction _arrayWithHoles(arr) { if (Array.isArray(arr)) return arr; }\n\n\n\n\n\n\n\n\n\nvar App = function App() {\n var chronosSetup = Object(react__WEBPACK_IMPORTED_MODULE_0__[\"useContext\"])(_context_SetupContext_js__WEBPACK_IMPORTED_MODULE_2__[\"default\"]); // useState hook to conditionally render the splash page only once per session\n\n var _useState = Object(react__WEBPACK_IMPORTED_MODULE_0__[\"useState\"])(chronosSetup.splash),\n _useState2 = _slicedToArray(_useState, 2),\n splash = _useState2[0],\n toggleSplash = _useState2[1];\n\n if (splash) return /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_0___default.a.createElement(_components_Splash_jsx__WEBPACK_IMPORTED_MODULE_4__[\"default\"], {\n toggleSplash: toggleSplash\n });\n return chronosSetup.setupRequired ? /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_0___default.a.createElement(_components_AddService_jsx__WEBPACK_IMPORTED_MODULE_3__[\"default\"], null) : /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_0___default.a.createElement(_containers_DashboardContainer_jsx__WEBPACK_IMPORTED_MODULE_5__[\"default\"], null);\n};\n\nreact_dom__WEBPACK_IMPORTED_MODULE_1___default.a.render( /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_0___default.a.createElement(App, null), document.getElementById('app'));\n\n//# sourceURL=webpack:///./app/index.js?"); /***/ }), -/***/ "./node_modules/chart.js/dist/Chart.js": -/*!*********************************************!*\ - !*** ./node_modules/chart.js/dist/Chart.js ***! - \*********************************************/ +/***/ "./app/stylesheets/AddService.css": +/*!****************************************!*\ + !*** ./app/stylesheets/AddService.css ***! + \****************************************/ /*! no static exports found */ /***/ (function(module, exports, __webpack_require__) { -eval("/*!\n * Chart.js v2.9.3\n * https://www.chartjs.org\n * (c) 2019 Chart.js Contributors\n * Released under the MIT License\n */\n(function (global, factory) {\n true ? module.exports = factory(function () {\n try {\n return __webpack_require__(/*! moment */ \"./node_modules/moment/moment.js\");\n } catch (e) {}\n }()) : undefined;\n})(this, function (moment) {\n 'use strict';\n\n moment = moment && moment.hasOwnProperty('default') ? moment['default'] : moment;\n\n function createCommonjsModule(fn, module) {\n return module = {\n exports: {}\n }, fn(module, module.exports), module.exports;\n }\n\n function getCjsExportFromNamespace(n) {\n return n && n['default'] || n;\n }\n\n var colorName = {\n \"aliceblue\": [240, 248, 255],\n \"antiquewhite\": [250, 235, 215],\n \"aqua\": [0, 255, 255],\n \"aquamarine\": [127, 255, 212],\n \"azure\": [240, 255, 255],\n \"beige\": [245, 245, 220],\n \"bisque\": [255, 228, 196],\n \"black\": [0, 0, 0],\n \"blanchedalmond\": [255, 235, 205],\n \"blue\": [0, 0, 255],\n \"blueviolet\": [138, 43, 226],\n \"brown\": [165, 42, 42],\n \"burlywood\": [222, 184, 135],\n \"cadetblue\": [95, 158, 160],\n \"chartreuse\": [127, 255, 0],\n \"chocolate\": [210, 105, 30],\n \"coral\": [255, 127, 80],\n \"cornflowerblue\": [100, 149, 237],\n \"cornsilk\": [255, 248, 220],\n \"crimson\": [220, 20, 60],\n \"cyan\": [0, 255, 255],\n \"darkblue\": [0, 0, 139],\n \"darkcyan\": [0, 139, 139],\n \"darkgoldenrod\": [184, 134, 11],\n \"darkgray\": [169, 169, 169],\n \"darkgreen\": [0, 100, 0],\n \"darkgrey\": [169, 169, 169],\n \"darkkhaki\": [189, 183, 107],\n \"darkmagenta\": [139, 0, 139],\n \"darkolivegreen\": [85, 107, 47],\n \"darkorange\": [255, 140, 0],\n \"darkorchid\": [153, 50, 204],\n \"darkred\": [139, 0, 0],\n \"darksalmon\": [233, 150, 122],\n \"darkseagreen\": [143, 188, 143],\n \"darkslateblue\": [72, 61, 139],\n \"darkslategray\": [47, 79, 79],\n \"darkslategrey\": [47, 79, 79],\n \"darkturquoise\": [0, 206, 209],\n \"darkviolet\": [148, 0, 211],\n \"deeppink\": [255, 20, 147],\n \"deepskyblue\": [0, 191, 255],\n \"dimgray\": [105, 105, 105],\n \"dimgrey\": [105, 105, 105],\n \"dodgerblue\": [30, 144, 255],\n \"firebrick\": [178, 34, 34],\n \"floralwhite\": [255, 250, 240],\n \"forestgreen\": [34, 139, 34],\n \"fuchsia\": [255, 0, 255],\n \"gainsboro\": [220, 220, 220],\n \"ghostwhite\": [248, 248, 255],\n \"gold\": [255, 215, 0],\n \"goldenrod\": [218, 165, 32],\n \"gray\": [128, 128, 128],\n \"green\": [0, 128, 0],\n \"greenyellow\": [173, 255, 47],\n \"grey\": [128, 128, 128],\n \"honeydew\": [240, 255, 240],\n \"hotpink\": [255, 105, 180],\n \"indianred\": [205, 92, 92],\n \"indigo\": [75, 0, 130],\n \"ivory\": [255, 255, 240],\n \"khaki\": [240, 230, 140],\n \"lavender\": [230, 230, 250],\n \"lavenderblush\": [255, 240, 245],\n \"lawngreen\": [124, 252, 0],\n \"lemonchiffon\": [255, 250, 205],\n \"lightblue\": [173, 216, 230],\n \"lightcoral\": [240, 128, 128],\n \"lightcyan\": [224, 255, 255],\n \"lightgoldenrodyellow\": [250, 250, 210],\n \"lightgray\": [211, 211, 211],\n \"lightgreen\": [144, 238, 144],\n \"lightgrey\": [211, 211, 211],\n \"lightpink\": [255, 182, 193],\n \"lightsalmon\": [255, 160, 122],\n \"lightseagreen\": [32, 178, 170],\n \"lightskyblue\": [135, 206, 250],\n \"lightslategray\": [119, 136, 153],\n \"lightslategrey\": [119, 136, 153],\n \"lightsteelblue\": [176, 196, 222],\n \"lightyellow\": [255, 255, 224],\n \"lime\": [0, 255, 0],\n \"limegreen\": [50, 205, 50],\n \"linen\": [250, 240, 230],\n \"magenta\": [255, 0, 255],\n \"maroon\": [128, 0, 0],\n \"mediumaquamarine\": [102, 205, 170],\n \"mediumblue\": [0, 0, 205],\n \"mediumorchid\": [186, 85, 211],\n \"mediumpurple\": [147, 112, 219],\n \"mediumseagreen\": [60, 179, 113],\n \"mediumslateblue\": [123, 104, 238],\n \"mediumspringgreen\": [0, 250, 154],\n \"mediumturquoise\": [72, 209, 204],\n \"mediumvioletred\": [199, 21, 133],\n \"midnightblue\": [25, 25, 112],\n \"mintcream\": [245, 255, 250],\n \"mistyrose\": [255, 228, 225],\n \"moccasin\": [255, 228, 181],\n \"navajowhite\": [255, 222, 173],\n \"navy\": [0, 0, 128],\n \"oldlace\": [253, 245, 230],\n \"olive\": [128, 128, 0],\n \"olivedrab\": [107, 142, 35],\n \"orange\": [255, 165, 0],\n \"orangered\": [255, 69, 0],\n \"orchid\": [218, 112, 214],\n \"palegoldenrod\": [238, 232, 170],\n \"palegreen\": [152, 251, 152],\n \"paleturquoise\": [175, 238, 238],\n \"palevioletred\": [219, 112, 147],\n \"papayawhip\": [255, 239, 213],\n \"peachpuff\": [255, 218, 185],\n \"peru\": [205, 133, 63],\n \"pink\": [255, 192, 203],\n \"plum\": [221, 160, 221],\n \"powderblue\": [176, 224, 230],\n \"purple\": [128, 0, 128],\n \"rebeccapurple\": [102, 51, 153],\n \"red\": [255, 0, 0],\n \"rosybrown\": [188, 143, 143],\n \"royalblue\": [65, 105, 225],\n \"saddlebrown\": [139, 69, 19],\n \"salmon\": [250, 128, 114],\n \"sandybrown\": [244, 164, 96],\n \"seagreen\": [46, 139, 87],\n \"seashell\": [255, 245, 238],\n \"sienna\": [160, 82, 45],\n \"silver\": [192, 192, 192],\n \"skyblue\": [135, 206, 235],\n \"slateblue\": [106, 90, 205],\n \"slategray\": [112, 128, 144],\n \"slategrey\": [112, 128, 144],\n \"snow\": [255, 250, 250],\n \"springgreen\": [0, 255, 127],\n \"steelblue\": [70, 130, 180],\n \"tan\": [210, 180, 140],\n \"teal\": [0, 128, 128],\n \"thistle\": [216, 191, 216],\n \"tomato\": [255, 99, 71],\n \"turquoise\": [64, 224, 208],\n \"violet\": [238, 130, 238],\n \"wheat\": [245, 222, 179],\n \"white\": [255, 255, 255],\n \"whitesmoke\": [245, 245, 245],\n \"yellow\": [255, 255, 0],\n \"yellowgreen\": [154, 205, 50]\n };\n var conversions = createCommonjsModule(function (module) {\n /* MIT license */\n // NOTE: conversions should only return primitive values (i.e. arrays, or\n // values that give correct `typeof` results).\n // do not use box values types (i.e. Number(), String(), etc.)\n var reverseKeywords = {};\n\n for (var key in colorName) {\n if (colorName.hasOwnProperty(key)) {\n reverseKeywords[colorName[key]] = key;\n }\n }\n\n var convert = module.exports = {\n rgb: {\n channels: 3,\n labels: 'rgb'\n },\n hsl: {\n channels: 3,\n labels: 'hsl'\n },\n hsv: {\n channels: 3,\n labels: 'hsv'\n },\n hwb: {\n channels: 3,\n labels: 'hwb'\n },\n cmyk: {\n channels: 4,\n labels: 'cmyk'\n },\n xyz: {\n channels: 3,\n labels: 'xyz'\n },\n lab: {\n channels: 3,\n labels: 'lab'\n },\n lch: {\n channels: 3,\n labels: 'lch'\n },\n hex: {\n channels: 1,\n labels: ['hex']\n },\n keyword: {\n channels: 1,\n labels: ['keyword']\n },\n ansi16: {\n channels: 1,\n labels: ['ansi16']\n },\n ansi256: {\n channels: 1,\n labels: ['ansi256']\n },\n hcg: {\n channels: 3,\n labels: ['h', 'c', 'g']\n },\n apple: {\n channels: 3,\n labels: ['r16', 'g16', 'b16']\n },\n gray: {\n channels: 1,\n labels: ['gray']\n }\n }; // hide .channels and .labels properties\n\n for (var model in convert) {\n if (convert.hasOwnProperty(model)) {\n if (!('channels' in convert[model])) {\n throw new Error('missing channels property: ' + model);\n }\n\n if (!('labels' in convert[model])) {\n throw new Error('missing channel labels property: ' + model);\n }\n\n if (convert[model].labels.length !== convert[model].channels) {\n throw new Error('channel and label counts mismatch: ' + model);\n }\n\n var channels = convert[model].channels;\n var labels = convert[model].labels;\n delete convert[model].channels;\n delete convert[model].labels;\n Object.defineProperty(convert[model], 'channels', {\n value: channels\n });\n Object.defineProperty(convert[model], 'labels', {\n value: labels\n });\n }\n }\n\n convert.rgb.hsl = function (rgb) {\n var r = rgb[0] / 255;\n var g = rgb[1] / 255;\n var b = rgb[2] / 255;\n var min = Math.min(r, g, b);\n var max = Math.max(r, g, b);\n var delta = max - min;\n var h;\n var s;\n var l;\n\n if (max === min) {\n h = 0;\n } else if (r === max) {\n h = (g - b) / delta;\n } else if (g === max) {\n h = 2 + (b - r) / delta;\n } else if (b === max) {\n h = 4 + (r - g) / delta;\n }\n\n h = Math.min(h * 60, 360);\n\n if (h < 0) {\n h += 360;\n }\n\n l = (min + max) / 2;\n\n if (max === min) {\n s = 0;\n } else if (l <= 0.5) {\n s = delta / (max + min);\n } else {\n s = delta / (2 - max - min);\n }\n\n return [h, s * 100, l * 100];\n };\n\n convert.rgb.hsv = function (rgb) {\n var rdif;\n var gdif;\n var bdif;\n var h;\n var s;\n var r = rgb[0] / 255;\n var g = rgb[1] / 255;\n var b = rgb[2] / 255;\n var v = Math.max(r, g, b);\n var diff = v - Math.min(r, g, b);\n\n var diffc = function (c) {\n return (v - c) / 6 / diff + 1 / 2;\n };\n\n if (diff === 0) {\n h = s = 0;\n } else {\n s = diff / v;\n rdif = diffc(r);\n gdif = diffc(g);\n bdif = diffc(b);\n\n if (r === v) {\n h = bdif - gdif;\n } else if (g === v) {\n h = 1 / 3 + rdif - bdif;\n } else if (b === v) {\n h = 2 / 3 + gdif - rdif;\n }\n\n if (h < 0) {\n h += 1;\n } else if (h > 1) {\n h -= 1;\n }\n }\n\n return [h * 360, s * 100, v * 100];\n };\n\n convert.rgb.hwb = function (rgb) {\n var r = rgb[0];\n var g = rgb[1];\n var b = rgb[2];\n var h = convert.rgb.hsl(rgb)[0];\n var w = 1 / 255 * Math.min(r, Math.min(g, b));\n b = 1 - 1 / 255 * Math.max(r, Math.max(g, b));\n return [h, w * 100, b * 100];\n };\n\n convert.rgb.cmyk = function (rgb) {\n var r = rgb[0] / 255;\n var g = rgb[1] / 255;\n var b = rgb[2] / 255;\n var c;\n var m;\n var y;\n var k;\n k = Math.min(1 - r, 1 - g, 1 - b);\n c = (1 - r - k) / (1 - k) || 0;\n m = (1 - g - k) / (1 - k) || 0;\n y = (1 - b - k) / (1 - k) || 0;\n return [c * 100, m * 100, y * 100, k * 100];\n };\n /**\n * See https://en.m.wikipedia.org/wiki/Euclidean_distance#Squared_Euclidean_distance\n * */\n\n\n function comparativeDistance(x, y) {\n return Math.pow(x[0] - y[0], 2) + Math.pow(x[1] - y[1], 2) + Math.pow(x[2] - y[2], 2);\n }\n\n convert.rgb.keyword = function (rgb) {\n var reversed = reverseKeywords[rgb];\n\n if (reversed) {\n return reversed;\n }\n\n var currentClosestDistance = Infinity;\n var currentClosestKeyword;\n\n for (var keyword in colorName) {\n if (colorName.hasOwnProperty(keyword)) {\n var value = colorName[keyword]; // Compute comparative distance\n\n var distance = comparativeDistance(rgb, value); // Check if its less, if so set as closest\n\n if (distance < currentClosestDistance) {\n currentClosestDistance = distance;\n currentClosestKeyword = keyword;\n }\n }\n }\n\n return currentClosestKeyword;\n };\n\n convert.keyword.rgb = function (keyword) {\n return colorName[keyword];\n };\n\n convert.rgb.xyz = function (rgb) {\n var r = rgb[0] / 255;\n var g = rgb[1] / 255;\n var b = rgb[2] / 255; // assume sRGB\n\n r = r > 0.04045 ? Math.pow((r + 0.055) / 1.055, 2.4) : r / 12.92;\n g = g > 0.04045 ? Math.pow((g + 0.055) / 1.055, 2.4) : g / 12.92;\n b = b > 0.04045 ? Math.pow((b + 0.055) / 1.055, 2.4) : b / 12.92;\n var x = r * 0.4124 + g * 0.3576 + b * 0.1805;\n var y = r * 0.2126 + g * 0.7152 + b * 0.0722;\n var z = r * 0.0193 + g * 0.1192 + b * 0.9505;\n return [x * 100, y * 100, z * 100];\n };\n\n convert.rgb.lab = function (rgb) {\n var xyz = convert.rgb.xyz(rgb);\n var x = xyz[0];\n var y = xyz[1];\n var z = xyz[2];\n var l;\n var a;\n var b;\n x /= 95.047;\n y /= 100;\n z /= 108.883;\n x = x > 0.008856 ? Math.pow(x, 1 / 3) : 7.787 * x + 16 / 116;\n y = y > 0.008856 ? Math.pow(y, 1 / 3) : 7.787 * y + 16 / 116;\n z = z > 0.008856 ? Math.pow(z, 1 / 3) : 7.787 * z + 16 / 116;\n l = 116 * y - 16;\n a = 500 * (x - y);\n b = 200 * (y - z);\n return [l, a, b];\n };\n\n convert.hsl.rgb = function (hsl) {\n var h = hsl[0] / 360;\n var s = hsl[1] / 100;\n var l = hsl[2] / 100;\n var t1;\n var t2;\n var t3;\n var rgb;\n var val;\n\n if (s === 0) {\n val = l * 255;\n return [val, val, val];\n }\n\n if (l < 0.5) {\n t2 = l * (1 + s);\n } else {\n t2 = l + s - l * s;\n }\n\n t1 = 2 * l - t2;\n rgb = [0, 0, 0];\n\n for (var i = 0; i < 3; i++) {\n t3 = h + 1 / 3 * -(i - 1);\n\n if (t3 < 0) {\n t3++;\n }\n\n if (t3 > 1) {\n t3--;\n }\n\n if (6 * t3 < 1) {\n val = t1 + (t2 - t1) * 6 * t3;\n } else if (2 * t3 < 1) {\n val = t2;\n } else if (3 * t3 < 2) {\n val = t1 + (t2 - t1) * (2 / 3 - t3) * 6;\n } else {\n val = t1;\n }\n\n rgb[i] = val * 255;\n }\n\n return rgb;\n };\n\n convert.hsl.hsv = function (hsl) {\n var h = hsl[0];\n var s = hsl[1] / 100;\n var l = hsl[2] / 100;\n var smin = s;\n var lmin = Math.max(l, 0.01);\n var sv;\n var v;\n l *= 2;\n s *= l <= 1 ? l : 2 - l;\n smin *= lmin <= 1 ? lmin : 2 - lmin;\n v = (l + s) / 2;\n sv = l === 0 ? 2 * smin / (lmin + smin) : 2 * s / (l + s);\n return [h, sv * 100, v * 100];\n };\n\n convert.hsv.rgb = function (hsv) {\n var h = hsv[0] / 60;\n var s = hsv[1] / 100;\n var v = hsv[2] / 100;\n var hi = Math.floor(h) % 6;\n var f = h - Math.floor(h);\n var p = 255 * v * (1 - s);\n var q = 255 * v * (1 - s * f);\n var t = 255 * v * (1 - s * (1 - f));\n v *= 255;\n\n switch (hi) {\n case 0:\n return [v, t, p];\n\n case 1:\n return [q, v, p];\n\n case 2:\n return [p, v, t];\n\n case 3:\n return [p, q, v];\n\n case 4:\n return [t, p, v];\n\n case 5:\n return [v, p, q];\n }\n };\n\n convert.hsv.hsl = function (hsv) {\n var h = hsv[0];\n var s = hsv[1] / 100;\n var v = hsv[2] / 100;\n var vmin = Math.max(v, 0.01);\n var lmin;\n var sl;\n var l;\n l = (2 - s) * v;\n lmin = (2 - s) * vmin;\n sl = s * vmin;\n sl /= lmin <= 1 ? lmin : 2 - lmin;\n sl = sl || 0;\n l /= 2;\n return [h, sl * 100, l * 100];\n }; // http://dev.w3.org/csswg/css-color/#hwb-to-rgb\n\n\n convert.hwb.rgb = function (hwb) {\n var h = hwb[0] / 360;\n var wh = hwb[1] / 100;\n var bl = hwb[2] / 100;\n var ratio = wh + bl;\n var i;\n var v;\n var f;\n var n; // wh + bl cant be > 1\n\n if (ratio > 1) {\n wh /= ratio;\n bl /= ratio;\n }\n\n i = Math.floor(6 * h);\n v = 1 - bl;\n f = 6 * h - i;\n\n if ((i & 0x01) !== 0) {\n f = 1 - f;\n }\n\n n = wh + f * (v - wh); // linear interpolation\n\n var r;\n var g;\n var b;\n\n switch (i) {\n default:\n case 6:\n case 0:\n r = v;\n g = n;\n b = wh;\n break;\n\n case 1:\n r = n;\n g = v;\n b = wh;\n break;\n\n case 2:\n r = wh;\n g = v;\n b = n;\n break;\n\n case 3:\n r = wh;\n g = n;\n b = v;\n break;\n\n case 4:\n r = n;\n g = wh;\n b = v;\n break;\n\n case 5:\n r = v;\n g = wh;\n b = n;\n break;\n }\n\n return [r * 255, g * 255, b * 255];\n };\n\n convert.cmyk.rgb = function (cmyk) {\n var c = cmyk[0] / 100;\n var m = cmyk[1] / 100;\n var y = cmyk[2] / 100;\n var k = cmyk[3] / 100;\n var r;\n var g;\n var b;\n r = 1 - Math.min(1, c * (1 - k) + k);\n g = 1 - Math.min(1, m * (1 - k) + k);\n b = 1 - Math.min(1, y * (1 - k) + k);\n return [r * 255, g * 255, b * 255];\n };\n\n convert.xyz.rgb = function (xyz) {\n var x = xyz[0] / 100;\n var y = xyz[1] / 100;\n var z = xyz[2] / 100;\n var r;\n var g;\n var b;\n r = x * 3.2406 + y * -1.5372 + z * -0.4986;\n g = x * -0.9689 + y * 1.8758 + z * 0.0415;\n b = x * 0.0557 + y * -0.2040 + z * 1.0570; // assume sRGB\n\n r = r > 0.0031308 ? 1.055 * Math.pow(r, 1.0 / 2.4) - 0.055 : r * 12.92;\n g = g > 0.0031308 ? 1.055 * Math.pow(g, 1.0 / 2.4) - 0.055 : g * 12.92;\n b = b > 0.0031308 ? 1.055 * Math.pow(b, 1.0 / 2.4) - 0.055 : b * 12.92;\n r = Math.min(Math.max(0, r), 1);\n g = Math.min(Math.max(0, g), 1);\n b = Math.min(Math.max(0, b), 1);\n return [r * 255, g * 255, b * 255];\n };\n\n convert.xyz.lab = function (xyz) {\n var x = xyz[0];\n var y = xyz[1];\n var z = xyz[2];\n var l;\n var a;\n var b;\n x /= 95.047;\n y /= 100;\n z /= 108.883;\n x = x > 0.008856 ? Math.pow(x, 1 / 3) : 7.787 * x + 16 / 116;\n y = y > 0.008856 ? Math.pow(y, 1 / 3) : 7.787 * y + 16 / 116;\n z = z > 0.008856 ? Math.pow(z, 1 / 3) : 7.787 * z + 16 / 116;\n l = 116 * y - 16;\n a = 500 * (x - y);\n b = 200 * (y - z);\n return [l, a, b];\n };\n\n convert.lab.xyz = function (lab) {\n var l = lab[0];\n var a = lab[1];\n var b = lab[2];\n var x;\n var y;\n var z;\n y = (l + 16) / 116;\n x = a / 500 + y;\n z = y - b / 200;\n var y2 = Math.pow(y, 3);\n var x2 = Math.pow(x, 3);\n var z2 = Math.pow(z, 3);\n y = y2 > 0.008856 ? y2 : (y - 16 / 116) / 7.787;\n x = x2 > 0.008856 ? x2 : (x - 16 / 116) / 7.787;\n z = z2 > 0.008856 ? z2 : (z - 16 / 116) / 7.787;\n x *= 95.047;\n y *= 100;\n z *= 108.883;\n return [x, y, z];\n };\n\n convert.lab.lch = function (lab) {\n var l = lab[0];\n var a = lab[1];\n var b = lab[2];\n var hr;\n var h;\n var c;\n hr = Math.atan2(b, a);\n h = hr * 360 / 2 / Math.PI;\n\n if (h < 0) {\n h += 360;\n }\n\n c = Math.sqrt(a * a + b * b);\n return [l, c, h];\n };\n\n convert.lch.lab = function (lch) {\n var l = lch[0];\n var c = lch[1];\n var h = lch[2];\n var a;\n var b;\n var hr;\n hr = h / 360 * 2 * Math.PI;\n a = c * Math.cos(hr);\n b = c * Math.sin(hr);\n return [l, a, b];\n };\n\n convert.rgb.ansi16 = function (args) {\n var r = args[0];\n var g = args[1];\n var b = args[2];\n var value = 1 in arguments ? arguments[1] : convert.rgb.hsv(args)[2]; // hsv -> ansi16 optimization\n\n value = Math.round(value / 50);\n\n if (value === 0) {\n return 30;\n }\n\n var ansi = 30 + (Math.round(b / 255) << 2 | Math.round(g / 255) << 1 | Math.round(r / 255));\n\n if (value === 2) {\n ansi += 60;\n }\n\n return ansi;\n };\n\n convert.hsv.ansi16 = function (args) {\n // optimization here; we already know the value and don't need to get\n // it converted for us.\n return convert.rgb.ansi16(convert.hsv.rgb(args), args[2]);\n };\n\n convert.rgb.ansi256 = function (args) {\n var r = args[0];\n var g = args[1];\n var b = args[2]; // we use the extended greyscale palette here, with the exception of\n // black and white. normal palette only has 4 greyscale shades.\n\n if (r === g && g === b) {\n if (r < 8) {\n return 16;\n }\n\n if (r > 248) {\n return 231;\n }\n\n return Math.round((r - 8) / 247 * 24) + 232;\n }\n\n var ansi = 16 + 36 * Math.round(r / 255 * 5) + 6 * Math.round(g / 255 * 5) + Math.round(b / 255 * 5);\n return ansi;\n };\n\n convert.ansi16.rgb = function (args) {\n var color = args % 10; // handle greyscale\n\n if (color === 0 || color === 7) {\n if (args > 50) {\n color += 3.5;\n }\n\n color = color / 10.5 * 255;\n return [color, color, color];\n }\n\n var mult = (~~(args > 50) + 1) * 0.5;\n var r = (color & 1) * mult * 255;\n var g = (color >> 1 & 1) * mult * 255;\n var b = (color >> 2 & 1) * mult * 255;\n return [r, g, b];\n };\n\n convert.ansi256.rgb = function (args) {\n // handle greyscale\n if (args >= 232) {\n var c = (args - 232) * 10 + 8;\n return [c, c, c];\n }\n\n args -= 16;\n var rem;\n var r = Math.floor(args / 36) / 5 * 255;\n var g = Math.floor((rem = args % 36) / 6) / 5 * 255;\n var b = rem % 6 / 5 * 255;\n return [r, g, b];\n };\n\n convert.rgb.hex = function (args) {\n var integer = ((Math.round(args[0]) & 0xFF) << 16) + ((Math.round(args[1]) & 0xFF) << 8) + (Math.round(args[2]) & 0xFF);\n var string = integer.toString(16).toUpperCase();\n return '000000'.substring(string.length) + string;\n };\n\n convert.hex.rgb = function (args) {\n var match = args.toString(16).match(/[a-f0-9]{6}|[a-f0-9]{3}/i);\n\n if (!match) {\n return [0, 0, 0];\n }\n\n var colorString = match[0];\n\n if (match[0].length === 3) {\n colorString = colorString.split('').map(function (char) {\n return char + char;\n }).join('');\n }\n\n var integer = parseInt(colorString, 16);\n var r = integer >> 16 & 0xFF;\n var g = integer >> 8 & 0xFF;\n var b = integer & 0xFF;\n return [r, g, b];\n };\n\n convert.rgb.hcg = function (rgb) {\n var r = rgb[0] / 255;\n var g = rgb[1] / 255;\n var b = rgb[2] / 255;\n var max = Math.max(Math.max(r, g), b);\n var min = Math.min(Math.min(r, g), b);\n var chroma = max - min;\n var grayscale;\n var hue;\n\n if (chroma < 1) {\n grayscale = min / (1 - chroma);\n } else {\n grayscale = 0;\n }\n\n if (chroma <= 0) {\n hue = 0;\n } else if (max === r) {\n hue = (g - b) / chroma % 6;\n } else if (max === g) {\n hue = 2 + (b - r) / chroma;\n } else {\n hue = 4 + (r - g) / chroma + 4;\n }\n\n hue /= 6;\n hue %= 1;\n return [hue * 360, chroma * 100, grayscale * 100];\n };\n\n convert.hsl.hcg = function (hsl) {\n var s = hsl[1] / 100;\n var l = hsl[2] / 100;\n var c = 1;\n var f = 0;\n\n if (l < 0.5) {\n c = 2.0 * s * l;\n } else {\n c = 2.0 * s * (1.0 - l);\n }\n\n if (c < 1.0) {\n f = (l - 0.5 * c) / (1.0 - c);\n }\n\n return [hsl[0], c * 100, f * 100];\n };\n\n convert.hsv.hcg = function (hsv) {\n var s = hsv[1] / 100;\n var v = hsv[2] / 100;\n var c = s * v;\n var f = 0;\n\n if (c < 1.0) {\n f = (v - c) / (1 - c);\n }\n\n return [hsv[0], c * 100, f * 100];\n };\n\n convert.hcg.rgb = function (hcg) {\n var h = hcg[0] / 360;\n var c = hcg[1] / 100;\n var g = hcg[2] / 100;\n\n if (c === 0.0) {\n return [g * 255, g * 255, g * 255];\n }\n\n var pure = [0, 0, 0];\n var hi = h % 1 * 6;\n var v = hi % 1;\n var w = 1 - v;\n var mg = 0;\n\n switch (Math.floor(hi)) {\n case 0:\n pure[0] = 1;\n pure[1] = v;\n pure[2] = 0;\n break;\n\n case 1:\n pure[0] = w;\n pure[1] = 1;\n pure[2] = 0;\n break;\n\n case 2:\n pure[0] = 0;\n pure[1] = 1;\n pure[2] = v;\n break;\n\n case 3:\n pure[0] = 0;\n pure[1] = w;\n pure[2] = 1;\n break;\n\n case 4:\n pure[0] = v;\n pure[1] = 0;\n pure[2] = 1;\n break;\n\n default:\n pure[0] = 1;\n pure[1] = 0;\n pure[2] = w;\n }\n\n mg = (1.0 - c) * g;\n return [(c * pure[0] + mg) * 255, (c * pure[1] + mg) * 255, (c * pure[2] + mg) * 255];\n };\n\n convert.hcg.hsv = function (hcg) {\n var c = hcg[1] / 100;\n var g = hcg[2] / 100;\n var v = c + g * (1.0 - c);\n var f = 0;\n\n if (v > 0.0) {\n f = c / v;\n }\n\n return [hcg[0], f * 100, v * 100];\n };\n\n convert.hcg.hsl = function (hcg) {\n var c = hcg[1] / 100;\n var g = hcg[2] / 100;\n var l = g * (1.0 - c) + 0.5 * c;\n var s = 0;\n\n if (l > 0.0 && l < 0.5) {\n s = c / (2 * l);\n } else if (l >= 0.5 && l < 1.0) {\n s = c / (2 * (1 - l));\n }\n\n return [hcg[0], s * 100, l * 100];\n };\n\n convert.hcg.hwb = function (hcg) {\n var c = hcg[1] / 100;\n var g = hcg[2] / 100;\n var v = c + g * (1.0 - c);\n return [hcg[0], (v - c) * 100, (1 - v) * 100];\n };\n\n convert.hwb.hcg = function (hwb) {\n var w = hwb[1] / 100;\n var b = hwb[2] / 100;\n var v = 1 - b;\n var c = v - w;\n var g = 0;\n\n if (c < 1) {\n g = (v - c) / (1 - c);\n }\n\n return [hwb[0], c * 100, g * 100];\n };\n\n convert.apple.rgb = function (apple) {\n return [apple[0] / 65535 * 255, apple[1] / 65535 * 255, apple[2] / 65535 * 255];\n };\n\n convert.rgb.apple = function (rgb) {\n return [rgb[0] / 255 * 65535, rgb[1] / 255 * 65535, rgb[2] / 255 * 65535];\n };\n\n convert.gray.rgb = function (args) {\n return [args[0] / 100 * 255, args[0] / 100 * 255, args[0] / 100 * 255];\n };\n\n convert.gray.hsl = convert.gray.hsv = function (args) {\n return [0, 0, args[0]];\n };\n\n convert.gray.hwb = function (gray) {\n return [0, 100, gray[0]];\n };\n\n convert.gray.cmyk = function (gray) {\n return [0, 0, 0, gray[0]];\n };\n\n convert.gray.lab = function (gray) {\n return [gray[0], 0, 0];\n };\n\n convert.gray.hex = function (gray) {\n var val = Math.round(gray[0] / 100 * 255) & 0xFF;\n var integer = (val << 16) + (val << 8) + val;\n var string = integer.toString(16).toUpperCase();\n return '000000'.substring(string.length) + string;\n };\n\n convert.rgb.gray = function (rgb) {\n var val = (rgb[0] + rgb[1] + rgb[2]) / 3;\n return [val / 255 * 100];\n };\n });\n var conversions_1 = conversions.rgb;\n var conversions_2 = conversions.hsl;\n var conversions_3 = conversions.hsv;\n var conversions_4 = conversions.hwb;\n var conversions_5 = conversions.cmyk;\n var conversions_6 = conversions.xyz;\n var conversions_7 = conversions.lab;\n var conversions_8 = conversions.lch;\n var conversions_9 = conversions.hex;\n var conversions_10 = conversions.keyword;\n var conversions_11 = conversions.ansi16;\n var conversions_12 = conversions.ansi256;\n var conversions_13 = conversions.hcg;\n var conversions_14 = conversions.apple;\n var conversions_15 = conversions.gray;\n /*\n \tthis function routes a model to all other models.\n \n \tall functions that are routed have a property `.conversion` attached\n \tto the returned synthetic function. This property is an array\n \tof strings, each with the steps in between the 'from' and 'to'\n \tcolor models (inclusive).\n \n \tconversions that are not possible simply are not included.\n */\n\n function buildGraph() {\n var graph = {}; // https://jsperf.com/object-keys-vs-for-in-with-closure/3\n\n var models = Object.keys(conversions);\n\n for (var len = models.length, i = 0; i < len; i++) {\n graph[models[i]] = {\n // http://jsperf.com/1-vs-infinity\n // micro-opt, but this is simple.\n distance: -1,\n parent: null\n };\n }\n\n return graph;\n } // https://en.wikipedia.org/wiki/Breadth-first_search\n\n\n function deriveBFS(fromModel) {\n var graph = buildGraph();\n var queue = [fromModel]; // unshift -> queue -> pop\n\n graph[fromModel].distance = 0;\n\n while (queue.length) {\n var current = queue.pop();\n var adjacents = Object.keys(conversions[current]);\n\n for (var len = adjacents.length, i = 0; i < len; i++) {\n var adjacent = adjacents[i];\n var node = graph[adjacent];\n\n if (node.distance === -1) {\n node.distance = graph[current].distance + 1;\n node.parent = current;\n queue.unshift(adjacent);\n }\n }\n }\n\n return graph;\n }\n\n function link(from, to) {\n return function (args) {\n return to(from(args));\n };\n }\n\n function wrapConversion(toModel, graph) {\n var path = [graph[toModel].parent, toModel];\n var fn = conversions[graph[toModel].parent][toModel];\n var cur = graph[toModel].parent;\n\n while (graph[cur].parent) {\n path.unshift(graph[cur].parent);\n fn = link(conversions[graph[cur].parent][cur], fn);\n cur = graph[cur].parent;\n }\n\n fn.conversion = path;\n return fn;\n }\n\n var route = function (fromModel) {\n var graph = deriveBFS(fromModel);\n var conversion = {};\n var models = Object.keys(graph);\n\n for (var len = models.length, i = 0; i < len; i++) {\n var toModel = models[i];\n var node = graph[toModel];\n\n if (node.parent === null) {\n // no possible conversion, or this node is the source model.\n continue;\n }\n\n conversion[toModel] = wrapConversion(toModel, graph);\n }\n\n return conversion;\n };\n\n var convert = {};\n var models = Object.keys(conversions);\n\n function wrapRaw(fn) {\n var wrappedFn = function (args) {\n if (args === undefined || args === null) {\n return args;\n }\n\n if (arguments.length > 1) {\n args = Array.prototype.slice.call(arguments);\n }\n\n return fn(args);\n }; // preserve .conversion property if there is one\n\n\n if ('conversion' in fn) {\n wrappedFn.conversion = fn.conversion;\n }\n\n return wrappedFn;\n }\n\n function wrapRounded(fn) {\n var wrappedFn = function (args) {\n if (args === undefined || args === null) {\n return args;\n }\n\n if (arguments.length > 1) {\n args = Array.prototype.slice.call(arguments);\n }\n\n var result = fn(args); // we're assuming the result is an array here.\n // see notice in conversions.js; don't use box types\n // in conversion functions.\n\n if (typeof result === 'object') {\n for (var len = result.length, i = 0; i < len; i++) {\n result[i] = Math.round(result[i]);\n }\n }\n\n return result;\n }; // preserve .conversion property if there is one\n\n\n if ('conversion' in fn) {\n wrappedFn.conversion = fn.conversion;\n }\n\n return wrappedFn;\n }\n\n models.forEach(function (fromModel) {\n convert[fromModel] = {};\n Object.defineProperty(convert[fromModel], 'channels', {\n value: conversions[fromModel].channels\n });\n Object.defineProperty(convert[fromModel], 'labels', {\n value: conversions[fromModel].labels\n });\n var routes = route(fromModel);\n var routeModels = Object.keys(routes);\n routeModels.forEach(function (toModel) {\n var fn = routes[toModel];\n convert[fromModel][toModel] = wrapRounded(fn);\n convert[fromModel][toModel].raw = wrapRaw(fn);\n });\n });\n var colorConvert = convert;\n var colorName$1 = {\n \"aliceblue\": [240, 248, 255],\n \"antiquewhite\": [250, 235, 215],\n \"aqua\": [0, 255, 255],\n \"aquamarine\": [127, 255, 212],\n \"azure\": [240, 255, 255],\n \"beige\": [245, 245, 220],\n \"bisque\": [255, 228, 196],\n \"black\": [0, 0, 0],\n \"blanchedalmond\": [255, 235, 205],\n \"blue\": [0, 0, 255],\n \"blueviolet\": [138, 43, 226],\n \"brown\": [165, 42, 42],\n \"burlywood\": [222, 184, 135],\n \"cadetblue\": [95, 158, 160],\n \"chartreuse\": [127, 255, 0],\n \"chocolate\": [210, 105, 30],\n \"coral\": [255, 127, 80],\n \"cornflowerblue\": [100, 149, 237],\n \"cornsilk\": [255, 248, 220],\n \"crimson\": [220, 20, 60],\n \"cyan\": [0, 255, 255],\n \"darkblue\": [0, 0, 139],\n \"darkcyan\": [0, 139, 139],\n \"darkgoldenrod\": [184, 134, 11],\n \"darkgray\": [169, 169, 169],\n \"darkgreen\": [0, 100, 0],\n \"darkgrey\": [169, 169, 169],\n \"darkkhaki\": [189, 183, 107],\n \"darkmagenta\": [139, 0, 139],\n \"darkolivegreen\": [85, 107, 47],\n \"darkorange\": [255, 140, 0],\n \"darkorchid\": [153, 50, 204],\n \"darkred\": [139, 0, 0],\n \"darksalmon\": [233, 150, 122],\n \"darkseagreen\": [143, 188, 143],\n \"darkslateblue\": [72, 61, 139],\n \"darkslategray\": [47, 79, 79],\n \"darkslategrey\": [47, 79, 79],\n \"darkturquoise\": [0, 206, 209],\n \"darkviolet\": [148, 0, 211],\n \"deeppink\": [255, 20, 147],\n \"deepskyblue\": [0, 191, 255],\n \"dimgray\": [105, 105, 105],\n \"dimgrey\": [105, 105, 105],\n \"dodgerblue\": [30, 144, 255],\n \"firebrick\": [178, 34, 34],\n \"floralwhite\": [255, 250, 240],\n \"forestgreen\": [34, 139, 34],\n \"fuchsia\": [255, 0, 255],\n \"gainsboro\": [220, 220, 220],\n \"ghostwhite\": [248, 248, 255],\n \"gold\": [255, 215, 0],\n \"goldenrod\": [218, 165, 32],\n \"gray\": [128, 128, 128],\n \"green\": [0, 128, 0],\n \"greenyellow\": [173, 255, 47],\n \"grey\": [128, 128, 128],\n \"honeydew\": [240, 255, 240],\n \"hotpink\": [255, 105, 180],\n \"indianred\": [205, 92, 92],\n \"indigo\": [75, 0, 130],\n \"ivory\": [255, 255, 240],\n \"khaki\": [240, 230, 140],\n \"lavender\": [230, 230, 250],\n \"lavenderblush\": [255, 240, 245],\n \"lawngreen\": [124, 252, 0],\n \"lemonchiffon\": [255, 250, 205],\n \"lightblue\": [173, 216, 230],\n \"lightcoral\": [240, 128, 128],\n \"lightcyan\": [224, 255, 255],\n \"lightgoldenrodyellow\": [250, 250, 210],\n \"lightgray\": [211, 211, 211],\n \"lightgreen\": [144, 238, 144],\n \"lightgrey\": [211, 211, 211],\n \"lightpink\": [255, 182, 193],\n \"lightsalmon\": [255, 160, 122],\n \"lightseagreen\": [32, 178, 170],\n \"lightskyblue\": [135, 206, 250],\n \"lightslategray\": [119, 136, 153],\n \"lightslategrey\": [119, 136, 153],\n \"lightsteelblue\": [176, 196, 222],\n \"lightyellow\": [255, 255, 224],\n \"lime\": [0, 255, 0],\n \"limegreen\": [50, 205, 50],\n \"linen\": [250, 240, 230],\n \"magenta\": [255, 0, 255],\n \"maroon\": [128, 0, 0],\n \"mediumaquamarine\": [102, 205, 170],\n \"mediumblue\": [0, 0, 205],\n \"mediumorchid\": [186, 85, 211],\n \"mediumpurple\": [147, 112, 219],\n \"mediumseagreen\": [60, 179, 113],\n \"mediumslateblue\": [123, 104, 238],\n \"mediumspringgreen\": [0, 250, 154],\n \"mediumturquoise\": [72, 209, 204],\n \"mediumvioletred\": [199, 21, 133],\n \"midnightblue\": [25, 25, 112],\n \"mintcream\": [245, 255, 250],\n \"mistyrose\": [255, 228, 225],\n \"moccasin\": [255, 228, 181],\n \"navajowhite\": [255, 222, 173],\n \"navy\": [0, 0, 128],\n \"oldlace\": [253, 245, 230],\n \"olive\": [128, 128, 0],\n \"olivedrab\": [107, 142, 35],\n \"orange\": [255, 165, 0],\n \"orangered\": [255, 69, 0],\n \"orchid\": [218, 112, 214],\n \"palegoldenrod\": [238, 232, 170],\n \"palegreen\": [152, 251, 152],\n \"paleturquoise\": [175, 238, 238],\n \"palevioletred\": [219, 112, 147],\n \"papayawhip\": [255, 239, 213],\n \"peachpuff\": [255, 218, 185],\n \"peru\": [205, 133, 63],\n \"pink\": [255, 192, 203],\n \"plum\": [221, 160, 221],\n \"powderblue\": [176, 224, 230],\n \"purple\": [128, 0, 128],\n \"rebeccapurple\": [102, 51, 153],\n \"red\": [255, 0, 0],\n \"rosybrown\": [188, 143, 143],\n \"royalblue\": [65, 105, 225],\n \"saddlebrown\": [139, 69, 19],\n \"salmon\": [250, 128, 114],\n \"sandybrown\": [244, 164, 96],\n \"seagreen\": [46, 139, 87],\n \"seashell\": [255, 245, 238],\n \"sienna\": [160, 82, 45],\n \"silver\": [192, 192, 192],\n \"skyblue\": [135, 206, 235],\n \"slateblue\": [106, 90, 205],\n \"slategray\": [112, 128, 144],\n \"slategrey\": [112, 128, 144],\n \"snow\": [255, 250, 250],\n \"springgreen\": [0, 255, 127],\n \"steelblue\": [70, 130, 180],\n \"tan\": [210, 180, 140],\n \"teal\": [0, 128, 128],\n \"thistle\": [216, 191, 216],\n \"tomato\": [255, 99, 71],\n \"turquoise\": [64, 224, 208],\n \"violet\": [238, 130, 238],\n \"wheat\": [245, 222, 179],\n \"white\": [255, 255, 255],\n \"whitesmoke\": [245, 245, 245],\n \"yellow\": [255, 255, 0],\n \"yellowgreen\": [154, 205, 50]\n };\n /* MIT license */\n\n var colorString = {\n getRgba: getRgba,\n getHsla: getHsla,\n getRgb: getRgb,\n getHsl: getHsl,\n getHwb: getHwb,\n getAlpha: getAlpha,\n hexString: hexString,\n rgbString: rgbString,\n rgbaString: rgbaString,\n percentString: percentString,\n percentaString: percentaString,\n hslString: hslString,\n hslaString: hslaString,\n hwbString: hwbString,\n keyword: keyword\n };\n\n function getRgba(string) {\n if (!string) {\n return;\n }\n\n var abbr = /^#([a-fA-F0-9]{3,4})$/i,\n hex = /^#([a-fA-F0-9]{6}([a-fA-F0-9]{2})?)$/i,\n rgba = /^rgba?\\(\\s*([+-]?\\d+)\\s*,\\s*([+-]?\\d+)\\s*,\\s*([+-]?\\d+)\\s*(?:,\\s*([+-]?[\\d\\.]+)\\s*)?\\)$/i,\n per = /^rgba?\\(\\s*([+-]?[\\d\\.]+)\\%\\s*,\\s*([+-]?[\\d\\.]+)\\%\\s*,\\s*([+-]?[\\d\\.]+)\\%\\s*(?:,\\s*([+-]?[\\d\\.]+)\\s*)?\\)$/i,\n keyword = /(\\w+)/;\n var rgb = [0, 0, 0],\n a = 1,\n match = string.match(abbr),\n hexAlpha = \"\";\n\n if (match) {\n match = match[1];\n hexAlpha = match[3];\n\n for (var i = 0; i < rgb.length; i++) {\n rgb[i] = parseInt(match[i] + match[i], 16);\n }\n\n if (hexAlpha) {\n a = Math.round(parseInt(hexAlpha + hexAlpha, 16) / 255 * 100) / 100;\n }\n } else if (match = string.match(hex)) {\n hexAlpha = match[2];\n match = match[1];\n\n for (var i = 0; i < rgb.length; i++) {\n rgb[i] = parseInt(match.slice(i * 2, i * 2 + 2), 16);\n }\n\n if (hexAlpha) {\n a = Math.round(parseInt(hexAlpha, 16) / 255 * 100) / 100;\n }\n } else if (match = string.match(rgba)) {\n for (var i = 0; i < rgb.length; i++) {\n rgb[i] = parseInt(match[i + 1]);\n }\n\n a = parseFloat(match[4]);\n } else if (match = string.match(per)) {\n for (var i = 0; i < rgb.length; i++) {\n rgb[i] = Math.round(parseFloat(match[i + 1]) * 2.55);\n }\n\n a = parseFloat(match[4]);\n } else if (match = string.match(keyword)) {\n if (match[1] == \"transparent\") {\n return [0, 0, 0, 0];\n }\n\n rgb = colorName$1[match[1]];\n\n if (!rgb) {\n return;\n }\n }\n\n for (var i = 0; i < rgb.length; i++) {\n rgb[i] = scale(rgb[i], 0, 255);\n }\n\n if (!a && a != 0) {\n a = 1;\n } else {\n a = scale(a, 0, 1);\n }\n\n rgb[3] = a;\n return rgb;\n }\n\n function getHsla(string) {\n if (!string) {\n return;\n }\n\n var hsl = /^hsla?\\(\\s*([+-]?\\d+)(?:deg)?\\s*,\\s*([+-]?[\\d\\.]+)%\\s*,\\s*([+-]?[\\d\\.]+)%\\s*(?:,\\s*([+-]?[\\d\\.]+)\\s*)?\\)/;\n var match = string.match(hsl);\n\n if (match) {\n var alpha = parseFloat(match[4]);\n var h = scale(parseInt(match[1]), 0, 360),\n s = scale(parseFloat(match[2]), 0, 100),\n l = scale(parseFloat(match[3]), 0, 100),\n a = scale(isNaN(alpha) ? 1 : alpha, 0, 1);\n return [h, s, l, a];\n }\n }\n\n function getHwb(string) {\n if (!string) {\n return;\n }\n\n var hwb = /^hwb\\(\\s*([+-]?\\d+)(?:deg)?\\s*,\\s*([+-]?[\\d\\.]+)%\\s*,\\s*([+-]?[\\d\\.]+)%\\s*(?:,\\s*([+-]?[\\d\\.]+)\\s*)?\\)/;\n var match = string.match(hwb);\n\n if (match) {\n var alpha = parseFloat(match[4]);\n var h = scale(parseInt(match[1]), 0, 360),\n w = scale(parseFloat(match[2]), 0, 100),\n b = scale(parseFloat(match[3]), 0, 100),\n a = scale(isNaN(alpha) ? 1 : alpha, 0, 1);\n return [h, w, b, a];\n }\n }\n\n function getRgb(string) {\n var rgba = getRgba(string);\n return rgba && rgba.slice(0, 3);\n }\n\n function getHsl(string) {\n var hsla = getHsla(string);\n return hsla && hsla.slice(0, 3);\n }\n\n function getAlpha(string) {\n var vals = getRgba(string);\n\n if (vals) {\n return vals[3];\n } else if (vals = getHsla(string)) {\n return vals[3];\n } else if (vals = getHwb(string)) {\n return vals[3];\n }\n } // generators\n\n\n function hexString(rgba, a) {\n var a = a !== undefined && rgba.length === 3 ? a : rgba[3];\n return \"#\" + hexDouble(rgba[0]) + hexDouble(rgba[1]) + hexDouble(rgba[2]) + (a >= 0 && a < 1 ? hexDouble(Math.round(a * 255)) : \"\");\n }\n\n function rgbString(rgba, alpha) {\n if (alpha < 1 || rgba[3] && rgba[3] < 1) {\n return rgbaString(rgba, alpha);\n }\n\n return \"rgb(\" + rgba[0] + \", \" + rgba[1] + \", \" + rgba[2] + \")\";\n }\n\n function rgbaString(rgba, alpha) {\n if (alpha === undefined) {\n alpha = rgba[3] !== undefined ? rgba[3] : 1;\n }\n\n return \"rgba(\" + rgba[0] + \", \" + rgba[1] + \", \" + rgba[2] + \", \" + alpha + \")\";\n }\n\n function percentString(rgba, alpha) {\n if (alpha < 1 || rgba[3] && rgba[3] < 1) {\n return percentaString(rgba, alpha);\n }\n\n var r = Math.round(rgba[0] / 255 * 100),\n g = Math.round(rgba[1] / 255 * 100),\n b = Math.round(rgba[2] / 255 * 100);\n return \"rgb(\" + r + \"%, \" + g + \"%, \" + b + \"%)\";\n }\n\n function percentaString(rgba, alpha) {\n var r = Math.round(rgba[0] / 255 * 100),\n g = Math.round(rgba[1] / 255 * 100),\n b = Math.round(rgba[2] / 255 * 100);\n return \"rgba(\" + r + \"%, \" + g + \"%, \" + b + \"%, \" + (alpha || rgba[3] || 1) + \")\";\n }\n\n function hslString(hsla, alpha) {\n if (alpha < 1 || hsla[3] && hsla[3] < 1) {\n return hslaString(hsla, alpha);\n }\n\n return \"hsl(\" + hsla[0] + \", \" + hsla[1] + \"%, \" + hsla[2] + \"%)\";\n }\n\n function hslaString(hsla, alpha) {\n if (alpha === undefined) {\n alpha = hsla[3] !== undefined ? hsla[3] : 1;\n }\n\n return \"hsla(\" + hsla[0] + \", \" + hsla[1] + \"%, \" + hsla[2] + \"%, \" + alpha + \")\";\n } // hwb is a bit different than rgb(a) & hsl(a) since there is no alpha specific syntax\n // (hwb have alpha optional & 1 is default value)\n\n\n function hwbString(hwb, alpha) {\n if (alpha === undefined) {\n alpha = hwb[3] !== undefined ? hwb[3] : 1;\n }\n\n return \"hwb(\" + hwb[0] + \", \" + hwb[1] + \"%, \" + hwb[2] + \"%\" + (alpha !== undefined && alpha !== 1 ? \", \" + alpha : \"\") + \")\";\n }\n\n function keyword(rgb) {\n return reverseNames[rgb.slice(0, 3)];\n } // helpers\n\n\n function scale(num, min, max) {\n return Math.min(Math.max(min, num), max);\n }\n\n function hexDouble(num) {\n var str = num.toString(16).toUpperCase();\n return str.length < 2 ? \"0\" + str : str;\n } //create a list of reverse color names\n\n\n var reverseNames = {};\n\n for (var name in colorName$1) {\n reverseNames[colorName$1[name]] = name;\n }\n /* MIT license */\n\n\n var Color = function (obj) {\n if (obj instanceof Color) {\n return obj;\n }\n\n if (!(this instanceof Color)) {\n return new Color(obj);\n }\n\n this.valid = false;\n this.values = {\n rgb: [0, 0, 0],\n hsl: [0, 0, 0],\n hsv: [0, 0, 0],\n hwb: [0, 0, 0],\n cmyk: [0, 0, 0, 0],\n alpha: 1\n }; // parse Color() argument\n\n var vals;\n\n if (typeof obj === 'string') {\n vals = colorString.getRgba(obj);\n\n if (vals) {\n this.setValues('rgb', vals);\n } else if (vals = colorString.getHsla(obj)) {\n this.setValues('hsl', vals);\n } else if (vals = colorString.getHwb(obj)) {\n this.setValues('hwb', vals);\n }\n } else if (typeof obj === 'object') {\n vals = obj;\n\n if (vals.r !== undefined || vals.red !== undefined) {\n this.setValues('rgb', vals);\n } else if (vals.l !== undefined || vals.lightness !== undefined) {\n this.setValues('hsl', vals);\n } else if (vals.v !== undefined || vals.value !== undefined) {\n this.setValues('hsv', vals);\n } else if (vals.w !== undefined || vals.whiteness !== undefined) {\n this.setValues('hwb', vals);\n } else if (vals.c !== undefined || vals.cyan !== undefined) {\n this.setValues('cmyk', vals);\n }\n }\n };\n\n Color.prototype = {\n isValid: function () {\n return this.valid;\n },\n rgb: function () {\n return this.setSpace('rgb', arguments);\n },\n hsl: function () {\n return this.setSpace('hsl', arguments);\n },\n hsv: function () {\n return this.setSpace('hsv', arguments);\n },\n hwb: function () {\n return this.setSpace('hwb', arguments);\n },\n cmyk: function () {\n return this.setSpace('cmyk', arguments);\n },\n rgbArray: function () {\n return this.values.rgb;\n },\n hslArray: function () {\n return this.values.hsl;\n },\n hsvArray: function () {\n return this.values.hsv;\n },\n hwbArray: function () {\n var values = this.values;\n\n if (values.alpha !== 1) {\n return values.hwb.concat([values.alpha]);\n }\n\n return values.hwb;\n },\n cmykArray: function () {\n return this.values.cmyk;\n },\n rgbaArray: function () {\n var values = this.values;\n return values.rgb.concat([values.alpha]);\n },\n hslaArray: function () {\n var values = this.values;\n return values.hsl.concat([values.alpha]);\n },\n alpha: function (val) {\n if (val === undefined) {\n return this.values.alpha;\n }\n\n this.setValues('alpha', val);\n return this;\n },\n red: function (val) {\n return this.setChannel('rgb', 0, val);\n },\n green: function (val) {\n return this.setChannel('rgb', 1, val);\n },\n blue: function (val) {\n return this.setChannel('rgb', 2, val);\n },\n hue: function (val) {\n if (val) {\n val %= 360;\n val = val < 0 ? 360 + val : val;\n }\n\n return this.setChannel('hsl', 0, val);\n },\n saturation: function (val) {\n return this.setChannel('hsl', 1, val);\n },\n lightness: function (val) {\n return this.setChannel('hsl', 2, val);\n },\n saturationv: function (val) {\n return this.setChannel('hsv', 1, val);\n },\n whiteness: function (val) {\n return this.setChannel('hwb', 1, val);\n },\n blackness: function (val) {\n return this.setChannel('hwb', 2, val);\n },\n value: function (val) {\n return this.setChannel('hsv', 2, val);\n },\n cyan: function (val) {\n return this.setChannel('cmyk', 0, val);\n },\n magenta: function (val) {\n return this.setChannel('cmyk', 1, val);\n },\n yellow: function (val) {\n return this.setChannel('cmyk', 2, val);\n },\n black: function (val) {\n return this.setChannel('cmyk', 3, val);\n },\n hexString: function () {\n return colorString.hexString(this.values.rgb);\n },\n rgbString: function () {\n return colorString.rgbString(this.values.rgb, this.values.alpha);\n },\n rgbaString: function () {\n return colorString.rgbaString(this.values.rgb, this.values.alpha);\n },\n percentString: function () {\n return colorString.percentString(this.values.rgb, this.values.alpha);\n },\n hslString: function () {\n return colorString.hslString(this.values.hsl, this.values.alpha);\n },\n hslaString: function () {\n return colorString.hslaString(this.values.hsl, this.values.alpha);\n },\n hwbString: function () {\n return colorString.hwbString(this.values.hwb, this.values.alpha);\n },\n keyword: function () {\n return colorString.keyword(this.values.rgb, this.values.alpha);\n },\n rgbNumber: function () {\n var rgb = this.values.rgb;\n return rgb[0] << 16 | rgb[1] << 8 | rgb[2];\n },\n luminosity: function () {\n // http://www.w3.org/TR/WCAG20/#relativeluminancedef\n var rgb = this.values.rgb;\n var lum = [];\n\n for (var i = 0; i < rgb.length; i++) {\n var chan = rgb[i] / 255;\n lum[i] = chan <= 0.03928 ? chan / 12.92 : Math.pow((chan + 0.055) / 1.055, 2.4);\n }\n\n return 0.2126 * lum[0] + 0.7152 * lum[1] + 0.0722 * lum[2];\n },\n contrast: function (color2) {\n // http://www.w3.org/TR/WCAG20/#contrast-ratiodef\n var lum1 = this.luminosity();\n var lum2 = color2.luminosity();\n\n if (lum1 > lum2) {\n return (lum1 + 0.05) / (lum2 + 0.05);\n }\n\n return (lum2 + 0.05) / (lum1 + 0.05);\n },\n level: function (color2) {\n var contrastRatio = this.contrast(color2);\n\n if (contrastRatio >= 7.1) {\n return 'AAA';\n }\n\n return contrastRatio >= 4.5 ? 'AA' : '';\n },\n dark: function () {\n // YIQ equation from http://24ways.org/2010/calculating-color-contrast\n var rgb = this.values.rgb;\n var yiq = (rgb[0] * 299 + rgb[1] * 587 + rgb[2] * 114) / 1000;\n return yiq < 128;\n },\n light: function () {\n return !this.dark();\n },\n negate: function () {\n var rgb = [];\n\n for (var i = 0; i < 3; i++) {\n rgb[i] = 255 - this.values.rgb[i];\n }\n\n this.setValues('rgb', rgb);\n return this;\n },\n lighten: function (ratio) {\n var hsl = this.values.hsl;\n hsl[2] += hsl[2] * ratio;\n this.setValues('hsl', hsl);\n return this;\n },\n darken: function (ratio) {\n var hsl = this.values.hsl;\n hsl[2] -= hsl[2] * ratio;\n this.setValues('hsl', hsl);\n return this;\n },\n saturate: function (ratio) {\n var hsl = this.values.hsl;\n hsl[1] += hsl[1] * ratio;\n this.setValues('hsl', hsl);\n return this;\n },\n desaturate: function (ratio) {\n var hsl = this.values.hsl;\n hsl[1] -= hsl[1] * ratio;\n this.setValues('hsl', hsl);\n return this;\n },\n whiten: function (ratio) {\n var hwb = this.values.hwb;\n hwb[1] += hwb[1] * ratio;\n this.setValues('hwb', hwb);\n return this;\n },\n blacken: function (ratio) {\n var hwb = this.values.hwb;\n hwb[2] += hwb[2] * ratio;\n this.setValues('hwb', hwb);\n return this;\n },\n greyscale: function () {\n var rgb = this.values.rgb; // http://en.wikipedia.org/wiki/Grayscale#Converting_color_to_grayscale\n\n var val = rgb[0] * 0.3 + rgb[1] * 0.59 + rgb[2] * 0.11;\n this.setValues('rgb', [val, val, val]);\n return this;\n },\n clearer: function (ratio) {\n var alpha = this.values.alpha;\n this.setValues('alpha', alpha - alpha * ratio);\n return this;\n },\n opaquer: function (ratio) {\n var alpha = this.values.alpha;\n this.setValues('alpha', alpha + alpha * ratio);\n return this;\n },\n rotate: function (degrees) {\n var hsl = this.values.hsl;\n var hue = (hsl[0] + degrees) % 360;\n hsl[0] = hue < 0 ? 360 + hue : hue;\n this.setValues('hsl', hsl);\n return this;\n },\n\n /**\n * Ported from sass implementation in C\n * https://github.com/sass/libsass/blob/0e6b4a2850092356aa3ece07c6b249f0221caced/functions.cpp#L209\n */\n mix: function (mixinColor, weight) {\n var color1 = this;\n var color2 = mixinColor;\n var p = weight === undefined ? 0.5 : weight;\n var w = 2 * p - 1;\n var a = color1.alpha() - color2.alpha();\n var w1 = ((w * a === -1 ? w : (w + a) / (1 + w * a)) + 1) / 2.0;\n var w2 = 1 - w1;\n 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));\n },\n toJSON: function () {\n return this.rgb();\n },\n clone: function () {\n // NOTE(SB): using node-clone creates a dependency to Buffer when using browserify,\n // making the final build way to big to embed in Chart.js. So let's do it manually,\n // assuming that values to clone are 1 dimension arrays containing only numbers,\n // except 'alpha' which is a number.\n var result = new Color();\n var source = this.values;\n var target = result.values;\n var value, type;\n\n for (var prop in source) {\n if (source.hasOwnProperty(prop)) {\n value = source[prop];\n type = {}.toString.call(value);\n\n if (type === '[object Array]') {\n target[prop] = value.slice(0);\n } else if (type === '[object Number]') {\n target[prop] = value;\n } else {\n console.error('unexpected color value:', value);\n }\n }\n }\n\n return result;\n }\n };\n Color.prototype.spaces = {\n rgb: ['red', 'green', 'blue'],\n hsl: ['hue', 'saturation', 'lightness'],\n hsv: ['hue', 'saturation', 'value'],\n hwb: ['hue', 'whiteness', 'blackness'],\n cmyk: ['cyan', 'magenta', 'yellow', 'black']\n };\n Color.prototype.maxes = {\n rgb: [255, 255, 255],\n hsl: [360, 100, 100],\n hsv: [360, 100, 100],\n hwb: [360, 100, 100],\n cmyk: [100, 100, 100, 100]\n };\n\n Color.prototype.getValues = function (space) {\n var values = this.values;\n var vals = {};\n\n for (var i = 0; i < space.length; i++) {\n vals[space.charAt(i)] = values[space][i];\n }\n\n if (values.alpha !== 1) {\n vals.a = values.alpha;\n } // {r: 255, g: 255, b: 255, a: 0.4}\n\n\n return vals;\n };\n\n Color.prototype.setValues = function (space, vals) {\n var values = this.values;\n var spaces = this.spaces;\n var maxes = this.maxes;\n var alpha = 1;\n var i;\n this.valid = true;\n\n if (space === 'alpha') {\n alpha = vals;\n } else if (vals.length) {\n // [10, 10, 10]\n values[space] = vals.slice(0, space.length);\n alpha = vals[space.length];\n } else if (vals[space.charAt(0)] !== undefined) {\n // {r: 10, g: 10, b: 10}\n for (i = 0; i < space.length; i++) {\n values[space][i] = vals[space.charAt(i)];\n }\n\n alpha = vals.a;\n } else if (vals[spaces[space][0]] !== undefined) {\n // {red: 10, green: 10, blue: 10}\n var chans = spaces[space];\n\n for (i = 0; i < space.length; i++) {\n values[space][i] = vals[chans[i]];\n }\n\n alpha = vals.alpha;\n }\n\n values.alpha = Math.max(0, Math.min(1, alpha === undefined ? values.alpha : alpha));\n\n if (space === 'alpha') {\n return false;\n }\n\n var capped; // cap values of the space prior converting all values\n\n for (i = 0; i < space.length; i++) {\n capped = Math.max(0, Math.min(maxes[space][i], values[space][i]));\n values[space][i] = Math.round(capped);\n } // convert to all the other color spaces\n\n\n for (var sname in spaces) {\n if (sname !== space) {\n values[sname] = colorConvert[space][sname](values[space]);\n }\n }\n\n return true;\n };\n\n Color.prototype.setSpace = function (space, args) {\n var vals = args[0];\n\n if (vals === undefined) {\n // color.rgb()\n return this.getValues(space);\n } // color.rgb(10, 10, 10)\n\n\n if (typeof vals === 'number') {\n vals = Array.prototype.slice.call(args);\n }\n\n this.setValues(space, vals);\n return this;\n };\n\n Color.prototype.setChannel = function (space, index, val) {\n var svalues = this.values[space];\n\n if (val === undefined) {\n // color.red()\n return svalues[index];\n } else if (val === svalues[index]) {\n // color.red(color.red())\n return this;\n } // color.red(100)\n\n\n svalues[index] = val;\n this.setValues(space, svalues);\n return this;\n };\n\n if (typeof window !== 'undefined') {\n window.Color = Color;\n }\n\n var chartjsColor = Color;\n /**\n * @namespace Chart.helpers\n */\n\n var helpers = {\n /**\n * An empty function that can be used, for example, for optional callback.\n */\n noop: function () {},\n\n /**\n * Returns a unique id, sequentially generated from a global variable.\n * @returns {number}\n * @function\n */\n uid: function () {\n var id = 0;\n return function () {\n return id++;\n };\n }(),\n\n /**\n * Returns true if `value` is neither null nor undefined, else returns false.\n * @param {*} value - The value to test.\n * @returns {boolean}\n * @since 2.7.0\n */\n isNullOrUndef: function (value) {\n return value === null || typeof value === 'undefined';\n },\n\n /**\n * Returns true if `value` is an array (including typed arrays), else returns false.\n * @param {*} value - The value to test.\n * @returns {boolean}\n * @function\n */\n isArray: function (value) {\n if (Array.isArray && Array.isArray(value)) {\n return true;\n }\n\n var type = Object.prototype.toString.call(value);\n\n if (type.substr(0, 7) === '[object' && type.substr(-6) === 'Array]') {\n return true;\n }\n\n return false;\n },\n\n /**\n * Returns true if `value` is an object (excluding null), else returns false.\n * @param {*} value - The value to test.\n * @returns {boolean}\n * @since 2.7.0\n */\n isObject: function (value) {\n return value !== null && Object.prototype.toString.call(value) === '[object Object]';\n },\n\n /**\n * Returns true if `value` is a finite number, else returns false\n * @param {*} value - The value to test.\n * @returns {boolean}\n */\n isFinite: function (value) {\n return (typeof value === 'number' || value instanceof Number) && isFinite(value);\n },\n\n /**\n * Returns `value` if defined, else returns `defaultValue`.\n * @param {*} value - The value to return if defined.\n * @param {*} defaultValue - The value to return if `value` is undefined.\n * @returns {*}\n */\n valueOrDefault: function (value, defaultValue) {\n return typeof value === 'undefined' ? defaultValue : value;\n },\n\n /**\n * Returns value at the given `index` in array if defined, else returns `defaultValue`.\n * @param {Array} value - The array to lookup for value at `index`.\n * @param {number} index - The index in `value` to lookup for value.\n * @param {*} defaultValue - The value to return if `value[index]` is undefined.\n * @returns {*}\n */\n valueAtIndexOrDefault: function (value, index, defaultValue) {\n return helpers.valueOrDefault(helpers.isArray(value) ? value[index] : value, defaultValue);\n },\n\n /**\n * Calls `fn` with the given `args` in the scope defined by `thisArg` and returns the\n * value returned by `fn`. If `fn` is not a function, this method returns undefined.\n * @param {function} fn - The function to call.\n * @param {Array|undefined|null} args - The arguments with which `fn` should be called.\n * @param {object} [thisArg] - The value of `this` provided for the call to `fn`.\n * @returns {*}\n */\n callback: function (fn, args, thisArg) {\n if (fn && typeof fn.call === 'function') {\n return fn.apply(thisArg, args);\n }\n },\n\n /**\n * Note(SB) for performance sake, this method should only be used when loopable type\n * is unknown or in none intensive code (not called often and small loopable). Else\n * it's preferable to use a regular for() loop and save extra function calls.\n * @param {object|Array} loopable - The object or array to be iterated.\n * @param {function} fn - The function to call for each item.\n * @param {object} [thisArg] - The value of `this` provided for the call to `fn`.\n * @param {boolean} [reverse] - If true, iterates backward on the loopable.\n */\n each: function (loopable, fn, thisArg, reverse) {\n var i, len, keys;\n\n if (helpers.isArray(loopable)) {\n len = loopable.length;\n\n if (reverse) {\n for (i = len - 1; i >= 0; i--) {\n fn.call(thisArg, loopable[i], i);\n }\n } else {\n for (i = 0; i < len; i++) {\n fn.call(thisArg, loopable[i], i);\n }\n }\n } else if (helpers.isObject(loopable)) {\n keys = Object.keys(loopable);\n len = keys.length;\n\n for (i = 0; i < len; i++) {\n fn.call(thisArg, loopable[keys[i]], keys[i]);\n }\n }\n },\n\n /**\n * Returns true if the `a0` and `a1` arrays have the same content, else returns false.\n * @see https://stackoverflow.com/a/14853974\n * @param {Array} a0 - The array to compare\n * @param {Array} a1 - The array to compare\n * @returns {boolean}\n */\n arrayEquals: function (a0, a1) {\n var i, ilen, v0, v1;\n\n if (!a0 || !a1 || a0.length !== a1.length) {\n return false;\n }\n\n for (i = 0, ilen = a0.length; i < ilen; ++i) {\n v0 = a0[i];\n v1 = a1[i];\n\n if (v0 instanceof Array && v1 instanceof Array) {\n if (!helpers.arrayEquals(v0, v1)) {\n return false;\n }\n } else if (v0 !== v1) {\n // NOTE: two different object instances will never be equal: {x:20} != {x:20}\n return false;\n }\n }\n\n return true;\n },\n\n /**\n * Returns a deep copy of `source` without keeping references on objects and arrays.\n * @param {*} source - The value to clone.\n * @returns {*}\n */\n clone: function (source) {\n if (helpers.isArray(source)) {\n return source.map(helpers.clone);\n }\n\n if (helpers.isObject(source)) {\n var target = {};\n var keys = Object.keys(source);\n var klen = keys.length;\n var k = 0;\n\n for (; k < klen; ++k) {\n target[keys[k]] = helpers.clone(source[keys[k]]);\n }\n\n return target;\n }\n\n return source;\n },\n\n /**\n * The default merger when Chart.helpers.merge is called without merger option.\n * Note(SB): also used by mergeConfig and mergeScaleConfig as fallback.\n * @private\n */\n _merger: function (key, target, source, options) {\n var tval = target[key];\n var sval = source[key];\n\n if (helpers.isObject(tval) && helpers.isObject(sval)) {\n helpers.merge(tval, sval, options);\n } else {\n target[key] = helpers.clone(sval);\n }\n },\n\n /**\n * Merges source[key] in target[key] only if target[key] is undefined.\n * @private\n */\n _mergerIf: function (key, target, source) {\n var tval = target[key];\n var sval = source[key];\n\n if (helpers.isObject(tval) && helpers.isObject(sval)) {\n helpers.mergeIf(tval, sval);\n } else if (!target.hasOwnProperty(key)) {\n target[key] = helpers.clone(sval);\n }\n },\n\n /**\n * Recursively deep copies `source` properties into `target` with the given `options`.\n * IMPORTANT: `target` is not cloned and will be updated with `source` properties.\n * @param {object} target - The target object in which all sources are merged into.\n * @param {object|object[]} source - Object(s) to merge into `target`.\n * @param {object} [options] - Merging options:\n * @param {function} [options.merger] - The merge method (key, target, source, options)\n * @returns {object} The `target` object.\n */\n merge: function (target, source, options) {\n var sources = helpers.isArray(source) ? source : [source];\n var ilen = sources.length;\n var merge, i, keys, klen, k;\n\n if (!helpers.isObject(target)) {\n return target;\n }\n\n options = options || {};\n merge = options.merger || helpers._merger;\n\n for (i = 0; i < ilen; ++i) {\n source = sources[i];\n\n if (!helpers.isObject(source)) {\n continue;\n }\n\n keys = Object.keys(source);\n\n for (k = 0, klen = keys.length; k < klen; ++k) {\n merge(keys[k], target, source, options);\n }\n }\n\n return target;\n },\n\n /**\n * Recursively deep copies `source` properties into `target` *only* if not defined in target.\n * IMPORTANT: `target` is not cloned and will be updated with `source` properties.\n * @param {object} target - The target object in which all sources are merged into.\n * @param {object|object[]} source - Object(s) to merge into `target`.\n * @returns {object} The `target` object.\n */\n mergeIf: function (target, source) {\n return helpers.merge(target, source, {\n merger: helpers._mergerIf\n });\n },\n\n /**\n * Applies the contents of two or more objects together into the first object.\n * @param {object} target - The target object in which all objects are merged into.\n * @param {object} arg1 - Object containing additional properties to merge in target.\n * @param {object} argN - Additional objects containing properties to merge in target.\n * @returns {object} The `target` object.\n */\n extend: Object.assign || function (target) {\n return helpers.merge(target, [].slice.call(arguments, 1), {\n merger: function (key, dst, src) {\n dst[key] = src[key];\n }\n });\n },\n\n /**\n * Basic javascript inheritance based on the model created in Backbone.js\n */\n inherits: function (extensions) {\n var me = this;\n var ChartElement = extensions && extensions.hasOwnProperty('constructor') ? extensions.constructor : function () {\n return me.apply(this, arguments);\n };\n\n var Surrogate = function () {\n this.constructor = ChartElement;\n };\n\n Surrogate.prototype = me.prototype;\n ChartElement.prototype = new Surrogate();\n ChartElement.extend = helpers.inherits;\n\n if (extensions) {\n helpers.extend(ChartElement.prototype, extensions);\n }\n\n ChartElement.__super__ = me.prototype;\n return ChartElement;\n },\n _deprecated: function (scope, value, previous, current) {\n if (value !== undefined) {\n console.warn(scope + ': \"' + previous + '\" is deprecated. Please use \"' + current + '\" instead');\n }\n }\n };\n var helpers_core = helpers; // DEPRECATIONS\n\n /**\n * Provided for backward compatibility, use Chart.helpers.callback instead.\n * @function Chart.helpers.callCallback\n * @deprecated since version 2.6.0\n * @todo remove at version 3\n * @private\n */\n\n helpers.callCallback = helpers.callback;\n /**\n * Provided for backward compatibility, use Array.prototype.indexOf instead.\n * Array.prototype.indexOf compatibility: Chrome, Opera, Safari, FF1.5+, IE9+\n * @function Chart.helpers.indexOf\n * @deprecated since version 2.7.0\n * @todo remove at version 3\n * @private\n */\n\n helpers.indexOf = function (array, item, fromIndex) {\n return Array.prototype.indexOf.call(array, item, fromIndex);\n };\n /**\n * Provided for backward compatibility, use Chart.helpers.valueOrDefault instead.\n * @function Chart.helpers.getValueOrDefault\n * @deprecated since version 2.7.0\n * @todo remove at version 3\n * @private\n */\n\n\n helpers.getValueOrDefault = helpers.valueOrDefault;\n /**\n * Provided for backward compatibility, use Chart.helpers.valueAtIndexOrDefault instead.\n * @function Chart.helpers.getValueAtIndexOrDefault\n * @deprecated since version 2.7.0\n * @todo remove at version 3\n * @private\n */\n\n helpers.getValueAtIndexOrDefault = helpers.valueAtIndexOrDefault;\n /**\n * Easing functions adapted from Robert Penner's easing equations.\n * @namespace Chart.helpers.easingEffects\n * @see http://www.robertpenner.com/easing/\n */\n\n var effects = {\n linear: function (t) {\n return t;\n },\n easeInQuad: function (t) {\n return t * t;\n },\n easeOutQuad: function (t) {\n return -t * (t - 2);\n },\n easeInOutQuad: function (t) {\n if ((t /= 0.5) < 1) {\n return 0.5 * t * t;\n }\n\n return -0.5 * (--t * (t - 2) - 1);\n },\n easeInCubic: function (t) {\n return t * t * t;\n },\n easeOutCubic: function (t) {\n return (t = t - 1) * t * t + 1;\n },\n easeInOutCubic: function (t) {\n if ((t /= 0.5) < 1) {\n return 0.5 * t * t * t;\n }\n\n return 0.5 * ((t -= 2) * t * t + 2);\n },\n easeInQuart: function (t) {\n return t * t * t * t;\n },\n easeOutQuart: function (t) {\n return -((t = t - 1) * t * t * t - 1);\n },\n easeInOutQuart: function (t) {\n if ((t /= 0.5) < 1) {\n return 0.5 * t * t * t * t;\n }\n\n return -0.5 * ((t -= 2) * t * t * t - 2);\n },\n easeInQuint: function (t) {\n return t * t * t * t * t;\n },\n easeOutQuint: function (t) {\n return (t = t - 1) * t * t * t * t + 1;\n },\n easeInOutQuint: function (t) {\n if ((t /= 0.5) < 1) {\n return 0.5 * t * t * t * t * t;\n }\n\n return 0.5 * ((t -= 2) * t * t * t * t + 2);\n },\n easeInSine: function (t) {\n return -Math.cos(t * (Math.PI / 2)) + 1;\n },\n easeOutSine: function (t) {\n return Math.sin(t * (Math.PI / 2));\n },\n easeInOutSine: function (t) {\n return -0.5 * (Math.cos(Math.PI * t) - 1);\n },\n easeInExpo: function (t) {\n return t === 0 ? 0 : Math.pow(2, 10 * (t - 1));\n },\n easeOutExpo: function (t) {\n return t === 1 ? 1 : -Math.pow(2, -10 * t) + 1;\n },\n easeInOutExpo: function (t) {\n if (t === 0) {\n return 0;\n }\n\n if (t === 1) {\n return 1;\n }\n\n if ((t /= 0.5) < 1) {\n return 0.5 * Math.pow(2, 10 * (t - 1));\n }\n\n return 0.5 * (-Math.pow(2, -10 * --t) + 2);\n },\n easeInCirc: function (t) {\n if (t >= 1) {\n return t;\n }\n\n return -(Math.sqrt(1 - t * t) - 1);\n },\n easeOutCirc: function (t) {\n return Math.sqrt(1 - (t = t - 1) * t);\n },\n easeInOutCirc: function (t) {\n if ((t /= 0.5) < 1) {\n return -0.5 * (Math.sqrt(1 - t * t) - 1);\n }\n\n return 0.5 * (Math.sqrt(1 - (t -= 2) * t) + 1);\n },\n easeInElastic: function (t) {\n var s = 1.70158;\n var p = 0;\n var a = 1;\n\n if (t === 0) {\n return 0;\n }\n\n if (t === 1) {\n return 1;\n }\n\n if (!p) {\n p = 0.3;\n }\n\n if (a < 1) {\n a = 1;\n s = p / 4;\n } else {\n s = p / (2 * Math.PI) * Math.asin(1 / a);\n }\n\n return -(a * Math.pow(2, 10 * (t -= 1)) * Math.sin((t - s) * (2 * Math.PI) / p));\n },\n easeOutElastic: function (t) {\n var s = 1.70158;\n var p = 0;\n var a = 1;\n\n if (t === 0) {\n return 0;\n }\n\n if (t === 1) {\n return 1;\n }\n\n if (!p) {\n p = 0.3;\n }\n\n if (a < 1) {\n a = 1;\n s = p / 4;\n } else {\n s = p / (2 * Math.PI) * Math.asin(1 / a);\n }\n\n return a * Math.pow(2, -10 * t) * Math.sin((t - s) * (2 * Math.PI) / p) + 1;\n },\n easeInOutElastic: function (t) {\n var s = 1.70158;\n var p = 0;\n var a = 1;\n\n if (t === 0) {\n return 0;\n }\n\n if ((t /= 0.5) === 2) {\n return 1;\n }\n\n if (!p) {\n p = 0.45;\n }\n\n if (a < 1) {\n a = 1;\n s = p / 4;\n } else {\n s = p / (2 * Math.PI) * Math.asin(1 / a);\n }\n\n if (t < 1) {\n return -0.5 * (a * Math.pow(2, 10 * (t -= 1)) * Math.sin((t - s) * (2 * Math.PI) / p));\n }\n\n return a * Math.pow(2, -10 * (t -= 1)) * Math.sin((t - s) * (2 * Math.PI) / p) * 0.5 + 1;\n },\n easeInBack: function (t) {\n var s = 1.70158;\n return t * t * ((s + 1) * t - s);\n },\n easeOutBack: function (t) {\n var s = 1.70158;\n return (t = t - 1) * t * ((s + 1) * t + s) + 1;\n },\n easeInOutBack: function (t) {\n var s = 1.70158;\n\n if ((t /= 0.5) < 1) {\n return 0.5 * (t * t * (((s *= 1.525) + 1) * t - s));\n }\n\n return 0.5 * ((t -= 2) * t * (((s *= 1.525) + 1) * t + s) + 2);\n },\n easeInBounce: function (t) {\n return 1 - effects.easeOutBounce(1 - t);\n },\n easeOutBounce: function (t) {\n if (t < 1 / 2.75) {\n return 7.5625 * t * t;\n }\n\n if (t < 2 / 2.75) {\n return 7.5625 * (t -= 1.5 / 2.75) * t + 0.75;\n }\n\n if (t < 2.5 / 2.75) {\n return 7.5625 * (t -= 2.25 / 2.75) * t + 0.9375;\n }\n\n return 7.5625 * (t -= 2.625 / 2.75) * t + 0.984375;\n },\n easeInOutBounce: function (t) {\n if (t < 0.5) {\n return effects.easeInBounce(t * 2) * 0.5;\n }\n\n return effects.easeOutBounce(t * 2 - 1) * 0.5 + 0.5;\n }\n };\n var helpers_easing = {\n effects: effects\n }; // DEPRECATIONS\n\n /**\n * Provided for backward compatibility, use Chart.helpers.easing.effects instead.\n * @function Chart.helpers.easingEffects\n * @deprecated since version 2.7.0\n * @todo remove at version 3\n * @private\n */\n\n helpers_core.easingEffects = effects;\n var PI = Math.PI;\n var RAD_PER_DEG = PI / 180;\n var DOUBLE_PI = PI * 2;\n var HALF_PI = PI / 2;\n var QUARTER_PI = PI / 4;\n var TWO_THIRDS_PI = PI * 2 / 3;\n /**\n * @namespace Chart.helpers.canvas\n */\n\n var exports$1 = {\n /**\n * Clears the entire canvas associated to the given `chart`.\n * @param {Chart} chart - The chart for which to clear the canvas.\n */\n clear: function (chart) {\n chart.ctx.clearRect(0, 0, chart.width, chart.height);\n },\n\n /**\n * Creates a \"path\" for a rectangle with rounded corners at position (x, y) with a\n * given size (width, height) and the same `radius` for all corners.\n * @param {CanvasRenderingContext2D} ctx - The canvas 2D Context.\n * @param {number} x - The x axis of the coordinate for the rectangle starting point.\n * @param {number} y - The y axis of the coordinate for the rectangle starting point.\n * @param {number} width - The rectangle's width.\n * @param {number} height - The rectangle's height.\n * @param {number} radius - The rounded amount (in pixels) for the four corners.\n * @todo handle `radius` as top-left, top-right, bottom-right, bottom-left array/object?\n */\n roundedRect: function (ctx, x, y, width, height, radius) {\n if (radius) {\n var r = Math.min(radius, height / 2, width / 2);\n var left = x + r;\n var top = y + r;\n var right = x + width - r;\n var bottom = y + height - r;\n ctx.moveTo(x, top);\n\n if (left < right && top < bottom) {\n ctx.arc(left, top, r, -PI, -HALF_PI);\n ctx.arc(right, top, r, -HALF_PI, 0);\n ctx.arc(right, bottom, r, 0, HALF_PI);\n ctx.arc(left, bottom, r, HALF_PI, PI);\n } else if (left < right) {\n ctx.moveTo(left, y);\n ctx.arc(right, top, r, -HALF_PI, HALF_PI);\n ctx.arc(left, top, r, HALF_PI, PI + HALF_PI);\n } else if (top < bottom) {\n ctx.arc(left, top, r, -PI, 0);\n ctx.arc(left, bottom, r, 0, PI);\n } else {\n ctx.arc(left, top, r, -PI, PI);\n }\n\n ctx.closePath();\n ctx.moveTo(x, y);\n } else {\n ctx.rect(x, y, width, height);\n }\n },\n drawPoint: function (ctx, style, radius, x, y, rotation) {\n var type, xOffset, yOffset, size, cornerRadius;\n var rad = (rotation || 0) * RAD_PER_DEG;\n\n if (style && typeof style === 'object') {\n type = style.toString();\n\n if (type === '[object HTMLImageElement]' || type === '[object HTMLCanvasElement]') {\n ctx.save();\n ctx.translate(x, y);\n ctx.rotate(rad);\n ctx.drawImage(style, -style.width / 2, -style.height / 2, style.width, style.height);\n ctx.restore();\n return;\n }\n }\n\n if (isNaN(radius) || radius <= 0) {\n return;\n }\n\n ctx.beginPath();\n\n switch (style) {\n // Default includes circle\n default:\n ctx.arc(x, y, radius, 0, DOUBLE_PI);\n ctx.closePath();\n break;\n\n case 'triangle':\n ctx.moveTo(x + Math.sin(rad) * radius, y - Math.cos(rad) * radius);\n rad += TWO_THIRDS_PI;\n ctx.lineTo(x + Math.sin(rad) * radius, y - Math.cos(rad) * radius);\n rad += TWO_THIRDS_PI;\n ctx.lineTo(x + Math.sin(rad) * radius, y - Math.cos(rad) * radius);\n ctx.closePath();\n break;\n\n case 'rectRounded':\n // NOTE: the rounded rect implementation changed to use `arc` instead of\n // `quadraticCurveTo` since it generates better results when rect is\n // almost a circle. 0.516 (instead of 0.5) produces results with visually\n // closer proportion to the previous impl and it is inscribed in the\n // circle with `radius`. For more details, see the following PRs:\n // https://github.com/chartjs/Chart.js/issues/5597\n // https://github.com/chartjs/Chart.js/issues/5858\n cornerRadius = radius * 0.516;\n size = radius - cornerRadius;\n xOffset = Math.cos(rad + QUARTER_PI) * size;\n yOffset = Math.sin(rad + QUARTER_PI) * size;\n ctx.arc(x - xOffset, y - yOffset, cornerRadius, rad - PI, rad - HALF_PI);\n ctx.arc(x + yOffset, y - xOffset, cornerRadius, rad - HALF_PI, rad);\n ctx.arc(x + xOffset, y + yOffset, cornerRadius, rad, rad + HALF_PI);\n ctx.arc(x - yOffset, y + xOffset, cornerRadius, rad + HALF_PI, rad + PI);\n ctx.closePath();\n break;\n\n case 'rect':\n if (!rotation) {\n size = Math.SQRT1_2 * radius;\n ctx.rect(x - size, y - size, 2 * size, 2 * size);\n break;\n }\n\n rad += QUARTER_PI;\n\n /* falls through */\n\n case 'rectRot':\n xOffset = Math.cos(rad) * radius;\n yOffset = Math.sin(rad) * radius;\n ctx.moveTo(x - xOffset, y - yOffset);\n ctx.lineTo(x + yOffset, y - xOffset);\n ctx.lineTo(x + xOffset, y + yOffset);\n ctx.lineTo(x - yOffset, y + xOffset);\n ctx.closePath();\n break;\n\n case 'crossRot':\n rad += QUARTER_PI;\n\n /* falls through */\n\n case 'cross':\n xOffset = Math.cos(rad) * radius;\n yOffset = Math.sin(rad) * radius;\n ctx.moveTo(x - xOffset, y - yOffset);\n ctx.lineTo(x + xOffset, y + yOffset);\n ctx.moveTo(x + yOffset, y - xOffset);\n ctx.lineTo(x - yOffset, y + xOffset);\n break;\n\n case 'star':\n xOffset = Math.cos(rad) * radius;\n yOffset = Math.sin(rad) * radius;\n ctx.moveTo(x - xOffset, y - yOffset);\n ctx.lineTo(x + xOffset, y + yOffset);\n ctx.moveTo(x + yOffset, y - xOffset);\n ctx.lineTo(x - yOffset, y + xOffset);\n rad += QUARTER_PI;\n xOffset = Math.cos(rad) * radius;\n yOffset = Math.sin(rad) * radius;\n ctx.moveTo(x - xOffset, y - yOffset);\n ctx.lineTo(x + xOffset, y + yOffset);\n ctx.moveTo(x + yOffset, y - xOffset);\n ctx.lineTo(x - yOffset, y + xOffset);\n break;\n\n case 'line':\n xOffset = Math.cos(rad) * radius;\n yOffset = Math.sin(rad) * radius;\n ctx.moveTo(x - xOffset, y - yOffset);\n ctx.lineTo(x + xOffset, y + yOffset);\n break;\n\n case 'dash':\n ctx.moveTo(x, y);\n ctx.lineTo(x + Math.cos(rad) * radius, y + Math.sin(rad) * radius);\n break;\n }\n\n ctx.fill();\n ctx.stroke();\n },\n\n /**\n * Returns true if the point is inside the rectangle\n * @param {object} point - The point to test\n * @param {object} area - The rectangle\n * @returns {boolean}\n * @private\n */\n _isPointInArea: function (point, area) {\n var epsilon = 1e-6; // 1e-6 is margin in pixels for accumulated error.\n\n return point.x > area.left - epsilon && point.x < area.right + epsilon && point.y > area.top - epsilon && point.y < area.bottom + epsilon;\n },\n clipArea: function (ctx, area) {\n ctx.save();\n ctx.beginPath();\n ctx.rect(area.left, area.top, area.right - area.left, area.bottom - area.top);\n ctx.clip();\n },\n unclipArea: function (ctx) {\n ctx.restore();\n },\n lineTo: function (ctx, previous, target, flip) {\n var stepped = target.steppedLine;\n\n if (stepped) {\n if (stepped === 'middle') {\n var midpoint = (previous.x + target.x) / 2.0;\n ctx.lineTo(midpoint, flip ? target.y : previous.y);\n ctx.lineTo(midpoint, flip ? previous.y : target.y);\n } else if (stepped === 'after' && !flip || stepped !== 'after' && flip) {\n ctx.lineTo(previous.x, target.y);\n } else {\n ctx.lineTo(target.x, previous.y);\n }\n\n ctx.lineTo(target.x, target.y);\n return;\n }\n\n if (!target.tension) {\n ctx.lineTo(target.x, target.y);\n return;\n }\n\n 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);\n }\n };\n var helpers_canvas = exports$1; // DEPRECATIONS\n\n /**\n * Provided for backward compatibility, use Chart.helpers.canvas.clear instead.\n * @namespace Chart.helpers.clear\n * @deprecated since version 2.7.0\n * @todo remove at version 3\n * @private\n */\n\n helpers_core.clear = exports$1.clear;\n /**\n * Provided for backward compatibility, use Chart.helpers.canvas.roundedRect instead.\n * @namespace Chart.helpers.drawRoundedRectangle\n * @deprecated since version 2.7.0\n * @todo remove at version 3\n * @private\n */\n\n helpers_core.drawRoundedRectangle = function (ctx) {\n ctx.beginPath();\n exports$1.roundedRect.apply(exports$1, arguments);\n };\n\n var defaults = {\n /**\n * @private\n */\n _set: function (scope, values) {\n return helpers_core.merge(this[scope] || (this[scope] = {}), values);\n }\n }; // TODO(v3): remove 'global' from namespace. all default are global and\n // there's inconsistency around which options are under 'global'\n\n defaults._set('global', {\n defaultColor: 'rgba(0,0,0,0.1)',\n defaultFontColor: '#666',\n defaultFontFamily: \"'Helvetica Neue', 'Helvetica', 'Arial', sans-serif\",\n defaultFontSize: 12,\n defaultFontStyle: 'normal',\n defaultLineHeight: 1.2,\n showLines: true\n });\n\n var core_defaults = defaults;\n var valueOrDefault = helpers_core.valueOrDefault;\n /**\n * Converts the given font object into a CSS font string.\n * @param {object} font - A font object.\n * @return {string} The CSS font string. See https://developer.mozilla.org/en-US/docs/Web/CSS/font\n * @private\n */\n\n function toFontString(font) {\n if (!font || helpers_core.isNullOrUndef(font.size) || helpers_core.isNullOrUndef(font.family)) {\n return null;\n }\n\n return (font.style ? font.style + ' ' : '') + (font.weight ? font.weight + ' ' : '') + font.size + 'px ' + font.family;\n }\n /**\n * @alias Chart.helpers.options\n * @namespace\n */\n\n\n var helpers_options = {\n /**\n * Converts the given line height `value` in pixels for a specific font `size`.\n * @param {number|string} value - The lineHeight to parse (eg. 1.6, '14px', '75%', '1.6em').\n * @param {number} size - The font size (in pixels) used to resolve relative `value`.\n * @returns {number} The effective line height in pixels (size * 1.2 if value is invalid).\n * @see https://developer.mozilla.org/en-US/docs/Web/CSS/line-height\n * @since 2.7.0\n */\n toLineHeight: function (value, size) {\n var matches = ('' + value).match(/^(normal|(\\d+(?:\\.\\d+)?)(px|em|%)?)$/);\n\n if (!matches || matches[1] === 'normal') {\n return size * 1.2;\n }\n\n value = +matches[2];\n\n switch (matches[3]) {\n case 'px':\n return value;\n\n case '%':\n value /= 100;\n break;\n }\n\n return size * value;\n },\n\n /**\n * Converts the given value into a padding object with pre-computed width/height.\n * @param {number|object} value - If a number, set the value to all TRBL component,\n * else, if and object, use defined properties and sets undefined ones to 0.\n * @returns {object} The padding values (top, right, bottom, left, width, height)\n * @since 2.7.0\n */\n toPadding: function (value) {\n var t, r, b, l;\n\n if (helpers_core.isObject(value)) {\n t = +value.top || 0;\n r = +value.right || 0;\n b = +value.bottom || 0;\n l = +value.left || 0;\n } else {\n t = r = b = l = +value || 0;\n }\n\n return {\n top: t,\n right: r,\n bottom: b,\n left: l,\n height: t + b,\n width: l + r\n };\n },\n\n /**\n * Parses font options and returns the font object.\n * @param {object} options - A object that contains font options to be parsed.\n * @return {object} The font object.\n * @todo Support font.* options and renamed to toFont().\n * @private\n */\n _parseFont: function (options) {\n var globalDefaults = core_defaults.global;\n var size = valueOrDefault(options.fontSize, globalDefaults.defaultFontSize);\n var font = {\n family: valueOrDefault(options.fontFamily, globalDefaults.defaultFontFamily),\n lineHeight: helpers_core.options.toLineHeight(valueOrDefault(options.lineHeight, globalDefaults.defaultLineHeight), size),\n size: size,\n style: valueOrDefault(options.fontStyle, globalDefaults.defaultFontStyle),\n weight: null,\n string: ''\n };\n font.string = toFontString(font);\n return font;\n },\n\n /**\n * Evaluates the given `inputs` sequentially and returns the first defined value.\n * @param {Array} inputs - An array of values, falling back to the last value.\n * @param {object} [context] - If defined and the current value is a function, the value\n * is called with `context` as first argument and the result becomes the new input.\n * @param {number} [index] - If defined and the current value is an array, the value\n * at `index` become the new input.\n * @param {object} [info] - object to return information about resolution in\n * @param {boolean} [info.cacheable] - Will be set to `false` if option is not cacheable.\n * @since 2.7.0\n */\n resolve: function (inputs, context, index, info) {\n var cacheable = true;\n var i, ilen, value;\n\n for (i = 0, ilen = inputs.length; i < ilen; ++i) {\n value = inputs[i];\n\n if (value === undefined) {\n continue;\n }\n\n if (context !== undefined && typeof value === 'function') {\n value = value(context);\n cacheable = false;\n }\n\n if (index !== undefined && helpers_core.isArray(value)) {\n value = value[index];\n cacheable = false;\n }\n\n if (value !== undefined) {\n if (info && !cacheable) {\n info.cacheable = false;\n }\n\n return value;\n }\n }\n }\n };\n /**\n * @alias Chart.helpers.math\n * @namespace\n */\n\n var exports$2 = {\n /**\n * Returns an array of factors sorted from 1 to sqrt(value)\n * @private\n */\n _factorize: function (value) {\n var result = [];\n var sqrt = Math.sqrt(value);\n var i;\n\n for (i = 1; i < sqrt; i++) {\n if (value % i === 0) {\n result.push(i);\n result.push(value / i);\n }\n }\n\n if (sqrt === (sqrt | 0)) {\n // if value is a square number\n result.push(sqrt);\n }\n\n result.sort(function (a, b) {\n return a - b;\n }).pop();\n return result;\n },\n log10: Math.log10 || function (x) {\n var exponent = Math.log(x) * Math.LOG10E; // Math.LOG10E = 1 / Math.LN10.\n // Check for whole powers of 10,\n // which due to floating point rounding error should be corrected.\n\n var powerOf10 = Math.round(exponent);\n var isPowerOf10 = x === Math.pow(10, powerOf10);\n return isPowerOf10 ? powerOf10 : exponent;\n }\n };\n var helpers_math = exports$2; // DEPRECATIONS\n\n /**\n * Provided for backward compatibility, use Chart.helpers.math.log10 instead.\n * @namespace Chart.helpers.log10\n * @deprecated since version 2.9.0\n * @todo remove at version 3\n * @private\n */\n\n helpers_core.log10 = exports$2.log10;\n\n var getRtlAdapter = function (rectX, width) {\n return {\n x: function (x) {\n return rectX + rectX + width - x;\n },\n setWidth: function (w) {\n width = w;\n },\n textAlign: function (align) {\n if (align === 'center') {\n return align;\n }\n\n return align === 'right' ? 'left' : 'right';\n },\n xPlus: function (x, value) {\n return x - value;\n },\n leftForLtr: function (x, itemWidth) {\n return x - itemWidth;\n }\n };\n };\n\n var getLtrAdapter = function () {\n return {\n x: function (x) {\n return x;\n },\n setWidth: function (w) {// eslint-disable-line no-unused-vars\n },\n textAlign: function (align) {\n return align;\n },\n xPlus: function (x, value) {\n return x + value;\n },\n leftForLtr: function (x, _itemWidth) {\n // eslint-disable-line no-unused-vars\n return x;\n }\n };\n };\n\n var getAdapter = function (rtl, rectX, width) {\n return rtl ? getRtlAdapter(rectX, width) : getLtrAdapter();\n };\n\n var overrideTextDirection = function (ctx, direction) {\n var style, original;\n\n if (direction === 'ltr' || direction === 'rtl') {\n style = ctx.canvas.style;\n original = [style.getPropertyValue('direction'), style.getPropertyPriority('direction')];\n style.setProperty('direction', direction, 'important');\n ctx.prevTextDirection = original;\n }\n };\n\n var restoreTextDirection = function (ctx) {\n var original = ctx.prevTextDirection;\n\n if (original !== undefined) {\n delete ctx.prevTextDirection;\n ctx.canvas.style.setProperty('direction', original[0], original[1]);\n }\n };\n\n var helpers_rtl = {\n getRtlAdapter: getAdapter,\n overrideTextDirection: overrideTextDirection,\n restoreTextDirection: restoreTextDirection\n };\n var helpers$1 = helpers_core;\n var easing = helpers_easing;\n var canvas = helpers_canvas;\n var options = helpers_options;\n var math = helpers_math;\n var rtl = helpers_rtl;\n helpers$1.easing = easing;\n helpers$1.canvas = canvas;\n helpers$1.options = options;\n helpers$1.math = math;\n helpers$1.rtl = rtl;\n\n function interpolate(start, view, model, ease) {\n var keys = Object.keys(model);\n var i, ilen, key, actual, origin, target, type, c0, c1;\n\n for (i = 0, ilen = keys.length; i < ilen; ++i) {\n key = keys[i];\n target = model[key]; // if a value is added to the model after pivot() has been called, the view\n // doesn't contain it, so let's initialize the view to the target value.\n\n if (!view.hasOwnProperty(key)) {\n view[key] = target;\n }\n\n actual = view[key];\n\n if (actual === target || key[0] === '_') {\n continue;\n }\n\n if (!start.hasOwnProperty(key)) {\n start[key] = actual;\n }\n\n origin = start[key];\n type = typeof target;\n\n if (type === typeof origin) {\n if (type === 'string') {\n c0 = chartjsColor(origin);\n\n if (c0.valid) {\n c1 = chartjsColor(target);\n\n if (c1.valid) {\n view[key] = c1.mix(c0, ease).rgbString();\n continue;\n }\n }\n } else if (helpers$1.isFinite(origin) && helpers$1.isFinite(target)) {\n view[key] = origin + (target - origin) * ease;\n continue;\n }\n }\n\n view[key] = target;\n }\n }\n\n var Element = function (configuration) {\n helpers$1.extend(this, configuration);\n this.initialize.apply(this, arguments);\n };\n\n helpers$1.extend(Element.prototype, {\n _type: undefined,\n initialize: function () {\n this.hidden = false;\n },\n pivot: function () {\n var me = this;\n\n if (!me._view) {\n me._view = helpers$1.extend({}, me._model);\n }\n\n me._start = {};\n return me;\n },\n transition: function (ease) {\n var me = this;\n var model = me._model;\n var start = me._start;\n var view = me._view; // No animation -> No Transition\n\n if (!model || ease === 1) {\n me._view = helpers$1.extend({}, model);\n me._start = null;\n return me;\n }\n\n if (!view) {\n view = me._view = {};\n }\n\n if (!start) {\n start = me._start = {};\n }\n\n interpolate(start, view, model, ease);\n return me;\n },\n tooltipPosition: function () {\n return {\n x: this._model.x,\n y: this._model.y\n };\n },\n hasValue: function () {\n return helpers$1.isNumber(this._model.x) && helpers$1.isNumber(this._model.y);\n }\n });\n Element.extend = helpers$1.inherits;\n var core_element = Element;\n var exports$3 = core_element.extend({\n chart: null,\n // the animation associated chart instance\n currentStep: 0,\n // the current animation step\n numSteps: 60,\n // default number of steps\n easing: '',\n // the easing to use for this animation\n render: null,\n // render function used by the animation service\n onAnimationProgress: null,\n // user specified callback to fire on each step of the animation\n onAnimationComplete: null // user specified callback to fire when the animation finishes\n\n });\n var core_animation = exports$3; // DEPRECATIONS\n\n /**\n * Provided for backward compatibility, use Chart.Animation instead\n * @prop Chart.Animation#animationObject\n * @deprecated since version 2.6.0\n * @todo remove at version 3\n */\n\n Object.defineProperty(exports$3.prototype, 'animationObject', {\n get: function () {\n return this;\n }\n });\n /**\n * Provided for backward compatibility, use Chart.Animation#chart instead\n * @prop Chart.Animation#chartInstance\n * @deprecated since version 2.6.0\n * @todo remove at version 3\n */\n\n Object.defineProperty(exports$3.prototype, 'chartInstance', {\n get: function () {\n return this.chart;\n },\n set: function (value) {\n this.chart = value;\n }\n });\n\n core_defaults._set('global', {\n animation: {\n duration: 1000,\n easing: 'easeOutQuart',\n onProgress: helpers$1.noop,\n onComplete: helpers$1.noop\n }\n });\n\n var core_animations = {\n animations: [],\n request: null,\n\n /**\n * @param {Chart} chart - The chart to animate.\n * @param {Chart.Animation} animation - The animation that we will animate.\n * @param {number} duration - The animation duration in ms.\n * @param {boolean} lazy - if true, the chart is not marked as animating to enable more responsive interactions\n */\n addAnimation: function (chart, animation, duration, lazy) {\n var animations = this.animations;\n var i, ilen;\n animation.chart = chart;\n animation.startTime = Date.now();\n animation.duration = duration;\n\n if (!lazy) {\n chart.animating = true;\n }\n\n for (i = 0, ilen = animations.length; i < ilen; ++i) {\n if (animations[i].chart === chart) {\n animations[i] = animation;\n return;\n }\n }\n\n animations.push(animation); // If there are no animations queued, manually kickstart a digest, for lack of a better word\n\n if (animations.length === 1) {\n this.requestAnimationFrame();\n }\n },\n cancelAnimation: function (chart) {\n var index = helpers$1.findIndex(this.animations, function (animation) {\n return animation.chart === chart;\n });\n\n if (index !== -1) {\n this.animations.splice(index, 1);\n chart.animating = false;\n }\n },\n requestAnimationFrame: function () {\n var me = this;\n\n if (me.request === null) {\n // Skip animation frame requests until the active one is executed.\n // This can happen when processing mouse events, e.g. 'mousemove'\n // and 'mouseout' events will trigger multiple renders.\n me.request = helpers$1.requestAnimFrame.call(window, function () {\n me.request = null;\n me.startDigest();\n });\n }\n },\n\n /**\n * @private\n */\n startDigest: function () {\n var me = this;\n me.advance(); // Do we have more stuff to animate?\n\n if (me.animations.length > 0) {\n me.requestAnimationFrame();\n }\n },\n\n /**\n * @private\n */\n advance: function () {\n var animations = this.animations;\n var animation, chart, numSteps, nextStep;\n var i = 0; // 1 animation per chart, so we are looping charts here\n\n while (i < animations.length) {\n animation = animations[i];\n chart = animation.chart;\n numSteps = animation.numSteps; // Make sure that currentStep starts at 1\n // https://github.com/chartjs/Chart.js/issues/6104\n\n nextStep = Math.floor((Date.now() - animation.startTime) / animation.duration * numSteps) + 1;\n animation.currentStep = Math.min(nextStep, numSteps);\n helpers$1.callback(animation.render, [chart, animation], chart);\n helpers$1.callback(animation.onAnimationProgress, [animation], chart);\n\n if (animation.currentStep >= numSteps) {\n helpers$1.callback(animation.onAnimationComplete, [animation], chart);\n chart.animating = false;\n animations.splice(i, 1);\n } else {\n ++i;\n }\n }\n }\n };\n var resolve = helpers$1.options.resolve;\n var arrayEvents = ['push', 'pop', 'shift', 'splice', 'unshift'];\n /**\n * Hooks the array methods that add or remove values ('push', pop', 'shift', 'splice',\n * 'unshift') and notify the listener AFTER the array has been altered. Listeners are\n * called on the 'onData*' callbacks (e.g. onDataPush, etc.) with same arguments.\n */\n\n function listenArrayEvents(array, listener) {\n if (array._chartjs) {\n array._chartjs.listeners.push(listener);\n\n return;\n }\n\n Object.defineProperty(array, '_chartjs', {\n configurable: true,\n enumerable: false,\n value: {\n listeners: [listener]\n }\n });\n arrayEvents.forEach(function (key) {\n var method = 'onData' + key.charAt(0).toUpperCase() + key.slice(1);\n var base = array[key];\n Object.defineProperty(array, key, {\n configurable: true,\n enumerable: false,\n value: function () {\n var args = Array.prototype.slice.call(arguments);\n var res = base.apply(this, args);\n helpers$1.each(array._chartjs.listeners, function (object) {\n if (typeof object[method] === 'function') {\n object[method].apply(object, args);\n }\n });\n return res;\n }\n });\n });\n }\n /**\n * Removes the given array event listener and cleanup extra attached properties (such as\n * the _chartjs stub and overridden methods) if array doesn't have any more listeners.\n */\n\n\n function unlistenArrayEvents(array, listener) {\n var stub = array._chartjs;\n\n if (!stub) {\n return;\n }\n\n var listeners = stub.listeners;\n var index = listeners.indexOf(listener);\n\n if (index !== -1) {\n listeners.splice(index, 1);\n }\n\n if (listeners.length > 0) {\n return;\n }\n\n arrayEvents.forEach(function (key) {\n delete array[key];\n });\n delete array._chartjs;\n } // Base class for all dataset controllers (line, bar, etc)\n\n\n var DatasetController = function (chart, datasetIndex) {\n this.initialize(chart, datasetIndex);\n };\n\n helpers$1.extend(DatasetController.prototype, {\n /**\n * Element type used to generate a meta dataset (e.g. Chart.element.Line).\n * @type {Chart.core.element}\n */\n datasetElementType: null,\n\n /**\n * Element type used to generate a meta data (e.g. Chart.element.Point).\n * @type {Chart.core.element}\n */\n dataElementType: null,\n\n /**\n * Dataset element option keys to be resolved in _resolveDatasetElementOptions.\n * A derived controller may override this to resolve controller-specific options.\n * The keys defined here are for backward compatibility for legend styles.\n * @private\n */\n _datasetElementOptions: ['backgroundColor', 'borderCapStyle', 'borderColor', 'borderDash', 'borderDashOffset', 'borderJoinStyle', 'borderWidth'],\n\n /**\n * Data element option keys to be resolved in _resolveDataElementOptions.\n * A derived controller may override this to resolve controller-specific options.\n * The keys defined here are for backward compatibility for legend styles.\n * @private\n */\n _dataElementOptions: ['backgroundColor', 'borderColor', 'borderWidth', 'pointStyle'],\n initialize: function (chart, datasetIndex) {\n var me = this;\n me.chart = chart;\n me.index = datasetIndex;\n me.linkScales();\n me.addElements();\n me._type = me.getMeta().type;\n },\n updateIndex: function (datasetIndex) {\n this.index = datasetIndex;\n },\n linkScales: function () {\n var me = this;\n var meta = me.getMeta();\n var chart = me.chart;\n var scales = chart.scales;\n var dataset = me.getDataset();\n var scalesOpts = chart.options.scales;\n\n if (meta.xAxisID === null || !(meta.xAxisID in scales) || dataset.xAxisID) {\n meta.xAxisID = dataset.xAxisID || scalesOpts.xAxes[0].id;\n }\n\n if (meta.yAxisID === null || !(meta.yAxisID in scales) || dataset.yAxisID) {\n meta.yAxisID = dataset.yAxisID || scalesOpts.yAxes[0].id;\n }\n },\n getDataset: function () {\n return this.chart.data.datasets[this.index];\n },\n getMeta: function () {\n return this.chart.getDatasetMeta(this.index);\n },\n getScaleForId: function (scaleID) {\n return this.chart.scales[scaleID];\n },\n\n /**\n * @private\n */\n _getValueScaleId: function () {\n return this.getMeta().yAxisID;\n },\n\n /**\n * @private\n */\n _getIndexScaleId: function () {\n return this.getMeta().xAxisID;\n },\n\n /**\n * @private\n */\n _getValueScale: function () {\n return this.getScaleForId(this._getValueScaleId());\n },\n\n /**\n * @private\n */\n _getIndexScale: function () {\n return this.getScaleForId(this._getIndexScaleId());\n },\n reset: function () {\n this._update(true);\n },\n\n /**\n * @private\n */\n destroy: function () {\n if (this._data) {\n unlistenArrayEvents(this._data, this);\n }\n },\n createMetaDataset: function () {\n var me = this;\n var type = me.datasetElementType;\n return type && new type({\n _chart: me.chart,\n _datasetIndex: me.index\n });\n },\n createMetaData: function (index) {\n var me = this;\n var type = me.dataElementType;\n return type && new type({\n _chart: me.chart,\n _datasetIndex: me.index,\n _index: index\n });\n },\n addElements: function () {\n var me = this;\n var meta = me.getMeta();\n var data = me.getDataset().data || [];\n var metaData = meta.data;\n var i, ilen;\n\n for (i = 0, ilen = data.length; i < ilen; ++i) {\n metaData[i] = metaData[i] || me.createMetaData(i);\n }\n\n meta.dataset = meta.dataset || me.createMetaDataset();\n },\n addElementAndReset: function (index) {\n var element = this.createMetaData(index);\n this.getMeta().data.splice(index, 0, element);\n this.updateElement(element, index, true);\n },\n buildOrUpdateElements: function () {\n var me = this;\n var dataset = me.getDataset();\n var data = dataset.data || (dataset.data = []); // In order to correctly handle data addition/deletion animation (an thus simulate\n // real-time charts), we need to monitor these data modifications and synchronize\n // the internal meta data accordingly.\n\n if (me._data !== data) {\n if (me._data) {\n // This case happens when the user replaced the data array instance.\n unlistenArrayEvents(me._data, me);\n }\n\n if (data && Object.isExtensible(data)) {\n listenArrayEvents(data, me);\n }\n\n me._data = data;\n } // Re-sync meta data in case the user replaced the data array or if we missed\n // any updates and so make sure that we handle number of datapoints changing.\n\n\n me.resyncElements();\n },\n\n /**\n * Returns the merged user-supplied and default dataset-level options\n * @private\n */\n _configure: function () {\n var me = this;\n me._config = helpers$1.merge({}, [me.chart.options.datasets[me._type], me.getDataset()], {\n merger: function (key, target, source) {\n if (key !== '_meta' && key !== 'data') {\n helpers$1._merger(key, target, source);\n }\n }\n });\n },\n _update: function (reset) {\n var me = this;\n\n me._configure();\n\n me._cachedDataOpts = null;\n me.update(reset);\n },\n update: helpers$1.noop,\n transition: function (easingValue) {\n var meta = this.getMeta();\n var elements = meta.data || [];\n var ilen = elements.length;\n var i = 0;\n\n for (; i < ilen; ++i) {\n elements[i].transition(easingValue);\n }\n\n if (meta.dataset) {\n meta.dataset.transition(easingValue);\n }\n },\n draw: function () {\n var meta = this.getMeta();\n var elements = meta.data || [];\n var ilen = elements.length;\n var i = 0;\n\n if (meta.dataset) {\n meta.dataset.draw();\n }\n\n for (; i < ilen; ++i) {\n elements[i].draw();\n }\n },\n\n /**\n * Returns a set of predefined style properties that should be used to represent the dataset\n * or the data if the index is specified\n * @param {number} index - data index\n * @return {IStyleInterface} style object\n */\n getStyle: function (index) {\n var me = this;\n var meta = me.getMeta();\n var dataset = meta.dataset;\n var style;\n\n me._configure();\n\n if (dataset && index === undefined) {\n style = me._resolveDatasetElementOptions(dataset || {});\n } else {\n index = index || 0;\n style = me._resolveDataElementOptions(meta.data[index] || {}, index);\n }\n\n if (style.fill === false || style.fill === null) {\n style.backgroundColor = style.borderColor;\n }\n\n return style;\n },\n\n /**\n * @private\n */\n _resolveDatasetElementOptions: function (element, hover) {\n var me = this;\n var chart = me.chart;\n var datasetOpts = me._config;\n var custom = element.custom || {};\n var options = chart.options.elements[me.datasetElementType.prototype._type] || {};\n var elementOptions = me._datasetElementOptions;\n var values = {};\n var i, ilen, key, readKey; // Scriptable options\n\n var context = {\n chart: chart,\n dataset: me.getDataset(),\n datasetIndex: me.index,\n hover: hover\n };\n\n for (i = 0, ilen = elementOptions.length; i < ilen; ++i) {\n key = elementOptions[i];\n readKey = hover ? 'hover' + key.charAt(0).toUpperCase() + key.slice(1) : key;\n values[key] = resolve([custom[readKey], datasetOpts[readKey], options[readKey]], context);\n }\n\n return values;\n },\n\n /**\n * @private\n */\n _resolveDataElementOptions: function (element, index) {\n var me = this;\n var custom = element && element.custom;\n var cached = me._cachedDataOpts;\n\n if (cached && !custom) {\n return cached;\n }\n\n var chart = me.chart;\n var datasetOpts = me._config;\n var options = chart.options.elements[me.dataElementType.prototype._type] || {};\n var elementOptions = me._dataElementOptions;\n var values = {}; // Scriptable options\n\n var context = {\n chart: chart,\n dataIndex: index,\n dataset: me.getDataset(),\n datasetIndex: me.index\n }; // `resolve` sets cacheable to `false` if any option is indexed or scripted\n\n var info = {\n cacheable: !custom\n };\n var keys, i, ilen, key;\n custom = custom || {};\n\n if (helpers$1.isArray(elementOptions)) {\n for (i = 0, ilen = elementOptions.length; i < ilen; ++i) {\n key = elementOptions[i];\n values[key] = resolve([custom[key], datasetOpts[key], options[key]], context, index, info);\n }\n } else {\n keys = Object.keys(elementOptions);\n\n for (i = 0, ilen = keys.length; i < ilen; ++i) {\n key = keys[i];\n values[key] = resolve([custom[key], datasetOpts[elementOptions[key]], datasetOpts[key], options[key]], context, index, info);\n }\n }\n\n if (info.cacheable) {\n me._cachedDataOpts = Object.freeze(values);\n }\n\n return values;\n },\n removeHoverStyle: function (element) {\n helpers$1.merge(element._model, element.$previousStyle || {});\n delete element.$previousStyle;\n },\n setHoverStyle: function (element) {\n var dataset = this.chart.data.datasets[element._datasetIndex];\n var index = element._index;\n var custom = element.custom || {};\n var model = element._model;\n var getHoverColor = helpers$1.getHoverColor;\n element.$previousStyle = {\n backgroundColor: model.backgroundColor,\n borderColor: model.borderColor,\n borderWidth: model.borderWidth\n };\n model.backgroundColor = resolve([custom.hoverBackgroundColor, dataset.hoverBackgroundColor, getHoverColor(model.backgroundColor)], undefined, index);\n model.borderColor = resolve([custom.hoverBorderColor, dataset.hoverBorderColor, getHoverColor(model.borderColor)], undefined, index);\n model.borderWidth = resolve([custom.hoverBorderWidth, dataset.hoverBorderWidth, model.borderWidth], undefined, index);\n },\n\n /**\n * @private\n */\n _removeDatasetHoverStyle: function () {\n var element = this.getMeta().dataset;\n\n if (element) {\n this.removeHoverStyle(element);\n }\n },\n\n /**\n * @private\n */\n _setDatasetHoverStyle: function () {\n var element = this.getMeta().dataset;\n var prev = {};\n var i, ilen, key, keys, hoverOptions, model;\n\n if (!element) {\n return;\n }\n\n model = element._model;\n hoverOptions = this._resolveDatasetElementOptions(element, true);\n keys = Object.keys(hoverOptions);\n\n for (i = 0, ilen = keys.length; i < ilen; ++i) {\n key = keys[i];\n prev[key] = model[key];\n model[key] = hoverOptions[key];\n }\n\n element.$previousStyle = prev;\n },\n\n /**\n * @private\n */\n resyncElements: function () {\n var me = this;\n var meta = me.getMeta();\n var data = me.getDataset().data;\n var numMeta = meta.data.length;\n var numData = data.length;\n\n if (numData < numMeta) {\n meta.data.splice(numData, numMeta - numData);\n } else if (numData > numMeta) {\n me.insertElements(numMeta, numData - numMeta);\n }\n },\n\n /**\n * @private\n */\n insertElements: function (start, count) {\n for (var i = 0; i < count; ++i) {\n this.addElementAndReset(start + i);\n }\n },\n\n /**\n * @private\n */\n onDataPush: function () {\n var count = arguments.length;\n this.insertElements(this.getDataset().data.length - count, count);\n },\n\n /**\n * @private\n */\n onDataPop: function () {\n this.getMeta().data.pop();\n },\n\n /**\n * @private\n */\n onDataShift: function () {\n this.getMeta().data.shift();\n },\n\n /**\n * @private\n */\n onDataSplice: function (start, count) {\n this.getMeta().data.splice(start, count);\n this.insertElements(start, arguments.length - 2);\n },\n\n /**\n * @private\n */\n onDataUnshift: function () {\n this.insertElements(0, arguments.length);\n }\n });\n DatasetController.extend = helpers$1.inherits;\n var core_datasetController = DatasetController;\n var TAU = Math.PI * 2;\n\n core_defaults._set('global', {\n elements: {\n arc: {\n backgroundColor: core_defaults.global.defaultColor,\n borderColor: '#fff',\n borderWidth: 2,\n borderAlign: 'center'\n }\n }\n });\n\n function clipArc(ctx, arc) {\n var startAngle = arc.startAngle;\n var endAngle = arc.endAngle;\n var pixelMargin = arc.pixelMargin;\n var angleMargin = pixelMargin / arc.outerRadius;\n var x = arc.x;\n var y = arc.y; // Draw an inner border by cliping the arc and drawing a double-width border\n // Enlarge the clipping arc by 0.33 pixels to eliminate glitches between borders\n\n ctx.beginPath();\n ctx.arc(x, y, arc.outerRadius, startAngle - angleMargin, endAngle + angleMargin);\n\n if (arc.innerRadius > pixelMargin) {\n angleMargin = pixelMargin / arc.innerRadius;\n ctx.arc(x, y, arc.innerRadius - pixelMargin, endAngle + angleMargin, startAngle - angleMargin, true);\n } else {\n ctx.arc(x, y, pixelMargin, endAngle + Math.PI / 2, startAngle - Math.PI / 2);\n }\n\n ctx.closePath();\n ctx.clip();\n }\n\n function drawFullCircleBorders(ctx, vm, arc, inner) {\n var endAngle = arc.endAngle;\n var i;\n\n if (inner) {\n arc.endAngle = arc.startAngle + TAU;\n clipArc(ctx, arc);\n arc.endAngle = endAngle;\n\n if (arc.endAngle === arc.startAngle && arc.fullCircles) {\n arc.endAngle += TAU;\n arc.fullCircles--;\n }\n }\n\n ctx.beginPath();\n ctx.arc(arc.x, arc.y, arc.innerRadius, arc.startAngle + TAU, arc.startAngle, true);\n\n for (i = 0; i < arc.fullCircles; ++i) {\n ctx.stroke();\n }\n\n ctx.beginPath();\n ctx.arc(arc.x, arc.y, vm.outerRadius, arc.startAngle, arc.startAngle + TAU);\n\n for (i = 0; i < arc.fullCircles; ++i) {\n ctx.stroke();\n }\n }\n\n function drawBorder(ctx, vm, arc) {\n var inner = vm.borderAlign === 'inner';\n\n if (inner) {\n ctx.lineWidth = vm.borderWidth * 2;\n ctx.lineJoin = 'round';\n } else {\n ctx.lineWidth = vm.borderWidth;\n ctx.lineJoin = 'bevel';\n }\n\n if (arc.fullCircles) {\n drawFullCircleBorders(ctx, vm, arc, inner);\n }\n\n if (inner) {\n clipArc(ctx, arc);\n }\n\n ctx.beginPath();\n ctx.arc(arc.x, arc.y, vm.outerRadius, arc.startAngle, arc.endAngle);\n ctx.arc(arc.x, arc.y, arc.innerRadius, arc.endAngle, arc.startAngle, true);\n ctx.closePath();\n ctx.stroke();\n }\n\n var element_arc = core_element.extend({\n _type: 'arc',\n inLabelRange: function (mouseX) {\n var vm = this._view;\n\n if (vm) {\n return Math.pow(mouseX - vm.x, 2) < Math.pow(vm.radius + vm.hoverRadius, 2);\n }\n\n return false;\n },\n inRange: function (chartX, chartY) {\n var vm = this._view;\n\n if (vm) {\n var pointRelativePosition = helpers$1.getAngleFromPoint(vm, {\n x: chartX,\n y: chartY\n });\n var angle = pointRelativePosition.angle;\n var distance = pointRelativePosition.distance; // Sanitise angle range\n\n var startAngle = vm.startAngle;\n var endAngle = vm.endAngle;\n\n while (endAngle < startAngle) {\n endAngle += TAU;\n }\n\n while (angle > endAngle) {\n angle -= TAU;\n }\n\n while (angle < startAngle) {\n angle += TAU;\n } // Check if within the range of the open/close angle\n\n\n var betweenAngles = angle >= startAngle && angle <= endAngle;\n var withinRadius = distance >= vm.innerRadius && distance <= vm.outerRadius;\n return betweenAngles && withinRadius;\n }\n\n return false;\n },\n getCenterPoint: function () {\n var vm = this._view;\n var halfAngle = (vm.startAngle + vm.endAngle) / 2;\n var halfRadius = (vm.innerRadius + vm.outerRadius) / 2;\n return {\n x: vm.x + Math.cos(halfAngle) * halfRadius,\n y: vm.y + Math.sin(halfAngle) * halfRadius\n };\n },\n getArea: function () {\n var vm = this._view;\n return Math.PI * ((vm.endAngle - vm.startAngle) / (2 * Math.PI)) * (Math.pow(vm.outerRadius, 2) - Math.pow(vm.innerRadius, 2));\n },\n tooltipPosition: function () {\n var vm = this._view;\n var centreAngle = vm.startAngle + (vm.endAngle - vm.startAngle) / 2;\n var rangeFromCentre = (vm.outerRadius - vm.innerRadius) / 2 + vm.innerRadius;\n return {\n x: vm.x + Math.cos(centreAngle) * rangeFromCentre,\n y: vm.y + Math.sin(centreAngle) * rangeFromCentre\n };\n },\n draw: function () {\n var ctx = this._chart.ctx;\n var vm = this._view;\n var pixelMargin = vm.borderAlign === 'inner' ? 0.33 : 0;\n var arc = {\n x: vm.x,\n y: vm.y,\n innerRadius: vm.innerRadius,\n outerRadius: Math.max(vm.outerRadius - pixelMargin, 0),\n pixelMargin: pixelMargin,\n startAngle: vm.startAngle,\n endAngle: vm.endAngle,\n fullCircles: Math.floor(vm.circumference / TAU)\n };\n var i;\n ctx.save();\n ctx.fillStyle = vm.backgroundColor;\n ctx.strokeStyle = vm.borderColor;\n\n if (arc.fullCircles) {\n arc.endAngle = arc.startAngle + TAU;\n ctx.beginPath();\n ctx.arc(arc.x, arc.y, arc.outerRadius, arc.startAngle, arc.endAngle);\n ctx.arc(arc.x, arc.y, arc.innerRadius, arc.endAngle, arc.startAngle, true);\n ctx.closePath();\n\n for (i = 0; i < arc.fullCircles; ++i) {\n ctx.fill();\n }\n\n arc.endAngle = arc.startAngle + vm.circumference % TAU;\n }\n\n ctx.beginPath();\n ctx.arc(arc.x, arc.y, arc.outerRadius, arc.startAngle, arc.endAngle);\n ctx.arc(arc.x, arc.y, arc.innerRadius, arc.endAngle, arc.startAngle, true);\n ctx.closePath();\n ctx.fill();\n\n if (vm.borderWidth) {\n drawBorder(ctx, vm, arc);\n }\n\n ctx.restore();\n }\n });\n var valueOrDefault$1 = helpers$1.valueOrDefault;\n var defaultColor = core_defaults.global.defaultColor;\n\n core_defaults._set('global', {\n elements: {\n line: {\n tension: 0.4,\n backgroundColor: defaultColor,\n borderWidth: 3,\n borderColor: defaultColor,\n borderCapStyle: 'butt',\n borderDash: [],\n borderDashOffset: 0.0,\n borderJoinStyle: 'miter',\n capBezierPoints: true,\n fill: true // do we fill in the area between the line and its base axis\n\n }\n }\n });\n\n var element_line = core_element.extend({\n _type: 'line',\n draw: function () {\n var me = this;\n var vm = me._view;\n var ctx = me._chart.ctx;\n var spanGaps = vm.spanGaps;\n\n var points = me._children.slice(); // clone array\n\n\n var globalDefaults = core_defaults.global;\n var globalOptionLineElements = globalDefaults.elements.line;\n var lastDrawnIndex = -1;\n var closePath = me._loop;\n var index, previous, currentVM;\n\n if (!points.length) {\n return;\n }\n\n if (me._loop) {\n for (index = 0; index < points.length; ++index) {\n previous = helpers$1.previousItem(points, index); // If the line has an open path, shift the point array\n\n if (!points[index]._view.skip && previous._view.skip) {\n points = points.slice(index).concat(points.slice(0, index));\n closePath = spanGaps;\n break;\n }\n } // If the line has a close path, add the first point again\n\n\n if (closePath) {\n points.push(points[0]);\n }\n }\n\n ctx.save(); // Stroke Line Options\n\n ctx.lineCap = vm.borderCapStyle || globalOptionLineElements.borderCapStyle; // IE 9 and 10 do not support line dash\n\n if (ctx.setLineDash) {\n ctx.setLineDash(vm.borderDash || globalOptionLineElements.borderDash);\n }\n\n ctx.lineDashOffset = valueOrDefault$1(vm.borderDashOffset, globalOptionLineElements.borderDashOffset);\n ctx.lineJoin = vm.borderJoinStyle || globalOptionLineElements.borderJoinStyle;\n ctx.lineWidth = valueOrDefault$1(vm.borderWidth, globalOptionLineElements.borderWidth);\n ctx.strokeStyle = vm.borderColor || globalDefaults.defaultColor; // Stroke Line\n\n ctx.beginPath(); // First point moves to it's starting position no matter what\n\n currentVM = points[0]._view;\n\n if (!currentVM.skip) {\n ctx.moveTo(currentVM.x, currentVM.y);\n lastDrawnIndex = 0;\n }\n\n for (index = 1; index < points.length; ++index) {\n currentVM = points[index]._view;\n previous = lastDrawnIndex === -1 ? helpers$1.previousItem(points, index) : points[lastDrawnIndex];\n\n if (!currentVM.skip) {\n if (lastDrawnIndex !== index - 1 && !spanGaps || lastDrawnIndex === -1) {\n // There was a gap and this is the first point after the gap\n ctx.moveTo(currentVM.x, currentVM.y);\n } else {\n // Line to next point\n helpers$1.canvas.lineTo(ctx, previous._view, currentVM);\n }\n\n lastDrawnIndex = index;\n }\n }\n\n if (closePath) {\n ctx.closePath();\n }\n\n ctx.stroke();\n ctx.restore();\n }\n });\n var valueOrDefault$2 = helpers$1.valueOrDefault;\n var defaultColor$1 = core_defaults.global.defaultColor;\n\n core_defaults._set('global', {\n elements: {\n point: {\n radius: 3,\n pointStyle: 'circle',\n backgroundColor: defaultColor$1,\n borderColor: defaultColor$1,\n borderWidth: 1,\n // Hover\n hitRadius: 1,\n hoverRadius: 4,\n hoverBorderWidth: 1\n }\n }\n });\n\n function xRange(mouseX) {\n var vm = this._view;\n return vm ? Math.abs(mouseX - vm.x) < vm.radius + vm.hitRadius : false;\n }\n\n function yRange(mouseY) {\n var vm = this._view;\n return vm ? Math.abs(mouseY - vm.y) < vm.radius + vm.hitRadius : false;\n }\n\n var element_point = core_element.extend({\n _type: 'point',\n inRange: function (mouseX, mouseY) {\n var vm = this._view;\n return vm ? Math.pow(mouseX - vm.x, 2) + Math.pow(mouseY - vm.y, 2) < Math.pow(vm.hitRadius + vm.radius, 2) : false;\n },\n inLabelRange: xRange,\n inXRange: xRange,\n inYRange: yRange,\n getCenterPoint: function () {\n var vm = this._view;\n return {\n x: vm.x,\n y: vm.y\n };\n },\n getArea: function () {\n return Math.PI * Math.pow(this._view.radius, 2);\n },\n tooltipPosition: function () {\n var vm = this._view;\n return {\n x: vm.x,\n y: vm.y,\n padding: vm.radius + vm.borderWidth\n };\n },\n draw: function (chartArea) {\n var vm = this._view;\n var ctx = this._chart.ctx;\n var pointStyle = vm.pointStyle;\n var rotation = vm.rotation;\n var radius = vm.radius;\n var x = vm.x;\n var y = vm.y;\n var globalDefaults = core_defaults.global;\n var defaultColor = globalDefaults.defaultColor; // eslint-disable-line no-shadow\n\n if (vm.skip) {\n return;\n } // Clipping for Points.\n\n\n if (chartArea === undefined || helpers$1.canvas._isPointInArea(vm, chartArea)) {\n ctx.strokeStyle = vm.borderColor || defaultColor;\n ctx.lineWidth = valueOrDefault$2(vm.borderWidth, globalDefaults.elements.point.borderWidth);\n ctx.fillStyle = vm.backgroundColor || defaultColor;\n helpers$1.canvas.drawPoint(ctx, pointStyle, radius, x, y, rotation);\n }\n }\n });\n var defaultColor$2 = core_defaults.global.defaultColor;\n\n core_defaults._set('global', {\n elements: {\n rectangle: {\n backgroundColor: defaultColor$2,\n borderColor: defaultColor$2,\n borderSkipped: 'bottom',\n borderWidth: 0\n }\n }\n });\n\n function isVertical(vm) {\n return vm && vm.width !== undefined;\n }\n /**\n * Helper function to get the bounds of the bar regardless of the orientation\n * @param bar {Chart.Element.Rectangle} the bar\n * @return {Bounds} bounds of the bar\n * @private\n */\n\n\n function getBarBounds(vm) {\n var x1, x2, y1, y2, half;\n\n if (isVertical(vm)) {\n half = vm.width / 2;\n x1 = vm.x - half;\n x2 = vm.x + half;\n y1 = Math.min(vm.y, vm.base);\n y2 = Math.max(vm.y, vm.base);\n } else {\n half = vm.height / 2;\n x1 = Math.min(vm.x, vm.base);\n x2 = Math.max(vm.x, vm.base);\n y1 = vm.y - half;\n y2 = vm.y + half;\n }\n\n return {\n left: x1,\n top: y1,\n right: x2,\n bottom: y2\n };\n }\n\n function swap(orig, v1, v2) {\n return orig === v1 ? v2 : orig === v2 ? v1 : orig;\n }\n\n function parseBorderSkipped(vm) {\n var edge = vm.borderSkipped;\n var res = {};\n\n if (!edge) {\n return res;\n }\n\n if (vm.horizontal) {\n if (vm.base > vm.x) {\n edge = swap(edge, 'left', 'right');\n }\n } else if (vm.base < vm.y) {\n edge = swap(edge, 'bottom', 'top');\n }\n\n res[edge] = true;\n return res;\n }\n\n function parseBorderWidth(vm, maxW, maxH) {\n var value = vm.borderWidth;\n var skip = parseBorderSkipped(vm);\n var t, r, b, l;\n\n if (helpers$1.isObject(value)) {\n t = +value.top || 0;\n r = +value.right || 0;\n b = +value.bottom || 0;\n l = +value.left || 0;\n } else {\n t = r = b = l = +value || 0;\n }\n\n return {\n t: skip.top || t < 0 ? 0 : t > maxH ? maxH : t,\n r: skip.right || r < 0 ? 0 : r > maxW ? maxW : r,\n b: skip.bottom || b < 0 ? 0 : b > maxH ? maxH : b,\n l: skip.left || l < 0 ? 0 : l > maxW ? maxW : l\n };\n }\n\n function boundingRects(vm) {\n var bounds = getBarBounds(vm);\n var width = bounds.right - bounds.left;\n var height = bounds.bottom - bounds.top;\n var border = parseBorderWidth(vm, width / 2, height / 2);\n return {\n outer: {\n x: bounds.left,\n y: bounds.top,\n w: width,\n h: height\n },\n inner: {\n x: bounds.left + border.l,\n y: bounds.top + border.t,\n w: width - border.l - border.r,\n h: height - border.t - border.b\n }\n };\n }\n\n function inRange(vm, x, y) {\n var skipX = x === null;\n var skipY = y === null;\n var bounds = !vm || skipX && skipY ? false : getBarBounds(vm);\n return bounds && (skipX || x >= bounds.left && x <= bounds.right) && (skipY || y >= bounds.top && y <= bounds.bottom);\n }\n\n var element_rectangle = core_element.extend({\n _type: 'rectangle',\n draw: function () {\n var ctx = this._chart.ctx;\n var vm = this._view;\n var rects = boundingRects(vm);\n var outer = rects.outer;\n var inner = rects.inner;\n ctx.fillStyle = vm.backgroundColor;\n ctx.fillRect(outer.x, outer.y, outer.w, outer.h);\n\n if (outer.w === inner.w && outer.h === inner.h) {\n return;\n }\n\n ctx.save();\n ctx.beginPath();\n ctx.rect(outer.x, outer.y, outer.w, outer.h);\n ctx.clip();\n ctx.fillStyle = vm.borderColor;\n ctx.rect(inner.x, inner.y, inner.w, inner.h);\n ctx.fill('evenodd');\n ctx.restore();\n },\n height: function () {\n var vm = this._view;\n return vm.base - vm.y;\n },\n inRange: function (mouseX, mouseY) {\n return inRange(this._view, mouseX, mouseY);\n },\n inLabelRange: function (mouseX, mouseY) {\n var vm = this._view;\n return isVertical(vm) ? inRange(vm, mouseX, null) : inRange(vm, null, mouseY);\n },\n inXRange: function (mouseX) {\n return inRange(this._view, mouseX, null);\n },\n inYRange: function (mouseY) {\n return inRange(this._view, null, mouseY);\n },\n getCenterPoint: function () {\n var vm = this._view;\n var x, y;\n\n if (isVertical(vm)) {\n x = vm.x;\n y = (vm.y + vm.base) / 2;\n } else {\n x = (vm.x + vm.base) / 2;\n y = vm.y;\n }\n\n return {\n x: x,\n y: y\n };\n },\n getArea: function () {\n var vm = this._view;\n return isVertical(vm) ? vm.width * Math.abs(vm.y - vm.base) : vm.height * Math.abs(vm.x - vm.base);\n },\n tooltipPosition: function () {\n var vm = this._view;\n return {\n x: vm.x,\n y: vm.y\n };\n }\n });\n var elements = {};\n var Arc = element_arc;\n var Line = element_line;\n var Point = element_point;\n var Rectangle = element_rectangle;\n elements.Arc = Arc;\n elements.Line = Line;\n elements.Point = Point;\n elements.Rectangle = Rectangle;\n var deprecated = helpers$1._deprecated;\n var valueOrDefault$3 = helpers$1.valueOrDefault;\n\n core_defaults._set('bar', {\n hover: {\n mode: 'label'\n },\n scales: {\n xAxes: [{\n type: 'category',\n offset: true,\n gridLines: {\n offsetGridLines: true\n }\n }],\n yAxes: [{\n type: 'linear'\n }]\n }\n });\n\n core_defaults._set('global', {\n datasets: {\n bar: {\n categoryPercentage: 0.8,\n barPercentage: 0.9\n }\n }\n });\n /**\n * Computes the \"optimal\" sample size to maintain bars equally sized while preventing overlap.\n * @private\n */\n\n\n function computeMinSampleSize(scale, pixels) {\n var min = scale._length;\n var prev, curr, i, ilen;\n\n for (i = 1, ilen = pixels.length; i < ilen; ++i) {\n min = Math.min(min, Math.abs(pixels[i] - pixels[i - 1]));\n }\n\n for (i = 0, ilen = scale.getTicks().length; i < ilen; ++i) {\n curr = scale.getPixelForTick(i);\n min = i > 0 ? Math.min(min, Math.abs(curr - prev)) : min;\n prev = curr;\n }\n\n return min;\n }\n /**\n * Computes an \"ideal\" category based on the absolute bar thickness or, if undefined or null,\n * uses the smallest interval (see computeMinSampleSize) that prevents bar overlapping. This\n * mode currently always generates bars equally sized (until we introduce scriptable options?).\n * @private\n */\n\n\n function computeFitCategoryTraits(index, ruler, options) {\n var thickness = options.barThickness;\n var count = ruler.stackCount;\n var curr = ruler.pixels[index];\n var min = helpers$1.isNullOrUndef(thickness) ? computeMinSampleSize(ruler.scale, ruler.pixels) : -1;\n var size, ratio;\n\n if (helpers$1.isNullOrUndef(thickness)) {\n size = min * options.categoryPercentage;\n ratio = options.barPercentage;\n } else {\n // When bar thickness is enforced, category and bar percentages are ignored.\n // Note(SB): we could add support for relative bar thickness (e.g. barThickness: '50%')\n // and deprecate barPercentage since this value is ignored when thickness is absolute.\n size = thickness * count;\n ratio = 1;\n }\n\n return {\n chunk: size / count,\n ratio: ratio,\n start: curr - size / 2\n };\n }\n /**\n * Computes an \"optimal\" category that globally arranges bars side by side (no gap when\n * percentage options are 1), based on the previous and following categories. This mode\n * generates bars with different widths when data are not evenly spaced.\n * @private\n */\n\n\n function computeFlexCategoryTraits(index, ruler, options) {\n var pixels = ruler.pixels;\n var curr = pixels[index];\n var prev = index > 0 ? pixels[index - 1] : null;\n var next = index < pixels.length - 1 ? pixels[index + 1] : null;\n var percent = options.categoryPercentage;\n var start, size;\n\n if (prev === null) {\n // first data: its size is double based on the next point or,\n // if it's also the last data, we use the scale size.\n prev = curr - (next === null ? ruler.end - ruler.start : next - curr);\n }\n\n if (next === null) {\n // last data: its size is also double based on the previous point.\n next = curr + curr - prev;\n }\n\n start = curr - (curr - Math.min(prev, next)) / 2 * percent;\n size = Math.abs(next - prev) / 2 * percent;\n return {\n chunk: size / ruler.stackCount,\n ratio: options.barPercentage,\n start: start\n };\n }\n\n var controller_bar = core_datasetController.extend({\n dataElementType: elements.Rectangle,\n\n /**\n * @private\n */\n _dataElementOptions: ['backgroundColor', 'borderColor', 'borderSkipped', 'borderWidth', 'barPercentage', 'barThickness', 'categoryPercentage', 'maxBarThickness', 'minBarLength'],\n initialize: function () {\n var me = this;\n var meta, scaleOpts;\n core_datasetController.prototype.initialize.apply(me, arguments);\n meta = me.getMeta();\n meta.stack = me.getDataset().stack;\n meta.bar = true;\n scaleOpts = me._getIndexScale().options;\n deprecated('bar chart', scaleOpts.barPercentage, 'scales.[x/y]Axes.barPercentage', 'dataset.barPercentage');\n deprecated('bar chart', scaleOpts.barThickness, 'scales.[x/y]Axes.barThickness', 'dataset.barThickness');\n deprecated('bar chart', scaleOpts.categoryPercentage, 'scales.[x/y]Axes.categoryPercentage', 'dataset.categoryPercentage');\n deprecated('bar chart', me._getValueScale().options.minBarLength, 'scales.[x/y]Axes.minBarLength', 'dataset.minBarLength');\n deprecated('bar chart', scaleOpts.maxBarThickness, 'scales.[x/y]Axes.maxBarThickness', 'dataset.maxBarThickness');\n },\n update: function (reset) {\n var me = this;\n var rects = me.getMeta().data;\n var i, ilen;\n me._ruler = me.getRuler();\n\n for (i = 0, ilen = rects.length; i < ilen; ++i) {\n me.updateElement(rects[i], i, reset);\n }\n },\n updateElement: function (rectangle, index, reset) {\n var me = this;\n var meta = me.getMeta();\n var dataset = me.getDataset();\n\n var options = me._resolveDataElementOptions(rectangle, index);\n\n rectangle._xScale = me.getScaleForId(meta.xAxisID);\n rectangle._yScale = me.getScaleForId(meta.yAxisID);\n rectangle._datasetIndex = me.index;\n rectangle._index = index;\n rectangle._model = {\n backgroundColor: options.backgroundColor,\n borderColor: options.borderColor,\n borderSkipped: options.borderSkipped,\n borderWidth: options.borderWidth,\n datasetLabel: dataset.label,\n label: me.chart.data.labels[index]\n };\n\n if (helpers$1.isArray(dataset.data[index])) {\n rectangle._model.borderSkipped = null;\n }\n\n me._updateElementGeometry(rectangle, index, reset, options);\n\n rectangle.pivot();\n },\n\n /**\n * @private\n */\n _updateElementGeometry: function (rectangle, index, reset, options) {\n var me = this;\n var model = rectangle._model;\n\n var vscale = me._getValueScale();\n\n var base = vscale.getBasePixel();\n var horizontal = vscale.isHorizontal();\n var ruler = me._ruler || me.getRuler();\n var vpixels = me.calculateBarValuePixels(me.index, index, options);\n var ipixels = me.calculateBarIndexPixels(me.index, index, ruler, options);\n model.horizontal = horizontal;\n model.base = reset ? base : vpixels.base;\n model.x = horizontal ? reset ? base : vpixels.head : ipixels.center;\n model.y = horizontal ? ipixels.center : reset ? base : vpixels.head;\n model.height = horizontal ? ipixels.size : undefined;\n model.width = horizontal ? undefined : ipixels.size;\n },\n\n /**\n * Returns the stacks based on groups and bar visibility.\n * @param {number} [last] - The dataset index\n * @returns {string[]} The list of stack IDs\n * @private\n */\n _getStacks: function (last) {\n var me = this;\n\n var scale = me._getIndexScale();\n\n var metasets = scale._getMatchingVisibleMetas(me._type);\n\n var stacked = scale.options.stacked;\n var ilen = metasets.length;\n var stacks = [];\n var i, meta;\n\n for (i = 0; i < ilen; ++i) {\n meta = metasets[i]; // stacked | meta.stack\n // | found | not found | undefined\n // false | x | x | x\n // true | | x |\n // undefined | | x | x\n\n if (stacked === false || stacks.indexOf(meta.stack) === -1 || stacked === undefined && meta.stack === undefined) {\n stacks.push(meta.stack);\n }\n\n if (meta.index === last) {\n break;\n }\n }\n\n return stacks;\n },\n\n /**\n * Returns the effective number of stacks based on groups and bar visibility.\n * @private\n */\n getStackCount: function () {\n return this._getStacks().length;\n },\n\n /**\n * Returns the stack index for the given dataset based on groups and bar visibility.\n * @param {number} [datasetIndex] - The dataset index\n * @param {string} [name] - The stack name to find\n * @returns {number} The stack index\n * @private\n */\n getStackIndex: function (datasetIndex, name) {\n var stacks = this._getStacks(datasetIndex);\n\n var index = name !== undefined ? stacks.indexOf(name) : -1; // indexOf returns -1 if element is not present\n\n return index === -1 ? stacks.length - 1 : index;\n },\n\n /**\n * @private\n */\n getRuler: function () {\n var me = this;\n\n var scale = me._getIndexScale();\n\n var pixels = [];\n var i, ilen;\n\n for (i = 0, ilen = me.getMeta().data.length; i < ilen; ++i) {\n pixels.push(scale.getPixelForValue(null, i, me.index));\n }\n\n return {\n pixels: pixels,\n start: scale._startPixel,\n end: scale._endPixel,\n stackCount: me.getStackCount(),\n scale: scale\n };\n },\n\n /**\n * Note: pixel values are not clamped to the scale area.\n * @private\n */\n calculateBarValuePixels: function (datasetIndex, index, options) {\n var me = this;\n var chart = me.chart;\n\n var scale = me._getValueScale();\n\n var isHorizontal = scale.isHorizontal();\n var datasets = chart.data.datasets;\n\n var metasets = scale._getMatchingVisibleMetas(me._type);\n\n var value = scale._parseValue(datasets[datasetIndex].data[index]);\n\n var minBarLength = options.minBarLength;\n var stacked = scale.options.stacked;\n var stack = me.getMeta().stack;\n var start = value.start === undefined ? 0 : value.max >= 0 && value.min >= 0 ? value.min : value.max;\n var length = value.start === undefined ? value.end : value.max >= 0 && value.min >= 0 ? value.max - value.min : value.min - value.max;\n var ilen = metasets.length;\n var i, imeta, ivalue, base, head, size, stackLength;\n\n if (stacked || stacked === undefined && stack !== undefined) {\n for (i = 0; i < ilen; ++i) {\n imeta = metasets[i];\n\n if (imeta.index === datasetIndex) {\n break;\n }\n\n if (imeta.stack === stack) {\n stackLength = scale._parseValue(datasets[imeta.index].data[index]);\n ivalue = stackLength.start === undefined ? stackLength.end : stackLength.min >= 0 && stackLength.max >= 0 ? stackLength.max : stackLength.min;\n\n if (value.min < 0 && ivalue < 0 || value.max >= 0 && ivalue > 0) {\n start += ivalue;\n }\n }\n }\n }\n\n base = scale.getPixelForValue(start);\n head = scale.getPixelForValue(start + length);\n size = head - base;\n\n if (minBarLength !== undefined && Math.abs(size) < minBarLength) {\n size = minBarLength;\n\n if (length >= 0 && !isHorizontal || length < 0 && isHorizontal) {\n head = base - minBarLength;\n } else {\n head = base + minBarLength;\n }\n }\n\n return {\n size: size,\n base: base,\n head: head,\n center: head + size / 2\n };\n },\n\n /**\n * @private\n */\n calculateBarIndexPixels: function (datasetIndex, index, ruler, options) {\n var me = this;\n var range = options.barThickness === 'flex' ? computeFlexCategoryTraits(index, ruler, options) : computeFitCategoryTraits(index, ruler, options);\n var stackIndex = me.getStackIndex(datasetIndex, me.getMeta().stack);\n var center = range.start + range.chunk * stackIndex + range.chunk / 2;\n var size = Math.min(valueOrDefault$3(options.maxBarThickness, Infinity), range.chunk * range.ratio);\n return {\n base: center - size / 2,\n head: center + size / 2,\n center: center,\n size: size\n };\n },\n draw: function () {\n var me = this;\n var chart = me.chart;\n\n var scale = me._getValueScale();\n\n var rects = me.getMeta().data;\n var dataset = me.getDataset();\n var ilen = rects.length;\n var i = 0;\n helpers$1.canvas.clipArea(chart.ctx, chart.chartArea);\n\n for (; i < ilen; ++i) {\n var val = scale._parseValue(dataset.data[i]);\n\n if (!isNaN(val.min) && !isNaN(val.max)) {\n rects[i].draw();\n }\n }\n\n helpers$1.canvas.unclipArea(chart.ctx);\n },\n\n /**\n * @private\n */\n _resolveDataElementOptions: function () {\n var me = this;\n var values = helpers$1.extend({}, core_datasetController.prototype._resolveDataElementOptions.apply(me, arguments));\n\n var indexOpts = me._getIndexScale().options;\n\n var valueOpts = me._getValueScale().options;\n\n values.barPercentage = valueOrDefault$3(indexOpts.barPercentage, values.barPercentage);\n values.barThickness = valueOrDefault$3(indexOpts.barThickness, values.barThickness);\n values.categoryPercentage = valueOrDefault$3(indexOpts.categoryPercentage, values.categoryPercentage);\n values.maxBarThickness = valueOrDefault$3(indexOpts.maxBarThickness, values.maxBarThickness);\n values.minBarLength = valueOrDefault$3(valueOpts.minBarLength, values.minBarLength);\n return values;\n }\n });\n var valueOrDefault$4 = helpers$1.valueOrDefault;\n var resolve$1 = helpers$1.options.resolve;\n\n core_defaults._set('bubble', {\n hover: {\n mode: 'single'\n },\n scales: {\n xAxes: [{\n type: 'linear',\n // bubble should probably use a linear scale by default\n position: 'bottom',\n id: 'x-axis-0' // need an ID so datasets can reference the scale\n\n }],\n yAxes: [{\n type: 'linear',\n position: 'left',\n id: 'y-axis-0'\n }]\n },\n tooltips: {\n callbacks: {\n title: function () {\n // Title doesn't make sense for scatter since we format the data as a point\n return '';\n },\n label: function (item, data) {\n var datasetLabel = data.datasets[item.datasetIndex].label || '';\n var dataPoint = data.datasets[item.datasetIndex].data[item.index];\n return datasetLabel + ': (' + item.xLabel + ', ' + item.yLabel + ', ' + dataPoint.r + ')';\n }\n }\n }\n });\n\n var controller_bubble = core_datasetController.extend({\n /**\n * @protected\n */\n dataElementType: elements.Point,\n\n /**\n * @private\n */\n _dataElementOptions: ['backgroundColor', 'borderColor', 'borderWidth', 'hoverBackgroundColor', 'hoverBorderColor', 'hoverBorderWidth', 'hoverRadius', 'hitRadius', 'pointStyle', 'rotation'],\n\n /**\n * @protected\n */\n update: function (reset) {\n var me = this;\n var meta = me.getMeta();\n var points = meta.data; // Update Points\n\n helpers$1.each(points, function (point, index) {\n me.updateElement(point, index, reset);\n });\n },\n\n /**\n * @protected\n */\n updateElement: function (point, index, reset) {\n var me = this;\n var meta = me.getMeta();\n var custom = point.custom || {};\n var xScale = me.getScaleForId(meta.xAxisID);\n var yScale = me.getScaleForId(meta.yAxisID);\n\n var options = me._resolveDataElementOptions(point, index);\n\n var data = me.getDataset().data[index];\n var dsIndex = me.index;\n var x = reset ? xScale.getPixelForDecimal(0.5) : xScale.getPixelForValue(typeof data === 'object' ? data : NaN, index, dsIndex);\n var y = reset ? yScale.getBasePixel() : yScale.getPixelForValue(data, index, dsIndex);\n point._xScale = xScale;\n point._yScale = yScale;\n point._options = options;\n point._datasetIndex = dsIndex;\n point._index = index;\n point._model = {\n backgroundColor: options.backgroundColor,\n borderColor: options.borderColor,\n borderWidth: options.borderWidth,\n hitRadius: options.hitRadius,\n pointStyle: options.pointStyle,\n rotation: options.rotation,\n radius: reset ? 0 : options.radius,\n skip: custom.skip || isNaN(x) || isNaN(y),\n x: x,\n y: y\n };\n point.pivot();\n },\n\n /**\n * @protected\n */\n setHoverStyle: function (point) {\n var model = point._model;\n var options = point._options;\n var getHoverColor = helpers$1.getHoverColor;\n point.$previousStyle = {\n backgroundColor: model.backgroundColor,\n borderColor: model.borderColor,\n borderWidth: model.borderWidth,\n radius: model.radius\n };\n model.backgroundColor = valueOrDefault$4(options.hoverBackgroundColor, getHoverColor(options.backgroundColor));\n model.borderColor = valueOrDefault$4(options.hoverBorderColor, getHoverColor(options.borderColor));\n model.borderWidth = valueOrDefault$4(options.hoverBorderWidth, options.borderWidth);\n model.radius = options.radius + options.hoverRadius;\n },\n\n /**\n * @private\n */\n _resolveDataElementOptions: function (point, index) {\n var me = this;\n var chart = me.chart;\n var dataset = me.getDataset();\n var custom = point.custom || {};\n var data = dataset.data[index] || {};\n\n var values = core_datasetController.prototype._resolveDataElementOptions.apply(me, arguments); // Scriptable options\n\n\n var context = {\n chart: chart,\n dataIndex: index,\n dataset: dataset,\n datasetIndex: me.index\n }; // In case values were cached (and thus frozen), we need to clone the values\n\n if (me._cachedDataOpts === values) {\n values = helpers$1.extend({}, values);\n } // Custom radius resolution\n\n\n values.radius = resolve$1([custom.radius, data.r, me._config.radius, chart.options.elements.point.radius], context, index);\n return values;\n }\n });\n var valueOrDefault$5 = helpers$1.valueOrDefault;\n var PI$1 = Math.PI;\n var DOUBLE_PI$1 = PI$1 * 2;\n var HALF_PI$1 = PI$1 / 2;\n\n core_defaults._set('doughnut', {\n animation: {\n // Boolean - Whether we animate the rotation of the Doughnut\n animateRotate: true,\n // Boolean - Whether we animate scaling the Doughnut from the centre\n animateScale: false\n },\n hover: {\n mode: 'single'\n },\n legendCallback: function (chart) {\n var list = document.createElement('ul');\n var data = chart.data;\n var datasets = data.datasets;\n var labels = data.labels;\n var i, ilen, listItem, listItemSpan;\n list.setAttribute('class', chart.id + '-legend');\n\n if (datasets.length) {\n for (i = 0, ilen = datasets[0].data.length; i < ilen; ++i) {\n listItem = list.appendChild(document.createElement('li'));\n listItemSpan = listItem.appendChild(document.createElement('span'));\n listItemSpan.style.backgroundColor = datasets[0].backgroundColor[i];\n\n if (labels[i]) {\n listItem.appendChild(document.createTextNode(labels[i]));\n }\n }\n }\n\n return list.outerHTML;\n },\n legend: {\n labels: {\n generateLabels: function (chart) {\n var data = chart.data;\n\n if (data.labels.length && data.datasets.length) {\n return data.labels.map(function (label, i) {\n var meta = chart.getDatasetMeta(0);\n var style = meta.controller.getStyle(i);\n return {\n text: label,\n fillStyle: style.backgroundColor,\n strokeStyle: style.borderColor,\n lineWidth: style.borderWidth,\n hidden: isNaN(data.datasets[0].data[i]) || meta.data[i].hidden,\n // Extra data used for toggling the correct item\n index: i\n };\n });\n }\n\n return [];\n }\n },\n onClick: function (e, legendItem) {\n var index = legendItem.index;\n var chart = this.chart;\n var i, ilen, meta;\n\n for (i = 0, ilen = (chart.data.datasets || []).length; i < ilen; ++i) {\n meta = chart.getDatasetMeta(i); // toggle visibility of index if exists\n\n if (meta.data[index]) {\n meta.data[index].hidden = !meta.data[index].hidden;\n }\n }\n\n chart.update();\n }\n },\n // The percentage of the chart that we cut out of the middle.\n cutoutPercentage: 50,\n // The rotation of the chart, where the first data arc begins.\n rotation: -HALF_PI$1,\n // The total circumference of the chart.\n circumference: DOUBLE_PI$1,\n // Need to override these to give a nice default\n tooltips: {\n callbacks: {\n title: function () {\n return '';\n },\n label: function (tooltipItem, data) {\n var dataLabel = data.labels[tooltipItem.index];\n var value = ': ' + data.datasets[tooltipItem.datasetIndex].data[tooltipItem.index];\n\n if (helpers$1.isArray(dataLabel)) {\n // show value on first line of multiline label\n // need to clone because we are changing the value\n dataLabel = dataLabel.slice();\n dataLabel[0] += value;\n } else {\n dataLabel += value;\n }\n\n return dataLabel;\n }\n }\n }\n });\n\n var controller_doughnut = core_datasetController.extend({\n dataElementType: elements.Arc,\n linkScales: helpers$1.noop,\n\n /**\n * @private\n */\n _dataElementOptions: ['backgroundColor', 'borderColor', 'borderWidth', 'borderAlign', 'hoverBackgroundColor', 'hoverBorderColor', 'hoverBorderWidth'],\n // Get index of the dataset in relation to the visible datasets. This allows determining the inner and outer radius correctly\n getRingIndex: function (datasetIndex) {\n var ringIndex = 0;\n\n for (var j = 0; j < datasetIndex; ++j) {\n if (this.chart.isDatasetVisible(j)) {\n ++ringIndex;\n }\n }\n\n return ringIndex;\n },\n update: function (reset) {\n var me = this;\n var chart = me.chart;\n var chartArea = chart.chartArea;\n var opts = chart.options;\n var ratioX = 1;\n var ratioY = 1;\n var offsetX = 0;\n var offsetY = 0;\n var meta = me.getMeta();\n var arcs = meta.data;\n var cutout = opts.cutoutPercentage / 100 || 0;\n var circumference = opts.circumference;\n\n var chartWeight = me._getRingWeight(me.index);\n\n var maxWidth, maxHeight, i, ilen; // If the chart's circumference isn't a full circle, calculate size as a ratio of the width/height of the arc\n\n if (circumference < DOUBLE_PI$1) {\n var startAngle = opts.rotation % DOUBLE_PI$1;\n startAngle += startAngle >= PI$1 ? -DOUBLE_PI$1 : startAngle < -PI$1 ? DOUBLE_PI$1 : 0;\n var endAngle = startAngle + circumference;\n var startX = Math.cos(startAngle);\n var startY = Math.sin(startAngle);\n var endX = Math.cos(endAngle);\n var endY = Math.sin(endAngle);\n var contains0 = startAngle <= 0 && endAngle >= 0 || endAngle >= DOUBLE_PI$1;\n var contains90 = startAngle <= HALF_PI$1 && endAngle >= HALF_PI$1 || endAngle >= DOUBLE_PI$1 + HALF_PI$1;\n var contains180 = startAngle === -PI$1 || endAngle >= PI$1;\n var contains270 = startAngle <= -HALF_PI$1 && endAngle >= -HALF_PI$1 || endAngle >= PI$1 + HALF_PI$1;\n var minX = contains180 ? -1 : Math.min(startX, startX * cutout, endX, endX * cutout);\n var minY = contains270 ? -1 : Math.min(startY, startY * cutout, endY, endY * cutout);\n var maxX = contains0 ? 1 : Math.max(startX, startX * cutout, endX, endX * cutout);\n var maxY = contains90 ? 1 : Math.max(startY, startY * cutout, endY, endY * cutout);\n ratioX = (maxX - minX) / 2;\n ratioY = (maxY - minY) / 2;\n offsetX = -(maxX + minX) / 2;\n offsetY = -(maxY + minY) / 2;\n }\n\n for (i = 0, ilen = arcs.length; i < ilen; ++i) {\n arcs[i]._options = me._resolveDataElementOptions(arcs[i], i);\n }\n\n chart.borderWidth = me.getMaxBorderWidth();\n maxWidth = (chartArea.right - chartArea.left - chart.borderWidth) / ratioX;\n maxHeight = (chartArea.bottom - chartArea.top - chart.borderWidth) / ratioY;\n chart.outerRadius = Math.max(Math.min(maxWidth, maxHeight) / 2, 0);\n chart.innerRadius = Math.max(chart.outerRadius * cutout, 0);\n chart.radiusLength = (chart.outerRadius - chart.innerRadius) / (me._getVisibleDatasetWeightTotal() || 1);\n chart.offsetX = offsetX * chart.outerRadius;\n chart.offsetY = offsetY * chart.outerRadius;\n meta.total = me.calculateTotal();\n me.outerRadius = chart.outerRadius - chart.radiusLength * me._getRingWeightOffset(me.index);\n me.innerRadius = Math.max(me.outerRadius - chart.radiusLength * chartWeight, 0);\n\n for (i = 0, ilen = arcs.length; i < ilen; ++i) {\n me.updateElement(arcs[i], i, reset);\n }\n },\n updateElement: function (arc, index, reset) {\n var me = this;\n var chart = me.chart;\n var chartArea = chart.chartArea;\n var opts = chart.options;\n var animationOpts = opts.animation;\n var centerX = (chartArea.left + chartArea.right) / 2;\n var centerY = (chartArea.top + chartArea.bottom) / 2;\n var startAngle = opts.rotation; // non reset case handled later\n\n var endAngle = opts.rotation; // non reset case handled later\n\n var dataset = me.getDataset();\n var circumference = reset && animationOpts.animateRotate ? 0 : arc.hidden ? 0 : me.calculateCircumference(dataset.data[index]) * (opts.circumference / DOUBLE_PI$1);\n var innerRadius = reset && animationOpts.animateScale ? 0 : me.innerRadius;\n var outerRadius = reset && animationOpts.animateScale ? 0 : me.outerRadius;\n var options = arc._options || {};\n helpers$1.extend(arc, {\n // Utility\n _datasetIndex: me.index,\n _index: index,\n // Desired view properties\n _model: {\n backgroundColor: options.backgroundColor,\n borderColor: options.borderColor,\n borderWidth: options.borderWidth,\n borderAlign: options.borderAlign,\n x: centerX + chart.offsetX,\n y: centerY + chart.offsetY,\n startAngle: startAngle,\n endAngle: endAngle,\n circumference: circumference,\n outerRadius: outerRadius,\n innerRadius: innerRadius,\n label: helpers$1.valueAtIndexOrDefault(dataset.label, index, chart.data.labels[index])\n }\n });\n var model = arc._model; // Set correct angles if not resetting\n\n if (!reset || !animationOpts.animateRotate) {\n if (index === 0) {\n model.startAngle = opts.rotation;\n } else {\n model.startAngle = me.getMeta().data[index - 1]._model.endAngle;\n }\n\n model.endAngle = model.startAngle + model.circumference;\n }\n\n arc.pivot();\n },\n calculateTotal: function () {\n var dataset = this.getDataset();\n var meta = this.getMeta();\n var total = 0;\n var value;\n helpers$1.each(meta.data, function (element, index) {\n value = dataset.data[index];\n\n if (!isNaN(value) && !element.hidden) {\n total += Math.abs(value);\n }\n });\n /* if (total === 0) {\n \ttotal = NaN;\n }*/\n\n return total;\n },\n calculateCircumference: function (value) {\n var total = this.getMeta().total;\n\n if (total > 0 && !isNaN(value)) {\n return DOUBLE_PI$1 * (Math.abs(value) / total);\n }\n\n return 0;\n },\n // gets the max border or hover width to properly scale pie charts\n getMaxBorderWidth: function (arcs) {\n var me = this;\n var max = 0;\n var chart = me.chart;\n var i, ilen, meta, arc, controller, options, borderWidth, hoverWidth;\n\n if (!arcs) {\n // Find the outmost visible dataset\n for (i = 0, ilen = chart.data.datasets.length; i < ilen; ++i) {\n if (chart.isDatasetVisible(i)) {\n meta = chart.getDatasetMeta(i);\n arcs = meta.data;\n\n if (i !== me.index) {\n controller = meta.controller;\n }\n\n break;\n }\n }\n }\n\n if (!arcs) {\n return 0;\n }\n\n for (i = 0, ilen = arcs.length; i < ilen; ++i) {\n arc = arcs[i];\n\n if (controller) {\n controller._configure();\n\n options = controller._resolveDataElementOptions(arc, i);\n } else {\n options = arc._options;\n }\n\n if (options.borderAlign !== 'inner') {\n borderWidth = options.borderWidth;\n hoverWidth = options.hoverBorderWidth;\n max = borderWidth > max ? borderWidth : max;\n max = hoverWidth > max ? hoverWidth : max;\n }\n }\n\n return max;\n },\n\n /**\n * @protected\n */\n setHoverStyle: function (arc) {\n var model = arc._model;\n var options = arc._options;\n var getHoverColor = helpers$1.getHoverColor;\n arc.$previousStyle = {\n backgroundColor: model.backgroundColor,\n borderColor: model.borderColor,\n borderWidth: model.borderWidth\n };\n model.backgroundColor = valueOrDefault$5(options.hoverBackgroundColor, getHoverColor(options.backgroundColor));\n model.borderColor = valueOrDefault$5(options.hoverBorderColor, getHoverColor(options.borderColor));\n model.borderWidth = valueOrDefault$5(options.hoverBorderWidth, options.borderWidth);\n },\n\n /**\n * Get radius length offset of the dataset in relation to the visible datasets weights. This allows determining the inner and outer radius correctly\n * @private\n */\n _getRingWeightOffset: function (datasetIndex) {\n var ringWeightOffset = 0;\n\n for (var i = 0; i < datasetIndex; ++i) {\n if (this.chart.isDatasetVisible(i)) {\n ringWeightOffset += this._getRingWeight(i);\n }\n }\n\n return ringWeightOffset;\n },\n\n /**\n * @private\n */\n _getRingWeight: function (dataSetIndex) {\n return Math.max(valueOrDefault$5(this.chart.data.datasets[dataSetIndex].weight, 1), 0);\n },\n\n /**\n * Returns the sum of all visibile data set weights. This value can be 0.\n * @private\n */\n _getVisibleDatasetWeightTotal: function () {\n return this._getRingWeightOffset(this.chart.data.datasets.length);\n }\n });\n\n core_defaults._set('horizontalBar', {\n hover: {\n mode: 'index',\n axis: 'y'\n },\n scales: {\n xAxes: [{\n type: 'linear',\n position: 'bottom'\n }],\n yAxes: [{\n type: 'category',\n position: 'left',\n offset: true,\n gridLines: {\n offsetGridLines: true\n }\n }]\n },\n elements: {\n rectangle: {\n borderSkipped: 'left'\n }\n },\n tooltips: {\n mode: 'index',\n axis: 'y'\n }\n });\n\n core_defaults._set('global', {\n datasets: {\n horizontalBar: {\n categoryPercentage: 0.8,\n barPercentage: 0.9\n }\n }\n });\n\n var controller_horizontalBar = controller_bar.extend({\n /**\n * @private\n */\n _getValueScaleId: function () {\n return this.getMeta().xAxisID;\n },\n\n /**\n * @private\n */\n _getIndexScaleId: function () {\n return this.getMeta().yAxisID;\n }\n });\n var valueOrDefault$6 = helpers$1.valueOrDefault;\n var resolve$2 = helpers$1.options.resolve;\n var isPointInArea = helpers$1.canvas._isPointInArea;\n\n core_defaults._set('line', {\n showLines: true,\n spanGaps: false,\n hover: {\n mode: 'label'\n },\n scales: {\n xAxes: [{\n type: 'category',\n id: 'x-axis-0'\n }],\n yAxes: [{\n type: 'linear',\n id: 'y-axis-0'\n }]\n }\n });\n\n function scaleClip(scale, halfBorderWidth) {\n var tickOpts = scale && scale.options.ticks || {};\n var reverse = tickOpts.reverse;\n var min = tickOpts.min === undefined ? halfBorderWidth : 0;\n var max = tickOpts.max === undefined ? halfBorderWidth : 0;\n return {\n start: reverse ? max : min,\n end: reverse ? min : max\n };\n }\n\n function defaultClip(xScale, yScale, borderWidth) {\n var halfBorderWidth = borderWidth / 2;\n var x = scaleClip(xScale, halfBorderWidth);\n var y = scaleClip(yScale, halfBorderWidth);\n return {\n top: y.end,\n right: x.end,\n bottom: y.start,\n left: x.start\n };\n }\n\n function toClip(value) {\n var t, r, b, l;\n\n if (helpers$1.isObject(value)) {\n t = value.top;\n r = value.right;\n b = value.bottom;\n l = value.left;\n } else {\n t = r = b = l = value;\n }\n\n return {\n top: t,\n right: r,\n bottom: b,\n left: l\n };\n }\n\n var controller_line = core_datasetController.extend({\n datasetElementType: elements.Line,\n dataElementType: elements.Point,\n\n /**\n * @private\n */\n _datasetElementOptions: ['backgroundColor', 'borderCapStyle', 'borderColor', 'borderDash', 'borderDashOffset', 'borderJoinStyle', 'borderWidth', 'cubicInterpolationMode', 'fill'],\n\n /**\n * @private\n */\n _dataElementOptions: {\n backgroundColor: 'pointBackgroundColor',\n borderColor: 'pointBorderColor',\n borderWidth: 'pointBorderWidth',\n hitRadius: 'pointHitRadius',\n hoverBackgroundColor: 'pointHoverBackgroundColor',\n hoverBorderColor: 'pointHoverBorderColor',\n hoverBorderWidth: 'pointHoverBorderWidth',\n hoverRadius: 'pointHoverRadius',\n pointStyle: 'pointStyle',\n radius: 'pointRadius',\n rotation: 'pointRotation'\n },\n update: function (reset) {\n var me = this;\n var meta = me.getMeta();\n var line = meta.dataset;\n var points = meta.data || [];\n var options = me.chart.options;\n var config = me._config;\n var showLine = me._showLine = valueOrDefault$6(config.showLine, options.showLines);\n var i, ilen;\n me._xScale = me.getScaleForId(meta.xAxisID);\n me._yScale = me.getScaleForId(meta.yAxisID); // Update Line\n\n if (showLine) {\n // Compatibility: If the properties are defined with only the old name, use those values\n if (config.tension !== undefined && config.lineTension === undefined) {\n config.lineTension = config.tension;\n } // Utility\n\n\n line._scale = me._yScale;\n line._datasetIndex = me.index; // Data\n\n line._children = points; // Model\n\n line._model = me._resolveDatasetElementOptions(line);\n line.pivot();\n } // Update Points\n\n\n for (i = 0, ilen = points.length; i < ilen; ++i) {\n me.updateElement(points[i], i, reset);\n }\n\n if (showLine && line._model.tension !== 0) {\n me.updateBezierControlPoints();\n } // Now pivot the point for animation\n\n\n for (i = 0, ilen = points.length; i < ilen; ++i) {\n points[i].pivot();\n }\n },\n updateElement: function (point, index, reset) {\n var me = this;\n var meta = me.getMeta();\n var custom = point.custom || {};\n var dataset = me.getDataset();\n var datasetIndex = me.index;\n var value = dataset.data[index];\n var xScale = me._xScale;\n var yScale = me._yScale;\n var lineModel = meta.dataset._model;\n var x, y;\n\n var options = me._resolveDataElementOptions(point, index);\n\n x = xScale.getPixelForValue(typeof value === 'object' ? value : NaN, index, datasetIndex);\n y = reset ? yScale.getBasePixel() : me.calculatePointY(value, index, datasetIndex); // Utility\n\n point._xScale = xScale;\n point._yScale = yScale;\n point._options = options;\n point._datasetIndex = datasetIndex;\n point._index = index; // Desired view properties\n\n point._model = {\n x: x,\n y: y,\n skip: custom.skip || isNaN(x) || isNaN(y),\n // Appearance\n radius: options.radius,\n pointStyle: options.pointStyle,\n rotation: options.rotation,\n backgroundColor: options.backgroundColor,\n borderColor: options.borderColor,\n borderWidth: options.borderWidth,\n tension: valueOrDefault$6(custom.tension, lineModel ? lineModel.tension : 0),\n steppedLine: lineModel ? lineModel.steppedLine : false,\n // Tooltip\n hitRadius: options.hitRadius\n };\n },\n\n /**\n * @private\n */\n _resolveDatasetElementOptions: function (element) {\n var me = this;\n var config = me._config;\n var custom = element.custom || {};\n var options = me.chart.options;\n var lineOptions = options.elements.line;\n\n var values = core_datasetController.prototype._resolveDatasetElementOptions.apply(me, arguments); // The default behavior of lines is to break at null values, according\n // to https://github.com/chartjs/Chart.js/issues/2435#issuecomment-216718158\n // This option gives lines the ability to span gaps\n\n\n values.spanGaps = valueOrDefault$6(config.spanGaps, options.spanGaps);\n values.tension = valueOrDefault$6(config.lineTension, lineOptions.tension);\n values.steppedLine = resolve$2([custom.steppedLine, config.steppedLine, lineOptions.stepped]);\n values.clip = toClip(valueOrDefault$6(config.clip, defaultClip(me._xScale, me._yScale, values.borderWidth)));\n return values;\n },\n calculatePointY: function (value, index, datasetIndex) {\n var me = this;\n var chart = me.chart;\n var yScale = me._yScale;\n var sumPos = 0;\n var sumNeg = 0;\n var i, ds, dsMeta, stackedRightValue, rightValue, metasets, ilen;\n\n if (yScale.options.stacked) {\n rightValue = +yScale.getRightValue(value);\n metasets = chart._getSortedVisibleDatasetMetas();\n ilen = metasets.length;\n\n for (i = 0; i < ilen; ++i) {\n dsMeta = metasets[i];\n\n if (dsMeta.index === datasetIndex) {\n break;\n }\n\n ds = chart.data.datasets[dsMeta.index];\n\n if (dsMeta.type === 'line' && dsMeta.yAxisID === yScale.id) {\n stackedRightValue = +yScale.getRightValue(ds.data[index]);\n\n if (stackedRightValue < 0) {\n sumNeg += stackedRightValue || 0;\n } else {\n sumPos += stackedRightValue || 0;\n }\n }\n }\n\n if (rightValue < 0) {\n return yScale.getPixelForValue(sumNeg + rightValue);\n }\n\n return yScale.getPixelForValue(sumPos + rightValue);\n }\n\n return yScale.getPixelForValue(value);\n },\n updateBezierControlPoints: function () {\n var me = this;\n var chart = me.chart;\n var meta = me.getMeta();\n var lineModel = meta.dataset._model;\n var area = chart.chartArea;\n var points = meta.data || [];\n var i, ilen, model, controlPoints; // Only consider points that are drawn in case the spanGaps option is used\n\n if (lineModel.spanGaps) {\n points = points.filter(function (pt) {\n return !pt._model.skip;\n });\n }\n\n function capControlPoint(pt, min, max) {\n return Math.max(Math.min(pt, max), min);\n }\n\n if (lineModel.cubicInterpolationMode === 'monotone') {\n helpers$1.splineCurveMonotone(points);\n } else {\n for (i = 0, ilen = points.length; i < ilen; ++i) {\n model = points[i]._model;\n controlPoints = helpers$1.splineCurve(helpers$1.previousItem(points, i)._model, model, helpers$1.nextItem(points, i)._model, lineModel.tension);\n model.controlPointPreviousX = controlPoints.previous.x;\n model.controlPointPreviousY = controlPoints.previous.y;\n model.controlPointNextX = controlPoints.next.x;\n model.controlPointNextY = controlPoints.next.y;\n }\n }\n\n if (chart.options.elements.line.capBezierPoints) {\n for (i = 0, ilen = points.length; i < ilen; ++i) {\n model = points[i]._model;\n\n if (isPointInArea(model, area)) {\n if (i > 0 && isPointInArea(points[i - 1]._model, area)) {\n model.controlPointPreviousX = capControlPoint(model.controlPointPreviousX, area.left, area.right);\n model.controlPointPreviousY = capControlPoint(model.controlPointPreviousY, area.top, area.bottom);\n }\n\n if (i < points.length - 1 && isPointInArea(points[i + 1]._model, area)) {\n model.controlPointNextX = capControlPoint(model.controlPointNextX, area.left, area.right);\n model.controlPointNextY = capControlPoint(model.controlPointNextY, area.top, area.bottom);\n }\n }\n }\n }\n },\n draw: function () {\n var me = this;\n var chart = me.chart;\n var meta = me.getMeta();\n var points = meta.data || [];\n var area = chart.chartArea;\n var canvas = chart.canvas;\n var i = 0;\n var ilen = points.length;\n var clip;\n\n if (me._showLine) {\n clip = meta.dataset._model.clip;\n helpers$1.canvas.clipArea(chart.ctx, {\n left: clip.left === false ? 0 : area.left - clip.left,\n right: clip.right === false ? canvas.width : area.right + clip.right,\n top: clip.top === false ? 0 : area.top - clip.top,\n bottom: clip.bottom === false ? canvas.height : area.bottom + clip.bottom\n });\n meta.dataset.draw();\n helpers$1.canvas.unclipArea(chart.ctx);\n } // Draw the points\n\n\n for (; i < ilen; ++i) {\n points[i].draw(area);\n }\n },\n\n /**\n * @protected\n */\n setHoverStyle: function (point) {\n var model = point._model;\n var options = point._options;\n var getHoverColor = helpers$1.getHoverColor;\n point.$previousStyle = {\n backgroundColor: model.backgroundColor,\n borderColor: model.borderColor,\n borderWidth: model.borderWidth,\n radius: model.radius\n };\n model.backgroundColor = valueOrDefault$6(options.hoverBackgroundColor, getHoverColor(options.backgroundColor));\n model.borderColor = valueOrDefault$6(options.hoverBorderColor, getHoverColor(options.borderColor));\n model.borderWidth = valueOrDefault$6(options.hoverBorderWidth, options.borderWidth);\n model.radius = valueOrDefault$6(options.hoverRadius, options.radius);\n }\n });\n var resolve$3 = helpers$1.options.resolve;\n\n core_defaults._set('polarArea', {\n scale: {\n type: 'radialLinear',\n angleLines: {\n display: false\n },\n gridLines: {\n circular: true\n },\n pointLabels: {\n display: false\n },\n ticks: {\n beginAtZero: true\n }\n },\n // Boolean - Whether to animate the rotation of the chart\n animation: {\n animateRotate: true,\n animateScale: true\n },\n startAngle: -0.5 * Math.PI,\n legendCallback: function (chart) {\n var list = document.createElement('ul');\n var data = chart.data;\n var datasets = data.datasets;\n var labels = data.labels;\n var i, ilen, listItem, listItemSpan;\n list.setAttribute('class', chart.id + '-legend');\n\n if (datasets.length) {\n for (i = 0, ilen = datasets[0].data.length; i < ilen; ++i) {\n listItem = list.appendChild(document.createElement('li'));\n listItemSpan = listItem.appendChild(document.createElement('span'));\n listItemSpan.style.backgroundColor = datasets[0].backgroundColor[i];\n\n if (labels[i]) {\n listItem.appendChild(document.createTextNode(labels[i]));\n }\n }\n }\n\n return list.outerHTML;\n },\n legend: {\n labels: {\n generateLabels: function (chart) {\n var data = chart.data;\n\n if (data.labels.length && data.datasets.length) {\n return data.labels.map(function (label, i) {\n var meta = chart.getDatasetMeta(0);\n var style = meta.controller.getStyle(i);\n return {\n text: label,\n fillStyle: style.backgroundColor,\n strokeStyle: style.borderColor,\n lineWidth: style.borderWidth,\n hidden: isNaN(data.datasets[0].data[i]) || meta.data[i].hidden,\n // Extra data used for toggling the correct item\n index: i\n };\n });\n }\n\n return [];\n }\n },\n onClick: function (e, legendItem) {\n var index = legendItem.index;\n var chart = this.chart;\n var i, ilen, meta;\n\n for (i = 0, ilen = (chart.data.datasets || []).length; i < ilen; ++i) {\n meta = chart.getDatasetMeta(i);\n meta.data[index].hidden = !meta.data[index].hidden;\n }\n\n chart.update();\n }\n },\n // Need to override these to give a nice default\n tooltips: {\n callbacks: {\n title: function () {\n return '';\n },\n label: function (item, data) {\n return data.labels[item.index] + ': ' + item.yLabel;\n }\n }\n }\n });\n\n var controller_polarArea = core_datasetController.extend({\n dataElementType: elements.Arc,\n linkScales: helpers$1.noop,\n\n /**\n * @private\n */\n _dataElementOptions: ['backgroundColor', 'borderColor', 'borderWidth', 'borderAlign', 'hoverBackgroundColor', 'hoverBorderColor', 'hoverBorderWidth'],\n\n /**\n * @private\n */\n _getIndexScaleId: function () {\n return this.chart.scale.id;\n },\n\n /**\n * @private\n */\n _getValueScaleId: function () {\n return this.chart.scale.id;\n },\n update: function (reset) {\n var me = this;\n var dataset = me.getDataset();\n var meta = me.getMeta();\n var start = me.chart.options.startAngle || 0;\n var starts = me._starts = [];\n var angles = me._angles = [];\n var arcs = meta.data;\n var i, ilen, angle;\n\n me._updateRadius();\n\n meta.count = me.countVisibleElements();\n\n for (i = 0, ilen = dataset.data.length; i < ilen; i++) {\n starts[i] = start;\n angle = me._computeAngle(i);\n angles[i] = angle;\n start += angle;\n }\n\n for (i = 0, ilen = arcs.length; i < ilen; ++i) {\n arcs[i]._options = me._resolveDataElementOptions(arcs[i], i);\n me.updateElement(arcs[i], i, reset);\n }\n },\n\n /**\n * @private\n */\n _updateRadius: function () {\n var me = this;\n var chart = me.chart;\n var chartArea = chart.chartArea;\n var opts = chart.options;\n var minSize = Math.min(chartArea.right - chartArea.left, chartArea.bottom - chartArea.top);\n chart.outerRadius = Math.max(minSize / 2, 0);\n chart.innerRadius = Math.max(opts.cutoutPercentage ? chart.outerRadius / 100 * opts.cutoutPercentage : 1, 0);\n chart.radiusLength = (chart.outerRadius - chart.innerRadius) / chart.getVisibleDatasetCount();\n me.outerRadius = chart.outerRadius - chart.radiusLength * me.index;\n me.innerRadius = me.outerRadius - chart.radiusLength;\n },\n updateElement: function (arc, index, reset) {\n var me = this;\n var chart = me.chart;\n var dataset = me.getDataset();\n var opts = chart.options;\n var animationOpts = opts.animation;\n var scale = chart.scale;\n var labels = chart.data.labels;\n var centerX = scale.xCenter;\n var centerY = scale.yCenter; // var negHalfPI = -0.5 * Math.PI;\n\n var datasetStartAngle = opts.startAngle;\n var distance = arc.hidden ? 0 : scale.getDistanceFromCenterForValue(dataset.data[index]);\n var startAngle = me._starts[index];\n var endAngle = startAngle + (arc.hidden ? 0 : me._angles[index]);\n var resetRadius = animationOpts.animateScale ? 0 : scale.getDistanceFromCenterForValue(dataset.data[index]);\n var options = arc._options || {};\n helpers$1.extend(arc, {\n // Utility\n _datasetIndex: me.index,\n _index: index,\n _scale: scale,\n // Desired view properties\n _model: {\n backgroundColor: options.backgroundColor,\n borderColor: options.borderColor,\n borderWidth: options.borderWidth,\n borderAlign: options.borderAlign,\n x: centerX,\n y: centerY,\n innerRadius: 0,\n outerRadius: reset ? resetRadius : distance,\n startAngle: reset && animationOpts.animateRotate ? datasetStartAngle : startAngle,\n endAngle: reset && animationOpts.animateRotate ? datasetStartAngle : endAngle,\n label: helpers$1.valueAtIndexOrDefault(labels, index, labels[index])\n }\n });\n arc.pivot();\n },\n countVisibleElements: function () {\n var dataset = this.getDataset();\n var meta = this.getMeta();\n var count = 0;\n helpers$1.each(meta.data, function (element, index) {\n if (!isNaN(dataset.data[index]) && !element.hidden) {\n count++;\n }\n });\n return count;\n },\n\n /**\n * @protected\n */\n setHoverStyle: function (arc) {\n var model = arc._model;\n var options = arc._options;\n var getHoverColor = helpers$1.getHoverColor;\n var valueOrDefault = helpers$1.valueOrDefault;\n arc.$previousStyle = {\n backgroundColor: model.backgroundColor,\n borderColor: model.borderColor,\n borderWidth: model.borderWidth\n };\n model.backgroundColor = valueOrDefault(options.hoverBackgroundColor, getHoverColor(options.backgroundColor));\n model.borderColor = valueOrDefault(options.hoverBorderColor, getHoverColor(options.borderColor));\n model.borderWidth = valueOrDefault(options.hoverBorderWidth, options.borderWidth);\n },\n\n /**\n * @private\n */\n _computeAngle: function (index) {\n var me = this;\n var count = this.getMeta().count;\n var dataset = me.getDataset();\n var meta = me.getMeta();\n\n if (isNaN(dataset.data[index]) || meta.data[index].hidden) {\n return 0;\n } // Scriptable options\n\n\n var context = {\n chart: me.chart,\n dataIndex: index,\n dataset: dataset,\n datasetIndex: me.index\n };\n return resolve$3([me.chart.options.elements.arc.angle, 2 * Math.PI / count], context, index);\n }\n });\n\n core_defaults._set('pie', helpers$1.clone(core_defaults.doughnut));\n\n core_defaults._set('pie', {\n cutoutPercentage: 0\n }); // Pie charts are Doughnut chart with different defaults\n\n\n var controller_pie = controller_doughnut;\n var valueOrDefault$7 = helpers$1.valueOrDefault;\n\n core_defaults._set('radar', {\n spanGaps: false,\n scale: {\n type: 'radialLinear'\n },\n elements: {\n line: {\n fill: 'start',\n tension: 0 // no bezier in radar\n\n }\n }\n });\n\n var controller_radar = core_datasetController.extend({\n datasetElementType: elements.Line,\n dataElementType: elements.Point,\n linkScales: helpers$1.noop,\n\n /**\n * @private\n */\n _datasetElementOptions: ['backgroundColor', 'borderWidth', 'borderColor', 'borderCapStyle', 'borderDash', 'borderDashOffset', 'borderJoinStyle', 'fill'],\n\n /**\n * @private\n */\n _dataElementOptions: {\n backgroundColor: 'pointBackgroundColor',\n borderColor: 'pointBorderColor',\n borderWidth: 'pointBorderWidth',\n hitRadius: 'pointHitRadius',\n hoverBackgroundColor: 'pointHoverBackgroundColor',\n hoverBorderColor: 'pointHoverBorderColor',\n hoverBorderWidth: 'pointHoverBorderWidth',\n hoverRadius: 'pointHoverRadius',\n pointStyle: 'pointStyle',\n radius: 'pointRadius',\n rotation: 'pointRotation'\n },\n\n /**\n * @private\n */\n _getIndexScaleId: function () {\n return this.chart.scale.id;\n },\n\n /**\n * @private\n */\n _getValueScaleId: function () {\n return this.chart.scale.id;\n },\n update: function (reset) {\n var me = this;\n var meta = me.getMeta();\n var line = meta.dataset;\n var points = meta.data || [];\n var scale = me.chart.scale;\n var config = me._config;\n var i, ilen; // Compatibility: If the properties are defined with only the old name, use those values\n\n if (config.tension !== undefined && config.lineTension === undefined) {\n config.lineTension = config.tension;\n } // Utility\n\n\n line._scale = scale;\n line._datasetIndex = me.index; // Data\n\n line._children = points;\n line._loop = true; // Model\n\n line._model = me._resolveDatasetElementOptions(line);\n line.pivot(); // Update Points\n\n for (i = 0, ilen = points.length; i < ilen; ++i) {\n me.updateElement(points[i], i, reset);\n } // Update bezier control points\n\n\n me.updateBezierControlPoints(); // Now pivot the point for animation\n\n for (i = 0, ilen = points.length; i < ilen; ++i) {\n points[i].pivot();\n }\n },\n updateElement: function (point, index, reset) {\n var me = this;\n var custom = point.custom || {};\n var dataset = me.getDataset();\n var scale = me.chart.scale;\n var pointPosition = scale.getPointPositionForValue(index, dataset.data[index]);\n\n var options = me._resolveDataElementOptions(point, index);\n\n var lineModel = me.getMeta().dataset._model;\n\n var x = reset ? scale.xCenter : pointPosition.x;\n var y = reset ? scale.yCenter : pointPosition.y; // Utility\n\n point._scale = scale;\n point._options = options;\n point._datasetIndex = me.index;\n point._index = index; // Desired view properties\n\n point._model = {\n x: x,\n // value not used in dataset scale, but we want a consistent API between scales\n y: y,\n skip: custom.skip || isNaN(x) || isNaN(y),\n // Appearance\n radius: options.radius,\n pointStyle: options.pointStyle,\n rotation: options.rotation,\n backgroundColor: options.backgroundColor,\n borderColor: options.borderColor,\n borderWidth: options.borderWidth,\n tension: valueOrDefault$7(custom.tension, lineModel ? lineModel.tension : 0),\n // Tooltip\n hitRadius: options.hitRadius\n };\n },\n\n /**\n * @private\n */\n _resolveDatasetElementOptions: function () {\n var me = this;\n var config = me._config;\n var options = me.chart.options;\n\n var values = core_datasetController.prototype._resolveDatasetElementOptions.apply(me, arguments);\n\n values.spanGaps = valueOrDefault$7(config.spanGaps, options.spanGaps);\n values.tension = valueOrDefault$7(config.lineTension, options.elements.line.tension);\n return values;\n },\n updateBezierControlPoints: function () {\n var me = this;\n var meta = me.getMeta();\n var area = me.chart.chartArea;\n var points = meta.data || [];\n var i, ilen, model, controlPoints; // Only consider points that are drawn in case the spanGaps option is used\n\n if (meta.dataset._model.spanGaps) {\n points = points.filter(function (pt) {\n return !pt._model.skip;\n });\n }\n\n function capControlPoint(pt, min, max) {\n return Math.max(Math.min(pt, max), min);\n }\n\n for (i = 0, ilen = points.length; i < ilen; ++i) {\n model = points[i]._model;\n controlPoints = helpers$1.splineCurve(helpers$1.previousItem(points, i, true)._model, model, helpers$1.nextItem(points, i, true)._model, model.tension); // Prevent the bezier going outside of the bounds of the graph\n\n model.controlPointPreviousX = capControlPoint(controlPoints.previous.x, area.left, area.right);\n model.controlPointPreviousY = capControlPoint(controlPoints.previous.y, area.top, area.bottom);\n model.controlPointNextX = capControlPoint(controlPoints.next.x, area.left, area.right);\n model.controlPointNextY = capControlPoint(controlPoints.next.y, area.top, area.bottom);\n }\n },\n setHoverStyle: function (point) {\n var model = point._model;\n var options = point._options;\n var getHoverColor = helpers$1.getHoverColor;\n point.$previousStyle = {\n backgroundColor: model.backgroundColor,\n borderColor: model.borderColor,\n borderWidth: model.borderWidth,\n radius: model.radius\n };\n model.backgroundColor = valueOrDefault$7(options.hoverBackgroundColor, getHoverColor(options.backgroundColor));\n model.borderColor = valueOrDefault$7(options.hoverBorderColor, getHoverColor(options.borderColor));\n model.borderWidth = valueOrDefault$7(options.hoverBorderWidth, options.borderWidth);\n model.radius = valueOrDefault$7(options.hoverRadius, options.radius);\n }\n });\n\n core_defaults._set('scatter', {\n hover: {\n mode: 'single'\n },\n scales: {\n xAxes: [{\n id: 'x-axis-1',\n // need an ID so datasets can reference the scale\n type: 'linear',\n // scatter should not use a category axis\n position: 'bottom'\n }],\n yAxes: [{\n id: 'y-axis-1',\n type: 'linear',\n position: 'left'\n }]\n },\n tooltips: {\n callbacks: {\n title: function () {\n return ''; // doesn't make sense for scatter since data are formatted as a point\n },\n label: function (item) {\n return '(' + item.xLabel + ', ' + item.yLabel + ')';\n }\n }\n }\n });\n\n core_defaults._set('global', {\n datasets: {\n scatter: {\n showLine: false\n }\n }\n }); // Scatter charts use line controllers\n\n\n var controller_scatter = controller_line; // NOTE export a map in which the key represents the controller type, not\n // the class, and so must be CamelCase in order to be correctly retrieved\n // by the controller in core.controller.js (`controllers[meta.type]`).\n\n var controllers = {\n bar: controller_bar,\n bubble: controller_bubble,\n doughnut: controller_doughnut,\n horizontalBar: controller_horizontalBar,\n line: controller_line,\n polarArea: controller_polarArea,\n pie: controller_pie,\n radar: controller_radar,\n scatter: controller_scatter\n };\n /**\n * Helper function to get relative position for an event\n * @param {Event|IEvent} event - The event to get the position for\n * @param {Chart} chart - The chart\n * @returns {object} the event position\n */\n\n function getRelativePosition(e, chart) {\n if (e.native) {\n return {\n x: e.x,\n y: e.y\n };\n }\n\n return helpers$1.getRelativePosition(e, chart);\n }\n /**\n * Helper function to traverse all of the visible elements in the chart\n * @param {Chart} chart - the chart\n * @param {function} handler - the callback to execute for each visible item\n */\n\n\n function parseVisibleItems(chart, handler) {\n var metasets = chart._getSortedVisibleDatasetMetas();\n\n var metadata, i, j, ilen, jlen, element;\n\n for (i = 0, ilen = metasets.length; i < ilen; ++i) {\n metadata = metasets[i].data;\n\n for (j = 0, jlen = metadata.length; j < jlen; ++j) {\n element = metadata[j];\n\n if (!element._view.skip) {\n handler(element);\n }\n }\n }\n }\n /**\n * Helper function to get the items that intersect the event position\n * @param {ChartElement[]} items - elements to filter\n * @param {object} position - the point to be nearest to\n * @return {ChartElement[]} the nearest items\n */\n\n\n function getIntersectItems(chart, position) {\n var elements = [];\n parseVisibleItems(chart, function (element) {\n if (element.inRange(position.x, position.y)) {\n elements.push(element);\n }\n });\n return elements;\n }\n /**\n * Helper function to get the items nearest to the event position considering all visible items in teh chart\n * @param {Chart} chart - the chart to look at elements from\n * @param {object} position - the point to be nearest to\n * @param {boolean} intersect - if true, only consider items that intersect the position\n * @param {function} distanceMetric - function to provide the distance between points\n * @return {ChartElement[]} the nearest items\n */\n\n\n function getNearestItems(chart, position, intersect, distanceMetric) {\n var minDistance = Number.POSITIVE_INFINITY;\n var nearestItems = [];\n parseVisibleItems(chart, function (element) {\n if (intersect && !element.inRange(position.x, position.y)) {\n return;\n }\n\n var center = element.getCenterPoint();\n var distance = distanceMetric(position, center);\n\n if (distance < minDistance) {\n nearestItems = [element];\n minDistance = distance;\n } else if (distance === minDistance) {\n // Can have multiple items at the same distance in which case we sort by size\n nearestItems.push(element);\n }\n });\n return nearestItems;\n }\n /**\n * Get a distance metric function for two points based on the\n * axis mode setting\n * @param {string} axis - the axis mode. x|y|xy\n */\n\n\n function getDistanceMetricForAxis(axis) {\n var useX = axis.indexOf('x') !== -1;\n var useY = axis.indexOf('y') !== -1;\n return function (pt1, pt2) {\n var deltaX = useX ? Math.abs(pt1.x - pt2.x) : 0;\n var deltaY = useY ? Math.abs(pt1.y - pt2.y) : 0;\n return Math.sqrt(Math.pow(deltaX, 2) + Math.pow(deltaY, 2));\n };\n }\n\n function indexMode(chart, e, options) {\n var position = getRelativePosition(e, chart); // Default axis for index mode is 'x' to match old behaviour\n\n options.axis = options.axis || 'x';\n var distanceMetric = getDistanceMetricForAxis(options.axis);\n var items = options.intersect ? getIntersectItems(chart, position) : getNearestItems(chart, position, false, distanceMetric);\n var elements = [];\n\n if (!items.length) {\n return [];\n }\n\n chart._getSortedVisibleDatasetMetas().forEach(function (meta) {\n var element = meta.data[items[0]._index]; // don't count items that are skipped (null data)\n\n if (element && !element._view.skip) {\n elements.push(element);\n }\n });\n\n return elements;\n }\n /**\n * @interface IInteractionOptions\n */\n\n /**\n * If true, only consider items that intersect the point\n * @name IInterfaceOptions#boolean\n * @type Boolean\n */\n\n /**\n * Contains interaction related functions\n * @namespace Chart.Interaction\n */\n\n\n var core_interaction = {\n // Helper function for different modes\n modes: {\n single: function (chart, e) {\n var position = getRelativePosition(e, chart);\n var elements = [];\n parseVisibleItems(chart, function (element) {\n if (element.inRange(position.x, position.y)) {\n elements.push(element);\n return elements;\n }\n });\n return elements.slice(0, 1);\n },\n\n /**\n * @function Chart.Interaction.modes.label\n * @deprecated since version 2.4.0\n * @todo remove at version 3\n * @private\n */\n label: indexMode,\n\n /**\n * Returns items at the same index. If the options.intersect parameter is true, we only return items if we intersect something\n * If the options.intersect mode is false, we find the nearest item and return the items at the same index as that item\n * @function Chart.Interaction.modes.index\n * @since v2.4.0\n * @param {Chart} chart - the chart we are returning items from\n * @param {Event} e - the event we are find things at\n * @param {IInteractionOptions} options - options to use during interaction\n * @return {Chart.Element[]} Array of elements that are under the point. If none are found, an empty array is returned\n */\n index: indexMode,\n\n /**\n * Returns items in the same dataset. If the options.intersect parameter is true, we only return items if we intersect something\n * If the options.intersect is false, we find the nearest item and return the items in that dataset\n * @function Chart.Interaction.modes.dataset\n * @param {Chart} chart - the chart we are returning items from\n * @param {Event} e - the event we are find things at\n * @param {IInteractionOptions} options - options to use during interaction\n * @return {Chart.Element[]} Array of elements that are under the point. If none are found, an empty array is returned\n */\n dataset: function (chart, e, options) {\n var position = getRelativePosition(e, chart);\n options.axis = options.axis || 'xy';\n var distanceMetric = getDistanceMetricForAxis(options.axis);\n var items = options.intersect ? getIntersectItems(chart, position) : getNearestItems(chart, position, false, distanceMetric);\n\n if (items.length > 0) {\n items = chart.getDatasetMeta(items[0]._datasetIndex).data;\n }\n\n return items;\n },\n\n /**\n * @function Chart.Interaction.modes.x-axis\n * @deprecated since version 2.4.0. Use index mode and intersect == true\n * @todo remove at version 3\n * @private\n */\n 'x-axis': function (chart, e) {\n return indexMode(chart, e, {\n intersect: false\n });\n },\n\n /**\n * Point mode returns all elements that hit test based on the event position\n * of the event\n * @function Chart.Interaction.modes.intersect\n * @param {Chart} chart - the chart we are returning items from\n * @param {Event} e - the event we are find things at\n * @return {Chart.Element[]} Array of elements that are under the point. If none are found, an empty array is returned\n */\n point: function (chart, e) {\n var position = getRelativePosition(e, chart);\n return getIntersectItems(chart, position);\n },\n\n /**\n * nearest mode returns the element closest to the point\n * @function Chart.Interaction.modes.intersect\n * @param {Chart} chart - the chart we are returning items from\n * @param {Event} e - the event we are find things at\n * @param {IInteractionOptions} options - options to use\n * @return {Chart.Element[]} Array of elements that are under the point. If none are found, an empty array is returned\n */\n nearest: function (chart, e, options) {\n var position = getRelativePosition(e, chart);\n options.axis = options.axis || 'xy';\n var distanceMetric = getDistanceMetricForAxis(options.axis);\n return getNearestItems(chart, position, options.intersect, distanceMetric);\n },\n\n /**\n * x mode returns the elements that hit-test at the current x coordinate\n * @function Chart.Interaction.modes.x\n * @param {Chart} chart - the chart we are returning items from\n * @param {Event} e - the event we are find things at\n * @param {IInteractionOptions} options - options to use\n * @return {Chart.Element[]} Array of elements that are under the point. If none are found, an empty array is returned\n */\n x: function (chart, e, options) {\n var position = getRelativePosition(e, chart);\n var items = [];\n var intersectsItem = false;\n parseVisibleItems(chart, function (element) {\n if (element.inXRange(position.x)) {\n items.push(element);\n }\n\n if (element.inRange(position.x, position.y)) {\n intersectsItem = true;\n }\n }); // If we want to trigger on an intersect and we don't have any items\n // that intersect the position, return nothing\n\n if (options.intersect && !intersectsItem) {\n items = [];\n }\n\n return items;\n },\n\n /**\n * y mode returns the elements that hit-test at the current y coordinate\n * @function Chart.Interaction.modes.y\n * @param {Chart} chart - the chart we are returning items from\n * @param {Event} e - the event we are find things at\n * @param {IInteractionOptions} options - options to use\n * @return {Chart.Element[]} Array of elements that are under the point. If none are found, an empty array is returned\n */\n y: function (chart, e, options) {\n var position = getRelativePosition(e, chart);\n var items = [];\n var intersectsItem = false;\n parseVisibleItems(chart, function (element) {\n if (element.inYRange(position.y)) {\n items.push(element);\n }\n\n if (element.inRange(position.x, position.y)) {\n intersectsItem = true;\n }\n }); // If we want to trigger on an intersect and we don't have any items\n // that intersect the position, return nothing\n\n if (options.intersect && !intersectsItem) {\n items = [];\n }\n\n return items;\n }\n }\n };\n var extend = helpers$1.extend;\n\n function filterByPosition(array, position) {\n return helpers$1.where(array, function (v) {\n return v.pos === position;\n });\n }\n\n function sortByWeight(array, reverse) {\n return array.sort(function (a, b) {\n var v0 = reverse ? b : a;\n var v1 = reverse ? a : b;\n return v0.weight === v1.weight ? v0.index - v1.index : v0.weight - v1.weight;\n });\n }\n\n function wrapBoxes(boxes) {\n var layoutBoxes = [];\n var i, ilen, box;\n\n for (i = 0, ilen = (boxes || []).length; i < ilen; ++i) {\n box = boxes[i];\n layoutBoxes.push({\n index: i,\n box: box,\n pos: box.position,\n horizontal: box.isHorizontal(),\n weight: box.weight\n });\n }\n\n return layoutBoxes;\n }\n\n function setLayoutDims(layouts, params) {\n var i, ilen, layout;\n\n for (i = 0, ilen = layouts.length; i < ilen; ++i) {\n layout = layouts[i]; // store width used instead of chartArea.w in fitBoxes\n\n layout.width = layout.horizontal ? layout.box.fullWidth && params.availableWidth : params.vBoxMaxWidth; // store height used instead of chartArea.h in fitBoxes\n\n layout.height = layout.horizontal && params.hBoxMaxHeight;\n }\n }\n\n function buildLayoutBoxes(boxes) {\n var layoutBoxes = wrapBoxes(boxes);\n var left = sortByWeight(filterByPosition(layoutBoxes, 'left'), true);\n var right = sortByWeight(filterByPosition(layoutBoxes, 'right'));\n var top = sortByWeight(filterByPosition(layoutBoxes, 'top'), true);\n var bottom = sortByWeight(filterByPosition(layoutBoxes, 'bottom'));\n return {\n leftAndTop: left.concat(top),\n rightAndBottom: right.concat(bottom),\n chartArea: filterByPosition(layoutBoxes, 'chartArea'),\n vertical: left.concat(right),\n horizontal: top.concat(bottom)\n };\n }\n\n function getCombinedMax(maxPadding, chartArea, a, b) {\n return Math.max(maxPadding[a], chartArea[a]) + Math.max(maxPadding[b], chartArea[b]);\n }\n\n function updateDims(chartArea, params, layout) {\n var box = layout.box;\n var maxPadding = chartArea.maxPadding;\n var newWidth, newHeight;\n\n if (layout.size) {\n // this layout was already counted for, lets first reduce old size\n chartArea[layout.pos] -= layout.size;\n }\n\n layout.size = layout.horizontal ? box.height : box.width;\n chartArea[layout.pos] += layout.size;\n\n if (box.getPadding) {\n var boxPadding = box.getPadding();\n maxPadding.top = Math.max(maxPadding.top, boxPadding.top);\n maxPadding.left = Math.max(maxPadding.left, boxPadding.left);\n maxPadding.bottom = Math.max(maxPadding.bottom, boxPadding.bottom);\n maxPadding.right = Math.max(maxPadding.right, boxPadding.right);\n }\n\n newWidth = params.outerWidth - getCombinedMax(maxPadding, chartArea, 'left', 'right');\n newHeight = params.outerHeight - getCombinedMax(maxPadding, chartArea, 'top', 'bottom');\n\n if (newWidth !== chartArea.w || newHeight !== chartArea.h) {\n chartArea.w = newWidth;\n chartArea.h = newHeight; // return true if chart area changed in layout's direction\n\n return layout.horizontal ? newWidth !== chartArea.w : newHeight !== chartArea.h;\n }\n }\n\n function handleMaxPadding(chartArea) {\n var maxPadding = chartArea.maxPadding;\n\n function updatePos(pos) {\n var change = Math.max(maxPadding[pos] - chartArea[pos], 0);\n chartArea[pos] += change;\n return change;\n }\n\n chartArea.y += updatePos('top');\n chartArea.x += updatePos('left');\n updatePos('right');\n updatePos('bottom');\n }\n\n function getMargins(horizontal, chartArea) {\n var maxPadding = chartArea.maxPadding;\n\n function marginForPositions(positions) {\n var margin = {\n left: 0,\n top: 0,\n right: 0,\n bottom: 0\n };\n positions.forEach(function (pos) {\n margin[pos] = Math.max(chartArea[pos], maxPadding[pos]);\n });\n return margin;\n }\n\n return horizontal ? marginForPositions(['left', 'right']) : marginForPositions(['top', 'bottom']);\n }\n\n function fitBoxes(boxes, chartArea, params) {\n var refitBoxes = [];\n var i, ilen, layout, box, refit, changed;\n\n for (i = 0, ilen = boxes.length; i < ilen; ++i) {\n layout = boxes[i];\n box = layout.box;\n box.update(layout.width || chartArea.w, layout.height || chartArea.h, getMargins(layout.horizontal, chartArea));\n\n if (updateDims(chartArea, params, layout)) {\n changed = true;\n\n if (refitBoxes.length) {\n // Dimensions changed and there were non full width boxes before this\n // -> we have to refit those\n refit = true;\n }\n }\n\n if (!box.fullWidth) {\n // fullWidth boxes don't need to be re-fitted in any case\n refitBoxes.push(layout);\n }\n }\n\n return refit ? fitBoxes(refitBoxes, chartArea, params) || changed : changed;\n }\n\n function placeBoxes(boxes, chartArea, params) {\n var userPadding = params.padding;\n var x = chartArea.x;\n var y = chartArea.y;\n var i, ilen, layout, box;\n\n for (i = 0, ilen = boxes.length; i < ilen; ++i) {\n layout = boxes[i];\n box = layout.box;\n\n if (layout.horizontal) {\n box.left = box.fullWidth ? userPadding.left : chartArea.left;\n box.right = box.fullWidth ? params.outerWidth - userPadding.right : chartArea.left + chartArea.w;\n box.top = y;\n box.bottom = y + box.height;\n box.width = box.right - box.left;\n y = box.bottom;\n } else {\n box.left = x;\n box.right = x + box.width;\n box.top = chartArea.top;\n box.bottom = chartArea.top + chartArea.h;\n box.height = box.bottom - box.top;\n x = box.right;\n }\n }\n\n chartArea.x = x;\n chartArea.y = y;\n }\n\n core_defaults._set('global', {\n layout: {\n padding: {\n top: 0,\n right: 0,\n bottom: 0,\n left: 0\n }\n }\n });\n /**\n * @interface ILayoutItem\n * @prop {string} position - The position of the item in the chart layout. Possible values are\n * 'left', 'top', 'right', 'bottom', and 'chartArea'\n * @prop {number} weight - The weight used to sort the item. Higher weights are further away from the chart area\n * @prop {boolean} fullWidth - if true, and the item is horizontal, then push vertical boxes down\n * @prop {function} isHorizontal - returns true if the layout item is horizontal (ie. top or bottom)\n * @prop {function} update - Takes two parameters: width and height. Returns size of item\n * @prop {function} getPadding - Returns an object with padding on the edges\n * @prop {number} width - Width of item. Must be valid after update()\n * @prop {number} height - Height of item. Must be valid after update()\n * @prop {number} left - Left edge of the item. Set by layout system and cannot be used in update\n * @prop {number} top - Top edge of the item. Set by layout system and cannot be used in update\n * @prop {number} right - Right edge of the item. Set by layout system and cannot be used in update\n * @prop {number} bottom - Bottom edge of the item. Set by layout system and cannot be used in update\n */\n // The layout service is very self explanatory. It's responsible for the layout within a chart.\n // Scales, Legends and Plugins all rely on the layout service and can easily register to be placed anywhere they need\n // It is this service's responsibility of carrying out that layout.\n\n\n var core_layouts = {\n defaults: {},\n\n /**\n * Register a box to a chart.\n * A box is simply a reference to an object that requires layout. eg. Scales, Legend, Title.\n * @param {Chart} chart - the chart to use\n * @param {ILayoutItem} item - the item to add to be layed out\n */\n addBox: function (chart, item) {\n if (!chart.boxes) {\n chart.boxes = [];\n } // initialize item with default values\n\n\n item.fullWidth = item.fullWidth || false;\n item.position = item.position || 'top';\n item.weight = item.weight || 0;\n\n item._layers = item._layers || function () {\n return [{\n z: 0,\n draw: function () {\n item.draw.apply(item, arguments);\n }\n }];\n };\n\n chart.boxes.push(item);\n },\n\n /**\n * Remove a layoutItem from a chart\n * @param {Chart} chart - the chart to remove the box from\n * @param {ILayoutItem} layoutItem - the item to remove from the layout\n */\n removeBox: function (chart, layoutItem) {\n var index = chart.boxes ? chart.boxes.indexOf(layoutItem) : -1;\n\n if (index !== -1) {\n chart.boxes.splice(index, 1);\n }\n },\n\n /**\n * Sets (or updates) options on the given `item`.\n * @param {Chart} chart - the chart in which the item lives (or will be added to)\n * @param {ILayoutItem} item - the item to configure with the given options\n * @param {object} options - the new item options.\n */\n configure: function (chart, item, options) {\n var props = ['fullWidth', 'position', 'weight'];\n var ilen = props.length;\n var i = 0;\n var prop;\n\n for (; i < ilen; ++i) {\n prop = props[i];\n\n if (options.hasOwnProperty(prop)) {\n item[prop] = options[prop];\n }\n }\n },\n\n /**\n * Fits boxes of the given chart into the given size by having each box measure itself\n * then running a fitting algorithm\n * @param {Chart} chart - the chart\n * @param {number} width - the width to fit into\n * @param {number} height - the height to fit into\n */\n update: function (chart, width, height) {\n if (!chart) {\n return;\n }\n\n var layoutOptions = chart.options.layout || {};\n var padding = helpers$1.options.toPadding(layoutOptions.padding);\n var availableWidth = width - padding.width;\n var availableHeight = height - padding.height;\n var boxes = buildLayoutBoxes(chart.boxes);\n var verticalBoxes = boxes.vertical;\n var horizontalBoxes = boxes.horizontal; // Essentially we now have any number of boxes on each of the 4 sides.\n // Our canvas looks like the following.\n // The areas L1 and L2 are the left axes. R1 is the right axis, T1 is the top axis and\n // B1 is the bottom axis\n // There are also 4 quadrant-like locations (left to right instead of clockwise) reserved for chart overlays\n // These locations are single-box locations only, when trying to register a chartArea location that is already taken,\n // an error will be thrown.\n //\n // |----------------------------------------------------|\n // | T1 (Full Width) |\n // |----------------------------------------------------|\n // | | | T2 | |\n // | |----|-------------------------------------|----|\n // | | | C1 | | C2 | |\n // | | |----| |----| |\n // | | | | |\n // | L1 | L2 | ChartArea (C0) | R1 |\n // | | | | |\n // | | |----| |----| |\n // | | | C3 | | C4 | |\n // | |----|-------------------------------------|----|\n // | | | B1 | |\n // |----------------------------------------------------|\n // | B2 (Full Width) |\n // |----------------------------------------------------|\n //\n\n var params = Object.freeze({\n outerWidth: width,\n outerHeight: height,\n padding: padding,\n availableWidth: availableWidth,\n vBoxMaxWidth: availableWidth / 2 / verticalBoxes.length,\n hBoxMaxHeight: availableHeight / 2\n });\n var chartArea = extend({\n maxPadding: extend({}, padding),\n w: availableWidth,\n h: availableHeight,\n x: padding.left,\n y: padding.top\n }, padding);\n setLayoutDims(verticalBoxes.concat(horizontalBoxes), params); // First fit vertical boxes\n\n fitBoxes(verticalBoxes, chartArea, params); // Then fit horizontal boxes\n\n if (fitBoxes(horizontalBoxes, chartArea, params)) {\n // if the area changed, re-fit vertical boxes\n fitBoxes(verticalBoxes, chartArea, params);\n }\n\n handleMaxPadding(chartArea); // Finally place the boxes to correct coordinates\n\n placeBoxes(boxes.leftAndTop, chartArea, params); // Move to opposite side of chart\n\n chartArea.x += chartArea.w;\n chartArea.y += chartArea.h;\n placeBoxes(boxes.rightAndBottom, chartArea, params);\n chart.chartArea = {\n left: chartArea.left,\n top: chartArea.top,\n right: chartArea.left + chartArea.w,\n bottom: chartArea.top + chartArea.h\n }; // Finally update boxes in chartArea (radial scale for example)\n\n helpers$1.each(boxes.chartArea, function (layout) {\n var box = layout.box;\n extend(box, chart.chartArea);\n box.update(chartArea.w, chartArea.h);\n });\n }\n };\n /**\n * Platform fallback implementation (minimal).\n * @see https://github.com/chartjs/Chart.js/pull/4591#issuecomment-319575939\n */\n\n var platform_basic = {\n acquireContext: function (item) {\n if (item && item.canvas) {\n // Support for any object associated to a canvas (including a context2d)\n item = item.canvas;\n }\n\n return item && item.getContext('2d') || null;\n }\n };\n var platform_dom = \"/*\\n * DOM element rendering detection\\n * https://davidwalsh.name/detect-node-insertion\\n */\\n@keyframes chartjs-render-animation {\\n\\tfrom { opacity: 0.99; }\\n\\tto { opacity: 1; }\\n}\\n\\n.chartjs-render-monitor {\\n\\tanimation: chartjs-render-animation 0.001s;\\n}\\n\\n/*\\n * DOM element resizing detection\\n * https://github.com/marcj/css-element-queries\\n */\\n.chartjs-size-monitor,\\n.chartjs-size-monitor-expand,\\n.chartjs-size-monitor-shrink {\\n\\tposition: absolute;\\n\\tdirection: ltr;\\n\\tleft: 0;\\n\\ttop: 0;\\n\\tright: 0;\\n\\tbottom: 0;\\n\\toverflow: hidden;\\n\\tpointer-events: none;\\n\\tvisibility: hidden;\\n\\tz-index: -1;\\n}\\n\\n.chartjs-size-monitor-expand > div {\\n\\tposition: absolute;\\n\\twidth: 1000000px;\\n\\theight: 1000000px;\\n\\tleft: 0;\\n\\ttop: 0;\\n}\\n\\n.chartjs-size-monitor-shrink > div {\\n\\tposition: absolute;\\n\\twidth: 200%;\\n\\theight: 200%;\\n\\tleft: 0;\\n\\ttop: 0;\\n}\\n\";\n var platform_dom$1 =\n /*#__PURE__*/\n Object.freeze({\n __proto__: null,\n 'default': platform_dom\n });\n var stylesheet = getCjsExportFromNamespace(platform_dom$1);\n var EXPANDO_KEY = '$chartjs';\n var CSS_PREFIX = 'chartjs-';\n var CSS_SIZE_MONITOR = CSS_PREFIX + 'size-monitor';\n var CSS_RENDER_MONITOR = CSS_PREFIX + 'render-monitor';\n var CSS_RENDER_ANIMATION = CSS_PREFIX + 'render-animation';\n var ANIMATION_START_EVENTS = ['animationstart', 'webkitAnimationStart'];\n /**\n * DOM event types -> Chart.js event types.\n * Note: only events with different types are mapped.\n * @see https://developer.mozilla.org/en-US/docs/Web/Events\n */\n\n var EVENT_TYPES = {\n touchstart: 'mousedown',\n touchmove: 'mousemove',\n touchend: 'mouseup',\n pointerenter: 'mouseenter',\n pointerdown: 'mousedown',\n pointermove: 'mousemove',\n pointerup: 'mouseup',\n pointerleave: 'mouseout',\n pointerout: 'mouseout'\n };\n /**\n * The \"used\" size is the final value of a dimension property after all calculations have\n * been performed. This method uses the computed style of `element` but returns undefined\n * if the computed style is not expressed in pixels. That can happen in some cases where\n * `element` has a size relative to its parent and this last one is not yet displayed,\n * for example because of `display: none` on a parent node.\n * @see https://developer.mozilla.org/en-US/docs/Web/CSS/used_value\n * @returns {number} Size in pixels or undefined if unknown.\n */\n\n function readUsedSize(element, property) {\n var value = helpers$1.getStyle(element, property);\n var matches = value && value.match(/^(\\d+)(\\.\\d+)?px$/);\n return matches ? Number(matches[1]) : undefined;\n }\n /**\n * Initializes the canvas style and render size without modifying the canvas display size,\n * since responsiveness is handled by the controller.resize() method. The config is used\n * to determine the aspect ratio to apply in case no explicit height has been specified.\n */\n\n\n function initCanvas(canvas, config) {\n var style = canvas.style; // NOTE(SB) canvas.getAttribute('width') !== canvas.width: in the first case it\n // returns null or '' if no explicit value has been set to the canvas attribute.\n\n var renderHeight = canvas.getAttribute('height');\n var renderWidth = canvas.getAttribute('width'); // Chart.js modifies some canvas values that we want to restore on destroy\n\n canvas[EXPANDO_KEY] = {\n initial: {\n height: renderHeight,\n width: renderWidth,\n style: {\n display: style.display,\n height: style.height,\n width: style.width\n }\n }\n }; // Force canvas to display as block to avoid extra space caused by inline\n // elements, which would interfere with the responsive resize process.\n // https://github.com/chartjs/Chart.js/issues/2538\n\n style.display = style.display || 'block';\n\n if (renderWidth === null || renderWidth === '') {\n var displayWidth = readUsedSize(canvas, 'width');\n\n if (displayWidth !== undefined) {\n canvas.width = displayWidth;\n }\n }\n\n if (renderHeight === null || renderHeight === '') {\n if (canvas.style.height === '') {\n // If no explicit render height and style height, let's apply the aspect ratio,\n // which one can be specified by the user but also by charts as default option\n // (i.e. options.aspectRatio). If not specified, use canvas aspect ratio of 2.\n canvas.height = canvas.width / (config.options.aspectRatio || 2);\n } else {\n var displayHeight = readUsedSize(canvas, 'height');\n\n if (displayWidth !== undefined) {\n canvas.height = displayHeight;\n }\n }\n }\n\n return canvas;\n }\n /**\n * Detects support for options object argument in addEventListener.\n * https://developer.mozilla.org/en-US/docs/Web/API/EventTarget/addEventListener#Safely_detecting_option_support\n * @private\n */\n\n\n var supportsEventListenerOptions = function () {\n var supports = false;\n\n try {\n var options = Object.defineProperty({}, 'passive', {\n // eslint-disable-next-line getter-return\n get: function () {\n supports = true;\n }\n });\n window.addEventListener('e', null, options);\n } catch (e) {// continue regardless of error\n }\n\n return supports;\n }(); // Default passive to true as expected by Chrome for 'touchstart' and 'touchend' events.\n // https://github.com/chartjs/Chart.js/issues/4287\n\n\n var eventListenerOptions = supportsEventListenerOptions ? {\n passive: true\n } : false;\n\n function addListener(node, type, listener) {\n node.addEventListener(type, listener, eventListenerOptions);\n }\n\n function removeListener(node, type, listener) {\n node.removeEventListener(type, listener, eventListenerOptions);\n }\n\n function createEvent(type, chart, x, y, nativeEvent) {\n return {\n type: type,\n chart: chart,\n native: nativeEvent || null,\n x: x !== undefined ? x : null,\n y: y !== undefined ? y : null\n };\n }\n\n function fromNativeEvent(event, chart) {\n var type = EVENT_TYPES[event.type] || event.type;\n var pos = helpers$1.getRelativePosition(event, chart);\n return createEvent(type, chart, pos.x, pos.y, event);\n }\n\n function throttled(fn, thisArg) {\n var ticking = false;\n var args = [];\n return function () {\n args = Array.prototype.slice.call(arguments);\n thisArg = thisArg || this;\n\n if (!ticking) {\n ticking = true;\n helpers$1.requestAnimFrame.call(window, function () {\n ticking = false;\n fn.apply(thisArg, args);\n });\n }\n };\n }\n\n function createDiv(cls) {\n var el = document.createElement('div');\n el.className = cls || '';\n return el;\n } // Implementation based on https://github.com/marcj/css-element-queries\n\n\n function createResizer(handler) {\n var maxSize = 1000000; // NOTE(SB) Don't use innerHTML because it could be considered unsafe.\n // https://github.com/chartjs/Chart.js/issues/5902\n\n var resizer = createDiv(CSS_SIZE_MONITOR);\n var expand = createDiv(CSS_SIZE_MONITOR + '-expand');\n var shrink = createDiv(CSS_SIZE_MONITOR + '-shrink');\n expand.appendChild(createDiv());\n shrink.appendChild(createDiv());\n resizer.appendChild(expand);\n resizer.appendChild(shrink);\n\n resizer._reset = function () {\n expand.scrollLeft = maxSize;\n expand.scrollTop = maxSize;\n shrink.scrollLeft = maxSize;\n shrink.scrollTop = maxSize;\n };\n\n var onScroll = function () {\n resizer._reset();\n\n handler();\n };\n\n addListener(expand, 'scroll', onScroll.bind(expand, 'expand'));\n addListener(shrink, 'scroll', onScroll.bind(shrink, 'shrink'));\n return resizer;\n } // https://davidwalsh.name/detect-node-insertion\n\n\n function watchForRender(node, handler) {\n var expando = node[EXPANDO_KEY] || (node[EXPANDO_KEY] = {});\n\n var proxy = expando.renderProxy = function (e) {\n if (e.animationName === CSS_RENDER_ANIMATION) {\n handler();\n }\n };\n\n helpers$1.each(ANIMATION_START_EVENTS, function (type) {\n addListener(node, type, proxy);\n }); // #4737: Chrome might skip the CSS animation when the CSS_RENDER_MONITOR class\n // is removed then added back immediately (same animation frame?). Accessing the\n // `offsetParent` property will force a reflow and re-evaluate the CSS animation.\n // https://gist.github.com/paulirish/5d52fb081b3570c81e3a#box-metrics\n // https://github.com/chartjs/Chart.js/issues/4737\n\n expando.reflow = !!node.offsetParent;\n node.classList.add(CSS_RENDER_MONITOR);\n }\n\n function unwatchForRender(node) {\n var expando = node[EXPANDO_KEY] || {};\n var proxy = expando.renderProxy;\n\n if (proxy) {\n helpers$1.each(ANIMATION_START_EVENTS, function (type) {\n removeListener(node, type, proxy);\n });\n delete expando.renderProxy;\n }\n\n node.classList.remove(CSS_RENDER_MONITOR);\n }\n\n function addResizeListener(node, listener, chart) {\n var expando = node[EXPANDO_KEY] || (node[EXPANDO_KEY] = {}); // Let's keep track of this added resizer and thus avoid DOM query when removing it.\n\n var resizer = expando.resizer = createResizer(throttled(function () {\n if (expando.resizer) {\n var container = chart.options.maintainAspectRatio && node.parentNode;\n var w = container ? container.clientWidth : 0;\n listener(createEvent('resize', chart));\n\n if (container && container.clientWidth < w && chart.canvas) {\n // If the container size shrank during chart resize, let's assume\n // scrollbar appeared. So we resize again with the scrollbar visible -\n // effectively making chart smaller and the scrollbar hidden again.\n // Because we are inside `throttled`, and currently `ticking`, scroll\n // events are ignored during this whole 2 resize process.\n // If we assumed wrong and something else happened, we are resizing\n // twice in a frame (potential performance issue)\n listener(createEvent('resize', chart));\n }\n }\n })); // The resizer needs to be attached to the node parent, so we first need to be\n // sure that `node` is attached to the DOM before injecting the resizer element.\n\n watchForRender(node, function () {\n if (expando.resizer) {\n var container = node.parentNode;\n\n if (container && container !== resizer.parentNode) {\n container.insertBefore(resizer, container.firstChild);\n } // The container size might have changed, let's reset the resizer state.\n\n\n resizer._reset();\n }\n });\n }\n\n function removeResizeListener(node) {\n var expando = node[EXPANDO_KEY] || {};\n var resizer = expando.resizer;\n delete expando.resizer;\n unwatchForRender(node);\n\n if (resizer && resizer.parentNode) {\n resizer.parentNode.removeChild(resizer);\n }\n }\n /**\n * Injects CSS styles inline if the styles are not already present.\n * @param {HTMLDocument|ShadowRoot} rootNode - the node to contain the plotly-logomark'}};},{}],716:[function(_dereq_,module,exports){/**\n* Copyright 2012-2020, Plotly, Inc.\n* All rights reserved.\n*\n* This source code is licensed under the MIT license found in the\n* LICENSE file in the root directory of this source tree.\n*/'use strict';/**\n * Determine the position anchor property of x/y xanchor/yanchor components.\n *\n * - values < 1/3 align the low side at that fraction,\n * - values [1/3, 2/3] align the center at that fraction,\n * - values > 2/3 align the right at that fraction.\n */exports.isLeftAnchor=function isLeftAnchor(opts){return opts.xanchor==='left'||opts.xanchor==='auto'&&opts.x<=1/3;};exports.isCenterAnchor=function isCenterAnchor(opts){return opts.xanchor==='center'||opts.xanchor==='auto'&&opts.x>1/3&&opts.x<2/3;};exports.isRightAnchor=function isRightAnchor(opts){return opts.xanchor==='right'||opts.xanchor==='auto'&&opts.x>=2/3;};exports.isTopAnchor=function isTopAnchor(opts){return opts.yanchor==='top'||opts.yanchor==='auto'&&opts.y>=2/3;};exports.isMiddleAnchor=function isMiddleAnchor(opts){return opts.yanchor==='middle'||opts.yanchor==='auto'&&opts.y>1/3&&opts.y<2/3;};exports.isBottomAnchor=function isBottomAnchor(opts){return opts.yanchor==='bottom'||opts.yanchor==='auto'&&opts.y<=1/3;};},{}],717:[function(_dereq_,module,exports){/**\n* Copyright 2012-2020, Plotly, Inc.\n* All rights reserved.\n*\n* This source code is licensed under the MIT license found in the\n* LICENSE file in the root directory of this source tree.\n*/'use strict';var modModule=_dereq_('./mod');var mod=modModule.mod;var modHalf=modModule.modHalf;var PI=Math.PI;var twoPI=2*PI;function deg2rad(deg){return deg/180*PI;}function rad2deg(rad){return rad/PI*180;}/**\n * is sector a full circle?\n * ... this comes up a lot in SVG path-drawing routines\n *\n * N.B. we consider all sectors that span more that 2pi 'full' circles\n *\n * @param {2-item array} aBnds : angular bounds in *radians*\n * @return {boolean}\n */function isFullCircle(aBnds){return Math.abs(aBnds[1]-aBnds[0])>twoPI-1e-14;}/**\n * angular delta between angle 'a' and 'b'\n * solution taken from: https://stackoverflow.com/a/2007279\n *\n * @param {number} a : first angle in *radians*\n * @param {number} b : second angle in *radians*\n * @return {number} angular delta in *radians*\n */function angleDelta(a,b){return modHalf(b-a,twoPI);}/**\n * angular distance between angle 'a' and 'b'\n *\n * @param {number} a : first angle in *radians*\n * @param {number} b : second angle in *radians*\n * @return {number} angular distance in *radians*\n */function angleDist(a,b){return Math.abs(angleDelta(a,b));}/**\n * is angle inside sector?\n *\n * @param {number} a : angle to test in *radians*\n * @param {2-item array} aBnds : sector's angular bounds in *radians*\n * @param {boolean}\n */function isAngleInsideSector(a,aBnds){if(isFullCircle(aBnds))return true;var s0,s1;if(aBnds[0]s1)s1+=twoPI;var a0=mod(a,twoPI);var a1=a0+twoPI;return a0>=s0&&a0<=s1||a1>=s0&&a1<=s1;}/**\n * is pt (r,a) inside sector?\n *\n * @param {number} r : pt's radial coordinate\n * @param {number} a : pt's angular coordinate in *radians*\n * @param {2-item array} rBnds : sector's radial bounds\n * @param {2-item array} aBnds : sector's angular bounds in *radians*\n * @return {boolean}\n */function isPtInsideSector(r,a,rBnds,aBnds){if(!isAngleInsideSector(a,aBnds))return false;var r0,r1;if(rBnds[0]=r0&&r<=r1;}// common to pathArc, pathSector and pathAnnulus\nfunction _path(r0,r1,a0,a1,cx,cy,isClosed){cx=cx||0;cy=cy||0;var isCircle=isFullCircle([a0,a1]);var aStart,aMid,aEnd;var rStart,rEnd;if(isCircle){aStart=0;aMid=PI;aEnd=twoPI;}else{if(a0opts.max){propOut.set(dflt);}else propOut.set(+v);}},integer:{coerceFunction:function(v,propOut,dflt,opts){if(v%1||!isNumeric(v)||opts.min!==undefined&&vopts.max){propOut.set(dflt);}else propOut.set(+v);}},string:{// TODO 'values shouldn't be in there (edge case: 'dash' in Scatter)\ncoerceFunction:function(v,propOut,dflt,opts){if(typeof v!=='string'){var okToCoerce=typeof v==='number';if(opts.strict===true||!okToCoerce)propOut.set(dflt);else propOut.set(String(v));}else if(opts.noBlank&&!v)propOut.set(dflt);else propOut.set(v);}},color:{coerceFunction:function(v,propOut,dflt){if(tinycolor(v).isValid())propOut.set(v);else propOut.set(dflt);}},colorlist:{coerceFunction:function(v,propOut,dflt){function isColor(color){return tinycolor(color).isValid();}if(!Array.isArray(v)||!v.length)propOut.set(dflt);else if(v.every(isColor))propOut.set(v);else propOut.set(dflt);}},colorscale:{coerceFunction:function(v,propOut,dflt){propOut.set(colorscales.get(v,dflt));}},angle:{coerceFunction:function(v,propOut,dflt){if(v==='auto')propOut.set('auto');else if(!isNumeric(v))propOut.set(dflt);else propOut.set(modHalf(+v,360));}},subplotid:{coerceFunction:function(v,propOut,dflt,opts){var regex=opts.regex||counterRegex(dflt);if(typeof v==='string'&®ex.test(v)){propOut.set(v);return;}propOut.set(dflt);},validateFunction:function(v,opts){var dflt=opts.dflt;if(v===dflt)return true;if(typeof v!=='string')return false;if(counterRegex(dflt).test(v))return true;return false;}},flaglist:{coerceFunction:function(v,propOut,dflt,opts){if(typeof v!=='string'){propOut.set(dflt);return;}if((opts.extras||[]).indexOf(v)!==-1){propOut.set(v);return;}var vParts=v.split('+');var i=0;while(iHH:MM:SS.sss?\n *\n * : space (our normal standard) or T or t (ISO-8601)\n * : Z, z, or [+\\-]HH:?MM and we THROW IT AWAY\n * this format comes from https://tools.ietf.org/html/rfc3339#section-5.6\n * but we allow it even with a space as the separator\n *\n * May truncate after any full field, and sss can be any length\n * even >3 digits, though javascript dates truncate to milliseconds,\n * we keep as much as javascript numeric precision can hold, but we only\n * report back up to 100 microsecond precision, because most dates support\n * this precision (close to 1970 support more, very far away support less)\n *\n * Expanded to support negative years to -9999 but you must always\n * give 4 digits, except for 2-digit positive years which we assume are\n * near the present time.\n * Note that we follow ISO 8601:2004: there *is* a year 0, which\n * is 1BC/BCE, and -1===2BC etc.\n *\n * World calendars: not all of these *have* agreed extensions to this full range,\n * if you have another calendar system but want a date range outside its validity,\n * you can use a gregorian date string prefixed with 'G' or 'g'.\n *\n * Where to cut off 2-digit years between 1900s and 2000s?\n * from http://support.microsoft.com/kb/244664:\n * 1930-2029 (the most retro of all...)\n * but in my mac chrome from eg. d=new Date(Date.parse('8/19/50')):\n * 1950-2049\n * by Java, from http://stackoverflow.com/questions/2024273/:\n * now-80 - now+19\n * or FileMaker Pro, from\n * http://www.filemaker.com/12help/html/add_view_data.4.21.html:\n * now-70 - now+29\n * but python strptime etc, via\n * http://docs.python.org/py3k/library/time.html:\n * 1969-2068 (super forward-looking, but static, not sliding!)\n *\n * lets go with now-70 to now+29, and if anyone runs into this problem\n * they can learn the hard way not to use 2-digit years, as no choice we\n * make now will cover all possibilities. mostly this will all be taken\n * care of in initial parsing, should only be an issue for hand-entered data\n * currently (2016) this range is:\n * 1946-2045\n */exports.dateTime2ms=function(s,calendar){// first check if s is a date object\nif(exports.isJSDate(s)){// Convert to the UTC milliseconds that give the same\n// hours as this date has in the local timezone\nvar tzOffset=s.getTimezoneOffset()*ONEMIN;var offsetTweak=(s.getUTCMinutes()-s.getMinutes())*ONEMIN+(s.getUTCSeconds()-s.getSeconds())*ONESEC+(s.getUTCMilliseconds()-s.getMilliseconds());if(offsetTweak){var comb=3*ONEMIN;tzOffset=tzOffset-comb/2+mod(offsetTweak-tzOffset+comb/2,comb);}s=Number(s)-tzOffset;if(s>=MIN_MS&&s<=MAX_MS)return s;return BADNUM;}// otherwise only accept strings and numbers\nif(typeof s!=='string'&&typeof s!=='number')return BADNUM;s=String(s);var isWorld=isWorldCalendar(calendar);// to handle out-of-range dates in international calendars, accept\n// 'G' as a prefix to force the built-in gregorian calendar.\nvar s0=s.charAt(0);if(isWorld&&(s0==='G'||s0==='g')){s=s.substr(1);calendar='';}var isChinese=isWorld&&calendar.substr(0,7)==='chinese';var match=s.match(isChinese?DATETIME_REGEXP_CN:DATETIME_REGEXP);if(!match)return BADNUM;var y=match[1];var m=match[3]||'1';var d=Number(match[5]||1);var H=Number(match[7]||0);var M=Number(match[9]||0);var S=Number(match[11]||0);if(isWorld){// disallow 2-digit years for world calendars\nif(y.length===2)return BADNUM;y=Number(y);var cDate;try{var calInstance=Registry.getComponentMethod('calendars','getCal')(calendar);if(isChinese){var isIntercalary=m.charAt(m.length-1)==='i';m=parseInt(m,10);cDate=calInstance.newDate(y,calInstance.toMonthIndex(y,m,isIntercalary),d);}else{cDate=calInstance.newDate(y,Number(m),d);}}catch(e){return BADNUM;}// Invalid ... date\nif(!cDate)return BADNUM;return(cDate.toJD()-EPOCHJD)*ONEDAY+H*ONEHOUR+M*ONEMIN+S*ONESEC;}if(y.length===2){y=(Number(y)+2000-YFIRST)%100+YFIRST;}else y=Number(y);// new Date uses months from 0; subtract 1 here just so we\n// don't have to do it again during the validity test below\nm-=1;// javascript takes new Date(0..99,m,d) to mean 1900-1999, so\n// to support years 0-99 we need to use setFullYear explicitly\n// Note that 2000 is a leap year.\nvar date=new Date(Date.UTC(2000,m,d,H,M));date.setUTCFullYear(y);if(date.getUTCMonth()!==m)return BADNUM;if(date.getUTCDate()!==d)return BADNUM;return date.getTime()+S*ONESEC;};MIN_MS=exports.MIN_MS=exports.dateTime2ms('-9999');MAX_MS=exports.MAX_MS=exports.dateTime2ms('9999-12-31 23:59:59.9999');// is string s a date? (see above)\nexports.isDateTime=function(s,calendar){return exports.dateTime2ms(s,calendar)!==BADNUM;};// pad a number with zeroes, to given # of digits before the decimal point\nfunction lpad(val,digits){return String(val+Math.pow(10,digits)).substr(1);}/**\n * Turn ms into string of the form YYYY-mm-dd HH:MM:SS.ssss\n * Crop any trailing zeros in time, except never stop right after hours\n * (we could choose to crop '-01' from date too but for now we always\n * show the whole date)\n * Optional range r is the data range that applies, also in ms.\n * If rng is big, the later parts of time will be omitted\n */var NINETYDAYS=90*ONEDAY;var THREEHOURS=3*ONEHOUR;var FIVEMIN=5*ONEMIN;exports.ms2DateTime=function(ms,r,calendar){if(typeof ms!=='number'||!(ms>=MIN_MS&&ms<=MAX_MS))return BADNUM;if(!r)r=0;var msecTenths=Math.floor(mod(ms+0.05,1)*10);var msRounded=Math.round(ms-msecTenths/10);var dateStr,h,m,s,msec10,d;if(isWorldCalendar(calendar)){var dateJD=Math.floor(msRounded/ONEDAY)+EPOCHJD;var timeMs=Math.floor(mod(ms,ONEDAY));try{dateStr=Registry.getComponentMethod('calendars','getCal')(calendar).fromJD(dateJD).formatDate('yyyy-mm-dd');}catch(e){// invalid date in this calendar - fall back to Gyyyy-mm-dd\ndateStr=utcFormat('G%Y-%m-%d')(new Date(msRounded));}// yyyy does NOT guarantee 4-digit years. YYYY mostly does, but does\n// other things for a few calendars, so we can't trust it. Just pad\n// it manually (after the '-' if there is one)\nif(dateStr.charAt(0)==='-'){while(dateStr.length<11)dateStr='-0'+dateStr.substr(1);}else{while(dateStr.length<10)dateStr='0'+dateStr;}// TODO: if this is faster, we could use this block for extracting\n// the time components of regular gregorian too\nh=r=MIN_MS+ONEDAY&&ms<=MAX_MS-ONEDAY))return BADNUM;var msecTenths=Math.floor(mod(ms+0.05,1)*10);var d=new Date(Math.round(ms-msecTenths/10));var dateStr=d3.time.format('%Y-%m-%d')(d);var h=d.getHours();var m=d.getMinutes();var s=d.getSeconds();var msec10=d.getUTCMilliseconds()*10+msecTenths;return includeTime(dateStr,h,m,s,msec10);};function includeTime(dateStr,h,m,s,msec10){// include each part that has nonzero data in or after it\nif(h||m||s||msec10){dateStr+=' '+lpad(h,2)+':'+lpad(m,2);if(s||msec10){dateStr+=':'+lpad(s,2);if(msec10){var digits=4;while(msec10%10===0){digits-=1;msec10/=10;}dateStr+='.'+lpad(msec10,digits);}}}return dateStr;}// normalize date format to date string, in case it starts as\n// a Date object or milliseconds\n// optional dflt is the return value if cleaning fails\nexports.cleanDate=function(v,dflt,calendar){// let us use cleanDate to provide a missing default without an error\nif(v===BADNUM)return dflt;if(exports.isJSDate(v)||typeof v==='number'&&isFinite(v)){// do not allow milliseconds (old) or jsdate objects (inherently\n// described as gregorian dates) with world calendars\nif(isWorldCalendar(calendar)){Loggers.error('JS Dates and milliseconds are incompatible with world calendars',v);return dflt;}// NOTE: if someone puts in a year as a number rather than a string,\n// this will mistakenly convert it thinking it's milliseconds from 1970\n// that is: '2012' -> Jan. 1, 2012, but 2012 -> 2012 epoch milliseconds\nv=exports.ms2DateTimeLocal(+v);if(!v&&dflt!==undefined)return dflt;}else if(!exports.isDateTime(v,calendar)){Loggers.error('unrecognized date',v);return dflt;}return v;};/*\n * Date formatting for ticks and hovertext\n */ /*\n * modDateFormat: Support world calendars, and add one item to\n * d3's vocabulary:\n * %{n}f where n is the max number of digits of fractional seconds\n */var fracMatch=/%\\d?f/g;function modDateFormat(fmt,x,formatter,calendar){fmt=fmt.replace(fracMatch,function(match){var digits=Math.min(+match.charAt(1)||6,6);var fracSecs=(x/1000%1+2).toFixed(digits).substr(2).replace(/0+$/,'')||'0';return fracSecs;});var d=new Date(Math.floor(x+0.05));if(isWorldCalendar(calendar)){try{fmt=Registry.getComponentMethod('calendars','worldCalFmt')(fmt,x,calendar);}catch(e){return'Invalid';}}return formatter(fmt)(d);}/*\n * formatTime: create a time string from:\n * x: milliseconds\n * tr: tickround ('M', 'S', or # digits)\n * only supports UTC times (where every day is 24 hours and 0 is at midnight)\n */var MAXSECONDS=[59,59.9,59.99,59.999,59.9999];function formatTime(x,tr){var timePart=mod(x+0.05,ONEDAY);var timeStr=lpad(Math.floor(timePart/ONEHOUR),2)+':'+lpad(mod(Math.floor(timePart/ONEMIN),60),2);if(tr!=='M'){if(!isNumeric(tr))tr=0;// should only be 'S'\n/*\n * this is a weird one - and shouldn't come up unless people\n * monkey with tick0 in weird ways, but we need to do something!\n * IN PARTICULAR we had better not display garbage (see below)\n * for numbers we always round to the nearest increment of the\n * precision we're showing, and this seems like the right way to\n * handle seconds and milliseconds, as they have a decimal point\n * and people will interpret that to mean rounding like numbers.\n * but for larger increments we floor the value: it's always\n * 2013 until the ball drops on the new year. We could argue about\n * which field it is where we start rounding (should 12:08:59\n * round to 12:09 if we're stopping at minutes?) but for now I'll\n * say we round seconds but floor everything else. BUT that means\n * we need to never round up to 60 seconds, ie 23:59:60\n */var sec=Math.min(mod(x/ONESEC,60),MAXSECONDS[tr]);var secStr=(100+sec).toFixed(tr).substr(1);if(tr>0){secStr=secStr.replace(/0+$/,'').replace(/[\\.]$/,'');}timeStr+=':'+secStr;}return timeStr;}/*\n * formatDate: turn a date into tick or hover label text.\n *\n * x: milliseconds, the value to convert\n * fmt: optional, an explicit format string (d3 format, even for world calendars)\n * tr: tickround ('y', 'm', 'd', 'M', 'S', or # digits)\n * used if no explicit fmt is provided\n * formatter: locale-aware d3 date formatter for standard gregorian calendars\n * should be the result of exports.getD3DateFormat(gd)\n * calendar: optional string, the world calendar system to use\n *\n * returns the date/time as a string, potentially with the leading portion\n * on a separate line (after '\\n')\n * Note that this means if you provide an explicit format which includes '\\n'\n * the axis may choose to strip things after it when they don't change from\n * one tick to the next (as it does with automatic formatting)\n */exports.formatDate=function(x,fmt,tr,formatter,calendar,extraFormat){calendar=isWorldCalendar(calendar)&&calendar;if(!fmt){if(tr==='y')fmt=extraFormat.year;else if(tr==='m')fmt=extraFormat.month;else if(tr==='d'){fmt=extraFormat.dayMonth+'\\n'+extraFormat.year;}else{return formatTime(x,tr)+'\\n'+modDateFormat(extraFormat.dayMonthYear,x,formatter,calendar);}}return modDateFormat(fmt,x,formatter,calendar);};/*\n * incrementMonth: make a new milliseconds value from the given one,\n * having changed the month\n *\n * special case for world calendars: multiples of 12 are treated as years,\n * even for calendar systems that don't have (always or ever) 12 months/year\n * TODO: perhaps we need a different code for year increments to support this?\n *\n * ms (number): the initial millisecond value\n * dMonth (int): the (signed) number of months to shift\n * calendar (string): the calendar system to use\n *\n * changing month does not (and CANNOT) always preserve day, since\n * months have different lengths. The worst example of this is:\n * d = new Date(1970,0,31); d.setMonth(1) -> Feb 31 turns into Mar 3\n *\n * But we want to be able to iterate over the last day of each month,\n * regardless of what its number is.\n * So shift 3 days forward, THEN set the new month, then unshift:\n * 1/31 -> 2/28 (or 29) -> 3/31 -> 4/30 -> ...\n *\n * Note that odd behavior still exists if you start from the 26th-28th:\n * 1/28 -> 2/28 -> 3/31\n * but at least you can't shift any dates into the wrong month,\n * and ticks on these days incrementing by month would be very unusual\n */var THREEDAYS=3*ONEDAY;exports.incrementMonth=function(ms,dMonth,calendar){calendar=isWorldCalendar(calendar)&&calendar;// pull time out and operate on pure dates, then add time back at the end\n// this gives maximum precision - not that we *normally* care if we're\n// incrementing by month, but better to be safe!\nvar timeMs=mod(ms,ONEDAY);ms=Math.round(ms-timeMs);if(calendar){try{var dateJD=Math.round(ms/ONEDAY)+EPOCHJD;var calInstance=Registry.getComponentMethod('calendars','getCal')(calendar);var cDate=calInstance.fromJD(dateJD);if(dMonth%12)calInstance.add(cDate,dMonth,'m');else calInstance.add(cDate,dMonth/12,'y');return(cDate.toJD()-EPOCHJD)*ONEDAY+timeMs;}catch(e){Loggers.error('invalid ms '+ms+' in calendar '+calendar);// then keep going in gregorian even though the result will be 'Invalid'\n}}var y=new Date(ms+THREEDAYS);return y.setUTCMonth(y.getUTCMonth()+dMonth)+timeMs-THREEDAYS;};/*\n * findExactDates: what fraction of data is exact days, months, or years?\n *\n * data: array of millisecond values\n * calendar (string) the calendar to test against\n */exports.findExactDates=function(data,calendar){var exactYears=0;var exactMonths=0;var exactDays=0;var blankCount=0;var d;var di;var calInstance=isWorldCalendar(calendar)&&Registry.getComponentMethod('calendars','getCal')(calendar);for(var i=0;i deep extend) first argument,\n * use `extendFlat` for first-level only extend and\n * use `extendDeep` for a deep extend.\n *\n * Other differences with jQuery version:\n * - Uses a modern (and faster) isPlainObject routine.\n * - Expected to work with object {} and array [] arguments only.\n * - Does not check for circular structure.\n * FYI: jQuery only does a check across one level.\n * Warning: this might result in infinite loops.\n *\n */function _extend(inputs,isDeep,keepAllKeys,noArrayCopies){var target=inputs[0];var length=inputs.length;var input,key,src,copy,copyIsArray,clone,allPrimitives;// TODO does this do the right thing for typed arrays?\nif(length===2&&isArray(target)&&isArray(inputs[1])&&target.length===0){allPrimitives=primitivesLoopSplice(inputs[1],target);if(allPrimitives){return target;}else{target.splice(0,target.length);// reset target and continue to next block\n}}for(var i=1;i0&&pts[l+1][0]<0)return l;}return null;}if(loc==='RUS'||loc==='FJI'){// Russia and Fiji have landmasses that cross the antimeridian,\n// we need to add +360 to their longitude coordinates, so that\n// polygon 'contains' doesn't get confused when crossing the antimeridian.\n//\n// Note that other countries have polygons on either side of the antimeridian\n// (e.g. some Aleutian island for the USA), but those don't confuse\n// the 'contains' method; these are skipped here.\nappendPolygon=function(_pts){var pts;if(doesCrossAntiMerdian(_pts)===null){pts=_pts;}else{pts=new Array(_pts.length);for(m=0;m<_pts.length;m++){// do not mutate calcdata[i][j].geojson !!\npts[m]=[_pts[m][0]<0?_pts[m][0]+360:_pts[m][0],_pts[m][1]];}}polygons.push(polygon.tester(pts));};}else if(loc==='ATA'){// Antarctica has a landmass that wraps around every longitudes which\n// confuses the 'contains' methods.\nappendPolygon=function(pts){var crossAntiMeridianIndex=doesCrossAntiMerdian(pts);// polygon that do not cross anti-meridian need no special handling\nif(crossAntiMeridianIndex===null){return polygons.push(polygon.tester(pts));}// stitch polygon by adding pt over South Pole,\n// so that it covers the projected region covers all latitudes\n//\n// Note that the algorithm below only works for polygons that\n// start and end on longitude -180 (like the ones built by\n// https://github.com/etpinard/sane-topojson).\nvar stitch=new Array(pts.length+1);var si=0;for(m=0;mcrossAntiMeridianIndex){stitch[si++]=[pts[m][0]+360,pts[m][1]];}else if(m===crossAntiMeridianIndex){stitch[si++]=pts[m];stitch[si++]=[pts[m][0],-90];}else{stitch[si++]=pts[m];}}// polygon.tester by default appends pt[0] to the points list,\n// we must remove it here, to avoid a jump in longitude from 180 to -180,\n// that would confuse the 'contains' method\nvar tester=polygon.tester(stitch);tester.pts.pop();polygons.push(tester);};}else{// otherwise using same array ref is fine\nappendPolygon=function(pts){polygons.push(polygon.tester(pts));};}switch(geometry.type){case'MultiPolygon':for(j=0;jmaxArea){maxArea=area;poly=polyi;}}}else{poly=geometry;}return turfCentroid.default(poly).geometry.coordinates;}function fetchTraceGeoData(calcData){var PlotlyGeoAssets=window.PlotlyGeoAssets||{};var promises=[];function fetch(url){return new Promise(function(resolve,reject){d3.json(url,function(err,d){if(err){delete PlotlyGeoAssets[url];var msg=err.status===404?'GeoJSON at URL \"'+url+'\" does not exist.':'Unexpected error while fetching from '+url;return reject(new Error(msg));}PlotlyGeoAssets[url]=d;return resolve(d);});});}function wait(url){return new Promise(function(resolve,reject){var cnt=0;var interval=setInterval(function(){if(PlotlyGeoAssets[url]&&PlotlyGeoAssets[url]!=='pending'){clearInterval(interval);return resolve(PlotlyGeoAssets[url]);}if(cnt>100){clearInterval(interval);return reject('Unexpected error while fetching from '+url);}cnt++;},50);});}for(var i=0;i0){coords.push(lineString);lineString=[];}}if(lineString.length>0){coords.push(lineString);}return coords;};/**\n * Make line ('LineString' or 'MultiLineString') GeoJSON\n *\n * @param {array} coords\n * results form calcTraceToLineCoords\n * @return {object} out\n * GeoJSON object\n *\n */exports.makeLine=function(coords){if(coords.length===1){return{type:'LineString',coordinates:coords[0]};}else{return{type:'MultiLineString',coordinates:coords};}};/**\n * Make polygon ('Polygon' or 'MultiPolygon') GeoJSON\n *\n * @param {array} coords\n * results form calcTraceToLineCoords\n * @return {object} out\n * GeoJSON object\n */exports.makePolygon=function(coords){if(coords.length===1){return{type:'Polygon',coordinates:coords};}else{var _coords=new Array(coords.length);for(var i=0;i2 and 3->4) - returns array [x,y] if they do, null if not\n */exports.segmentsIntersect=segmentsIntersect;function segmentsIntersect(x1,y1,x2,y2,x3,y3,x4,y4){var a=x2-x1;var b=x3-x1;var c=x4-x3;var d=y2-y1;var e=y3-y1;var f=y4-y3;var det=a*f-c*d;// parallel lines? intersection is undefined\n// ignore the case where they are colinear\nif(det===0)return null;var t=(b*f-c*e)/det;var u=(b*d-a*e)/det;// segments do not intersect?\nif(u<0||u>1||t<0||t>1)return null;return{x:x1+a*t,y:y1+d*t};}/*\n * find the minimum distance between two line segments (1->2 and 3->4)\n */exports.segmentDistance=function segmentDistance(x1,y1,x2,y2,x3,y3,x4,y4){if(segmentsIntersect(x1,y1,x2,y2,x3,y3,x4,y4))return 0;// the two segments and their lengths squared\nvar x12=x2-x1;var y12=y2-y1;var x34=x4-x3;var y34=y4-y3;var ll12=x12*x12+y12*y12;var ll34=x34*x34+y34*y34;// calculate distance squared, then take the sqrt at the very end\nvar dist2=Math.min(perpDistance2(x12,y12,ll12,x3-x1,y3-y1),perpDistance2(x12,y12,ll12,x4-x1,y4-y1),perpDistance2(x34,y34,ll34,x1-x3,y1-y3),perpDistance2(x34,y34,ll34,x2-x3,y2-y3));return Math.sqrt(dist2);};/*\n * distance squared from segment ab to point c\n * [xab, yab] is the vector b-a\n * [xac, yac] is the vector c-a\n * llab is the length squared of (b-a), just to simplify calculation\n */function perpDistance2(xab,yab,llab,xac,yac){var fcAB=xac*xab+yac*yab;if(fcAB<0){// point c is closer to point a\nreturn xac*xac+yac*yac;}else if(fcAB>llab){// point c is closer to point b\nvar xbc=xac-xab;var ybc=yac-yab;return xbc*xbc+ybc*ybc;}else{// perpendicular distance is the shortest\nvar crossProduct=xac*yab-yac*xab;return crossProduct*crossProduct/llab;}}// a very short-term cache for getTextLocation, just because\n// we're often looping over the same locations multiple times\n// invalidated as soon as we look at a different path\nvar locationCache,workingPath,workingTextWidth;// turn a path and position along it into x, y, and angle for the given text\nexports.getTextLocation=function getTextLocation(path,totalPathLen,positionOnPath,textWidth){if(path!==workingPath||textWidth!==workingTextWidth){locationCache={};workingPath=path;workingTextWidth=textWidth;}if(locationCache[positionOnPath]){return locationCache[positionOnPath];}// for the angle, use points on the path separated by the text width\n// even though due to curvature, the text will cover a bit more than that\nvar p0=path.getPointAtLength(mod(positionOnPath-textWidth/2,totalPathLen));var p1=path.getPointAtLength(mod(positionOnPath+textWidth/2,totalPathLen));// note: atan handles 1/0 nicely\nvar theta=Math.atan((p1.y-p0.y)/(p1.x-p0.x));// center the text at 2/3 of the center position plus 1/3 the p0/p1 midpoint\n// that's the average position of this segment, assuming it's roughly quadratic\nvar pCenter=path.getPointAtLength(mod(positionOnPath,totalPathLen));var x=(pCenter.x*4+p0.x+p1.x)/6;var y=(pCenter.y*4+p0.y+p1.y)/6;var out={x:x,y:y,theta:theta};locationCache[positionOnPath]=out;return out;};exports.clearLocationCache=function(){workingPath=null;};/*\n * Find the segment of `path` that's within the visible area\n * given by `bounds` {left, right, top, bottom}, to within a\n * precision of `buffer` px\n *\n * returns: undefined if nothing is visible, else object:\n * {\n * min: position where the path first enters bounds, or 0 if it\n * starts within bounds\n * max: position where the path last exits bounds, or the path length\n * if it finishes within bounds\n * len: max - min, ie the length of visible path\n * total: the total path length - just included so the caller doesn't\n * need to call path.getTotalLength() again\n * isClosed: true iff the start and end points of the path are both visible\n * and are at the same point\n * }\n *\n * Works by starting from either end and repeatedly finding the distance from\n * that point to the plot area, and if it's outside the plot, moving along the\n * path by that distance (because the plot must be at least that far away on\n * the path). Note that if a path enters, exits, and re-enters the plot, we\n * will not capture this behavior.\n */exports.getVisibleSegment=function getVisibleSegment(path,bounds,buffer){var left=bounds.left;var right=bounds.right;var top=bounds.top;var bottom=bounds.bottom;var pMin=0;var pTotal=path.getTotalLength();var pMax=pTotal;var pt0,ptTotal;function getDistToPlot(len){var pt=path.getPointAtLength(len);// hold on to the start and end points for `closed`\nif(len===0)pt0=pt;else if(len===pTotal)ptTotal=pt;var dx=pt.xright?pt.x-right:0;var dy=pt.ybottom?pt.y-bottom:0;return Math.sqrt(dx*dx+dy*dy);}var distToPlot=getDistToPlot(pMin);while(distToPlot){pMin+=distToPlot+buffer;if(pMin>pMax)return;distToPlot=getDistToPlot(pMin);}distToPlot=getDistToPlot(pMax);while(distToPlot){pMax-=distToPlot+buffer;if(pMin>pMax)return;distToPlot=getDistToPlot(pMax);}return{min:pMin,max:pMax,len:pMax-pMin,total:pTotal,isClosed:pMin===0&&pMax===pTotal&&Math.abs(pt0.x-ptTotal.x)<0.1&&Math.abs(pt0.y-ptTotal.y)<0.1};};/**\n * Find point on SVG path corresponding to a given constraint coordinate\n *\n * @param {SVGPathElement} path\n * @param {Number} val : constraint coordinate value\n * @param {String} coord : 'x' or 'y' the constraint coordinate\n * @param {Object} opts :\n * - {Number} pathLength : supply total path length before hand\n * - {Number} tolerance\n * - {Number} iterationLimit\n * @return {SVGPoint}\n */exports.findPointOnPath=function findPointOnPath(path,val,coord,opts){opts=opts||{};var pathLength=opts.pathLength||path.getTotalLength();var tolerance=opts.tolerance||1e-3;var iterationLimit=opts.iterationLimit||30;// if path starts at a val greater than the path tail (like on vertical violins),\n// we must flip the sign of the computed diff.\nvar mul=path.getPointAtLength(0)[coord]>path.getPointAtLength(pathLength)[coord]?-1:1;var i=0;var b0=0;var b1=pathLength;var mid;var pt;var diff;while(i0){b1=mid;}else{b0=mid;}i++;}}return pt;};},{\"./mod\":742}],732:[function(_dereq_,module,exports){/**\n* Copyright 2012-2020, Plotly, Inc.\n* All rights reserved.\n*\n* This source code is licensed under the MIT license found in the\n* LICENSE file in the root directory of this source tree.\n*/'use strict';var isNumeric=_dereq_('fast-isnumeric');var tinycolor=_dereq_('tinycolor2');var rgba=_dereq_('color-normalize');var Colorscale=_dereq_('../components/colorscale');var colorDflt=_dereq_('../components/color/attributes').defaultLine;var isArrayOrTypedArray=_dereq_('./array').isArrayOrTypedArray;var colorDfltRgba=rgba(colorDflt);var opacityDflt=1;function calculateColor(colorIn,opacityIn){var colorOut=colorIn;colorOut[3]*=opacityIn;return colorOut;}function validateColor(colorIn){if(isNumeric(colorIn))return colorDfltRgba;var colorOut=rgba(colorIn);return colorOut.length?colorOut:colorDfltRgba;}function validateOpacity(opacityIn){return isNumeric(opacityIn)?opacityIn:opacityDflt;}function formatColor(containerIn,opacityIn,len){var colorIn=containerIn.color;var isArrayColorIn=isArrayOrTypedArray(colorIn);var isArrayOpacityIn=isArrayOrTypedArray(opacityIn);var cOpts=Colorscale.extractOpts(containerIn);var colorOut=[];var sclFunc,getColor,getOpacity,colori,opacityi;if(cOpts.colorscale!==undefined){sclFunc=Colorscale.makeColorScaleFuncFromTrace(containerIn);}else{sclFunc=validateColor;}if(isArrayColorIn){getColor=function(c,i){// FIXME: there is double work, considering that sclFunc does the opposite\nreturn c[i]===undefined?colorDfltRgba:rgba(sclFunc(c[i]));};}else getColor=validateColor;if(isArrayOpacityIn){getOpacity=function(o,i){return o[i]===undefined?opacityDflt:validateOpacity(o[i]);};}else getOpacity=validateOpacity;if(isArrayColorIn||isArrayOpacityIn){for(var i=0;i` element need\n// access to the same data, or a `descend`, which fans a scenegraph node into a bunch of\n// of elements, e.g. points, lines, rows, requiring an array as input.\n// The role of the `keyFun` is to identify what elements are being entered/exited/updated,\n// otherwise D3 reverts to using a plain index which would screw up `transition`s.\nkeyFun:function(d){return d.key;},repeat:wrap,descend:identity,// Plotly.js uses a convention of storing the actual contents of the `calcData` as the\n// element zero of a container array. These helpers are just used for clarity as a\n// newcomer to the codebase may not know what the `[0]` is, and whether there can be further\n// elements (not atm).\nwrap:wrap,unwrap:function(d){return d[0];}};},{\"./identity\":734}],734:[function(_dereq_,module,exports){/**\n* Copyright 2012-2020, Plotly, Inc.\n* All rights reserved.\n*\n* This source code is licensed under the MIT license found in the\n* LICENSE file in the root directory of this source tree.\n*/'use strict';// Simple helper functions\n// none of these need any external deps\nmodule.exports=function identity(d){return d;};},{}],735:[function(_dereq_,module,exports){/**\n* Copyright 2012-2020, Plotly, Inc.\n* All rights reserved.\n*\n* This source code is licensed under the MIT license found in the\n* LICENSE file in the root directory of this source tree.\n*/'use strict';var d3=_dereq_('d3');var isNumeric=_dereq_('fast-isnumeric');var numConstants=_dereq_('../constants/numerical');var FP_SAFE=numConstants.FP_SAFE;var BADNUM=numConstants.BADNUM;var lib=module.exports={};lib.nestedProperty=_dereq_('./nested_property');lib.keyedContainer=_dereq_('./keyed_container');lib.relativeAttr=_dereq_('./relative_attr');lib.isPlainObject=_dereq_('./is_plain_object');lib.toLogRange=_dereq_('./to_log_range');lib.relinkPrivateKeys=_dereq_('./relink_private');var arrayModule=_dereq_('./array');lib.isTypedArray=arrayModule.isTypedArray;lib.isArrayOrTypedArray=arrayModule.isArrayOrTypedArray;lib.isArray1D=arrayModule.isArray1D;lib.ensureArray=arrayModule.ensureArray;lib.concat=arrayModule.concat;lib.maxRowLength=arrayModule.maxRowLength;lib.minRowLength=arrayModule.minRowLength;var modModule=_dereq_('./mod');lib.mod=modModule.mod;lib.modHalf=modModule.modHalf;var coerceModule=_dereq_('./coerce');lib.valObjectMeta=coerceModule.valObjectMeta;lib.coerce=coerceModule.coerce;lib.coerce2=coerceModule.coerce2;lib.coerceFont=coerceModule.coerceFont;lib.coerceHoverinfo=coerceModule.coerceHoverinfo;lib.coerceSelectionMarkerOpacity=coerceModule.coerceSelectionMarkerOpacity;lib.validate=coerceModule.validate;var datesModule=_dereq_('./dates');lib.dateTime2ms=datesModule.dateTime2ms;lib.isDateTime=datesModule.isDateTime;lib.ms2DateTime=datesModule.ms2DateTime;lib.ms2DateTimeLocal=datesModule.ms2DateTimeLocal;lib.cleanDate=datesModule.cleanDate;lib.isJSDate=datesModule.isJSDate;lib.formatDate=datesModule.formatDate;lib.incrementMonth=datesModule.incrementMonth;lib.dateTick0=datesModule.dateTick0;lib.dfltRange=datesModule.dfltRange;lib.findExactDates=datesModule.findExactDates;lib.MIN_MS=datesModule.MIN_MS;lib.MAX_MS=datesModule.MAX_MS;var searchModule=_dereq_('./search');lib.findBin=searchModule.findBin;lib.sorterAsc=searchModule.sorterAsc;lib.sorterDes=searchModule.sorterDes;lib.distinctVals=searchModule.distinctVals;lib.roundUp=searchModule.roundUp;lib.sort=searchModule.sort;lib.findIndexOfMin=searchModule.findIndexOfMin;var statsModule=_dereq_('./stats');lib.aggNums=statsModule.aggNums;lib.len=statsModule.len;lib.mean=statsModule.mean;lib.median=statsModule.median;lib.midRange=statsModule.midRange;lib.variance=statsModule.variance;lib.stdev=statsModule.stdev;lib.interp=statsModule.interp;var matrixModule=_dereq_('./matrix');lib.init2dArray=matrixModule.init2dArray;lib.transposeRagged=matrixModule.transposeRagged;lib.dot=matrixModule.dot;lib.translationMatrix=matrixModule.translationMatrix;lib.rotationMatrix=matrixModule.rotationMatrix;lib.rotationXYMatrix=matrixModule.rotationXYMatrix;lib.apply2DTransform=matrixModule.apply2DTransform;lib.apply2DTransform2=matrixModule.apply2DTransform2;var anglesModule=_dereq_('./angles');lib.deg2rad=anglesModule.deg2rad;lib.rad2deg=anglesModule.rad2deg;lib.angleDelta=anglesModule.angleDelta;lib.angleDist=anglesModule.angleDist;lib.isFullCircle=anglesModule.isFullCircle;lib.isAngleInsideSector=anglesModule.isAngleInsideSector;lib.isPtInsideSector=anglesModule.isPtInsideSector;lib.pathArc=anglesModule.pathArc;lib.pathSector=anglesModule.pathSector;lib.pathAnnulus=anglesModule.pathAnnulus;var anchorUtils=_dereq_('./anchor_utils');lib.isLeftAnchor=anchorUtils.isLeftAnchor;lib.isCenterAnchor=anchorUtils.isCenterAnchor;lib.isRightAnchor=anchorUtils.isRightAnchor;lib.isTopAnchor=anchorUtils.isTopAnchor;lib.isMiddleAnchor=anchorUtils.isMiddleAnchor;lib.isBottomAnchor=anchorUtils.isBottomAnchor;var geom2dModule=_dereq_('./geometry2d');lib.segmentsIntersect=geom2dModule.segmentsIntersect;lib.segmentDistance=geom2dModule.segmentDistance;lib.getTextLocation=geom2dModule.getTextLocation;lib.clearLocationCache=geom2dModule.clearLocationCache;lib.getVisibleSegment=geom2dModule.getVisibleSegment;lib.findPointOnPath=geom2dModule.findPointOnPath;var extendModule=_dereq_('./extend');lib.extendFlat=extendModule.extendFlat;lib.extendDeep=extendModule.extendDeep;lib.extendDeepAll=extendModule.extendDeepAll;lib.extendDeepNoArrays=extendModule.extendDeepNoArrays;var loggersModule=_dereq_('./loggers');lib.log=loggersModule.log;lib.warn=loggersModule.warn;lib.error=loggersModule.error;var regexModule=_dereq_('./regex');lib.counterRegex=regexModule.counter;var throttleModule=_dereq_('./throttle');lib.throttle=throttleModule.throttle;lib.throttleDone=throttleModule.done;lib.clearThrottle=throttleModule.clear;var domModule=_dereq_('./dom');lib.getGraphDiv=domModule.getGraphDiv;lib.isPlotDiv=domModule.isPlotDiv;lib.removeElement=domModule.removeElement;lib.addStyleRule=domModule.addStyleRule;lib.addRelatedStyleRule=domModule.addRelatedStyleRule;lib.deleteRelatedStyleRule=domModule.deleteRelatedStyleRule;lib.clearResponsive=_dereq_('./clear_responsive');lib.makeTraceGroups=_dereq_('./make_trace_groups');lib._=_dereq_('./localize');lib.notifier=_dereq_('./notifier');lib.filterUnique=_dereq_('./filter_unique');lib.filterVisible=_dereq_('./filter_visible');lib.pushUnique=_dereq_('./push_unique');lib.cleanNumber=_dereq_('./clean_number');lib.ensureNumber=function ensureNumber(v){if(!isNumeric(v))return BADNUM;v=Number(v);if(v<-FP_SAFE||v>FP_SAFE)return BADNUM;return isNumeric(v)?Number(v):BADNUM;};/**\n * Is v a valid array index? Accepts numeric strings as well as numbers.\n *\n * @param {any} v: the value to test\n * @param {Optional[integer]} len: the array length we are indexing\n *\n * @return {bool}: v is a valid array index\n */lib.isIndex=function(v,len){if(len!==undefined&&v>=len)return false;return isNumeric(v)&&v>=0&&v%1===0;};lib.noop=_dereq_('./noop');lib.identity=_dereq_('./identity');/**\n * create an array of length 'cnt' filled with 'v' at all indices\n *\n * @param {any} v\n * @param {number} cnt\n * @return {array}\n */lib.repeat=function(v,cnt){var out=new Array(cnt);for(var i=0;iv1)return Math.max(v1,Math.min(v0,v));return Math.max(v0,Math.min(v1,v));};/**\n * do two bounding boxes from getBoundingClientRect,\n * ie {left,right,top,bottom,width,height}, overlap?\n * takes optional padding pixels\n */lib.bBoxIntersect=function(a,b,pad){pad=pad||0;return a.left<=b.right+pad&&b.left<=a.right+pad&&a.top<=b.bottom+pad&&b.top<=a.bottom+pad;};/*\n * simpleMap: alternative to Array.map that only\n * passes on the element and up to 2 extra args you\n * provide (but not the array index or the whole array)\n *\n * array: the array to map it to\n * func: the function to apply\n * x1, x2: optional extra args\n */lib.simpleMap=function(array,func,x1,x2){var len=array.length;var out=new Array(len);for(var i=0;i=Math.pow(2,bits)){if(_recursion>10){lib.warn('randstr failed uniqueness');return res;}return randstr(existing,bits,base,(_recursion||0)+1);}else return res;};lib.OptionControl=function(opt,optname){/*\n * An environment to contain all option setters and\n * getters that collectively modify opts.\n *\n * You can call up opts from any function in new object\n * as this.optname || this.opt\n *\n * See FitOpts for example of usage\n */if(!opt)opt={};if(!optname)optname='opt';var self={};self.optionList=[];self._newoption=function(optObj){optObj[optname]=opt;self[optObj.name]=optObj;self.optionList.push(optObj);};self['_'+optname]=opt;return self;};/**\n * lib.smooth: smooth arrayIn by convolving with\n * a hann window with given full width at half max\n * bounce the ends in, so the output has the same length as the input\n */lib.smooth=function(arrayIn,FWHM){FWHM=Math.round(FWHM)||0;// only makes sense for integers\nif(FWHM<2)return arrayIn;var alen=arrayIn.length;var alen2=2*alen;var wlen=2*FWHM-1;var w=new Array(wlen);var arrayOut=new Array(alen);var i;var j;var k;var v;// first make the window array\nfor(i=0;i=alen2)k-=alen2*Math.floor(k/alen2);// single bounce\nif(k<0)k=-1-k;else if(k>=alen)k=alen2-1-k;v+=arrayIn[k]*w[j];}arrayOut[i]=v;}return arrayOut;};/**\n * syncOrAsync: run a sequence of functions synchronously\n * as long as its returns are not promises (ie have no .then)\n * includes one argument arg to send to all functions...\n * this is mainly just to prevent us having to make wrapper functions\n * when the only purpose of the wrapper is to reference gd\n * and a final step to be executed at the end\n * TODO: if there's an error and everything is sync,\n * this doesn't happen yet because we want to make sure\n * that it gets reported\n */lib.syncOrAsync=function(sequence,arg,finalStep){var ret,fni;function continueAsync(){return lib.syncOrAsync(sequence,arg,finalStep);}while(sequence.length){fni=sequence.splice(0,1)[0];ret=fni(arg);if(ret&&ret.then){return ret.then(continueAsync).then(undefined,lib.promiseError);}}return finalStep&&finalStep(arg);};/**\n * Helper to strip trailing slash, from\n * http://stackoverflow.com/questions/6680825/return-string-without-trailing-slash\n */lib.stripTrailingSlash=function(str){if(str.substr(-1)==='/')return str.substr(0,str.length-1);return str;};lib.noneOrAll=function(containerIn,containerOut,attrList){/**\n * some attributes come together, so if you have one of them\n * in the input, you should copy the default values of the others\n * to the input as well.\n */if(!containerIn)return;var hasAny=false;var hasAll=true;var i;var val;for(i=0;i0?w:0;});};/** fills calcdata field (given by cdAttr) with traceAttr values\n * or function of traceAttr values (e.g. some fallback)\n *\n * N.B. Loops over all cd items.\n *\n * @param {array} traceAttr : trace attribute\n * @param {object} cd : calcdata trace\n * @param {string} cdAttr : calcdata key\n * @param {function} [fn] : optional function to apply to each array item\n */lib.fillArray=function(traceAttr,cd,cdAttr,fn){fn=fn||lib.identity;if(lib.isArrayOrTypedArray(traceAttr)){for(var i=0;i or 1 elements\n * because extend-like algorithms are hella slow\n * obj2 is assumed to already be clean of these things (including no arrays)\n */lib.minExtend=function(obj1,obj2){var objOut={};if(typeof obj2!=='object')obj2={};var arrayLen=3;var keys=Object.keys(obj1);var i,k,v;for(i=0;i { nested: { test: {path: 'value'}}}\n *\n * It also handles array notation, e.g.:\n *\n * lib.expandObjectPaths({'foo[1].bar': 'value'});\n * => { foo: [null, {bar: value}] }\n *\n * It handles merges the results when two properties are specified in parallel:\n *\n * lib.expandObjectPaths({'foo[1].bar': 10, 'foo[0].bar': 20});\n * => { foo: [{bar: 10}, {bar: 20}] }\n *\n * It does NOT, however, merge mulitple mutliply-nested arrays::\n *\n * lib.expandObjectPaths({'marker[1].range[1]': 5, 'marker[1].range[0]': 4})\n * => { marker: [null, {range: 4}] }\n */ // Store this to avoid recompiling regex on *every* prop since this may happen many\n// many times for animations. Could maybe be inside the function. Not sure about\n// scoping vs. recompilation tradeoff, but at least it's not just inlining it into\n// the inner loop.\nvar dottedPropertyRegex=/^([^\\[\\.]+)\\.(.+)?/;var indexedPropertyRegex=/^([^\\.]+)\\[([0-9]+)\\](\\.)?(.+)?/;lib.expandObjectPaths=function(data){var match,key,prop,datum,idx,dest,trailingPath;if(typeof data==='object'&&!Array.isArray(data)){for(key in data){if(data.hasOwnProperty(key)){if(match=key.match(dottedPropertyRegex)){datum=data[key];prop=match[1];delete data[key];data[prop]=lib.extendDeepNoArrays(data[prop]||{},lib.objectFromPath(key,lib.expandObjectPaths(datum))[prop]);}else if(match=key.match(indexedPropertyRegex)){datum=data[key];prop=match[1];idx=parseInt(match[2]);delete data[key];data[prop]=data[prop]||[];if(match[3]==='.'){// This is the case where theere are subsequent properties into which\n// we must recurse, e.g. transforms[0].value\ntrailingPath=match[4];dest=data[prop][idx]=data[prop][idx]||{};// NB: Extend deep no arrays prevents this from working on multiple\n// nested properties in the same object, e.g.\n//\n// {\n// foo[0].bar[1].range\n// foo[0].bar[0].range\n// }\n//\n// In this case, the extendDeepNoArrays will overwrite one array with\n// the other, so that both properties *will not* be present in the\n// result. Fixing this would require a more intelligent tracking\n// of changes and merging than extendDeepNoArrays currently accomplishes.\nlib.extendDeepNoArrays(dest,lib.objectFromPath(trailingPath,lib.expandObjectPaths(datum)));}else{// This is the case where this property is the end of the line,\n// e.g. xaxis.range[0]\ndata[prop][idx]=lib.expandObjectPaths(datum);}}else{data[key]=lib.expandObjectPaths(data[key]);}}}}return data;};/**\n * Converts value to string separated by the provided separators.\n *\n * @example\n * lib.numSeparate(2016, '.,');\n * // returns '2016'\n *\n * @example\n * lib.numSeparate(3000, '.,', true);\n * // returns '3,000'\n *\n * @example\n * lib.numSeparate(1234.56, '|,')\n * // returns '1,234|56'\n *\n * @param {string|number} value the value to be converted\n * @param {string} separators string of decimal, then thousands separators\n * @param {boolean} separatethousands boolean, 4-digit integers are separated if true\n *\n * @return {string} the value that has been separated\n */lib.numSeparate=function(value,separators,separatethousands){if(!separatethousands)separatethousands=false;if(typeof separators!=='string'||separators.length===0){throw new Error('Separator string required for formatting!');}if(typeof value==='number'){value=String(value);}var thousandsRe=/(\\d+)(\\d{3})/;var decimalSep=separators.charAt(0);var thouSep=separators.charAt(1);var x=value.split('.');var x1=x[0];var x2=x.length>1?decimalSep+x[1]:'';// Years are ignored for thousands separators\nif(thouSep&&(x.length>1||x1.length>4||separatethousands)){while(thousandsRe.test(x1)){x1=x1.replace(thousandsRe,'$1'+thouSep+'$2');}}return x1+x2;};lib.TEMPLATE_STRING_REGEX=/%{([^\\s%{}:]*)([:|\\|][^}]*)?}/g;var SIMPLE_PROPERTY_REGEX=/^\\w*$/;/**\n * Substitute values from an object into a string\n *\n * Examples:\n * Lib.templateString('name: %{trace}', {trace: 'asdf'}) --> 'name: asdf'\n * Lib.templateString('name: %{trace[0].name}', {trace: [{name: 'asdf'}]}) --> 'name: asdf'\n *\n * @param {string} input string containing %{...} template strings\n * @param {obj} data object containing substitution values\n *\n * @return {string} templated string\n */lib.templateString=function(string,obj){// Not all that useful, but cache nestedProperty instantiation\n// just in case it speeds things up *slightly*:\nvar getterCache={};return string.replace(lib.TEMPLATE_STRING_REGEX,function(dummy,key){var v;if(SIMPLE_PROPERTY_REGEX.test(key)){v=obj[key];}else{getterCache[key]=getterCache[key]||lib.nestedProperty(obj,key).get;v=getterCache[key]();}return lib.isValidTextValue(v)?v:'';});};var hovertemplateWarnings={max:10,count:0,name:'hovertemplate'};lib.hovertemplateString=function(){return templateFormatString.apply(hovertemplateWarnings,arguments);};var texttemplateWarnings={max:10,count:0,name:'texttemplate'};lib.texttemplateString=function(){return templateFormatString.apply(texttemplateWarnings,arguments);};var TEMPLATE_STRING_FORMAT_SEPARATOR=/^[:|\\|]/;/**\n * Substitute values from an object into a string and optionally formats them using d3-format,\n * or fallback to associated labels.\n *\n * Examples:\n * Lib.hovertemplateString('name: %{trace}', {trace: 'asdf'}) --> 'name: asdf'\n * Lib.hovertemplateString('name: %{trace[0].name}', {trace: [{name: 'asdf'}]}) --> 'name: asdf'\n * Lib.hovertemplateString('price: %{y:$.2f}', {y: 1}) --> 'price: $1.00'\n *\n * @param {string} input string containing %{...:...} template strings\n * @param {obj} data object containing fallback text when no formatting is specified, ex.: {yLabel: 'formattedYValue'}\n * @param {obj} d3 locale\n * @param {obj} data objects containing substitution values\n *\n * @return {string} templated string\n */function templateFormatString(string,labels,d3locale){var opts=this;var args=arguments;if(!labels)labels={};// Not all that useful, but cache nestedProperty instantiation\n// just in case it speeds things up *slightly*:\nvar getterCache={};return string.replace(lib.TEMPLATE_STRING_REGEX,function(match,key,format){var obj,value,i;for(i=3;i=char0&&charA<=char9;var isNumB=charB>=char0&&charB<=char9;if(isNumA)numA=10*numA+charA-char0;if(isNumB)numB=10*numB+charB-char0;if(!isNumA||!isNumB){if(numA!==numB)return numA-numB;if(charA!==charB)return charA-charB;}}return numB-numA;};// repeatable pseudorandom generator\nvar randSeed=2000000000;lib.seedPseudoRandom=function(){randSeed=2000000000;};lib.pseudoRandom=function(){var lastVal=randSeed;randSeed=(69069*randSeed+1)%4294967296;// don't let consecutive vals be too close together\n// gets away from really trying to be random, in favor of better local uniformity\nif(Math.abs(randSeed-lastVal)<429496729)return lib.pseudoRandom();return randSeed/4294967296;};/** Fill hover 'pointData' container with 'correct' hover text value\n *\n * - If trace hoverinfo contains a 'text' flag and hovertext is not set,\n * the text elements will be seen in the hover labels.\n *\n * - If trace hoverinfo contains a 'text' flag and hovertext is set,\n * hovertext takes precedence over text\n * i.e. the hoverinfo elements will be seen in the hover labels\n *\n * @param {object} calcPt\n * @param {object} trace\n * @param {object || array} contOut (mutated here)\n */lib.fillText=function(calcPt,trace,contOut){var fill=Array.isArray(contOut)?function(v){contOut.push(v);}:function(v){contOut.text=v;};var htx=lib.extractOption(calcPt,trace,'htx','hovertext');if(lib.isValidTextValue(htx))return fill(htx);var tx=lib.extractOption(calcPt,trace,'tx','text');if(lib.isValidTextValue(tx))return fill(tx);};// accept all truthy values and 0 (which gets cast to '0' in the hover labels)\nlib.isValidTextValue=function(v){return v||v===0;};/**\n * @param {number} ratio\n * @param {number} n (number of decimal places)\n */lib.formatPercent=function(ratio,n){n=n||0;var str=(Math.round(100*ratio*Math.pow(10,n))*Math.pow(0.1,n)).toFixed(n)+'%';for(var i=0;i1)scale=1;return'translate('+(targetX-scale*(textX+anchorX))+','+(targetY-scale*(textY+anchorY))+')'+(scale<1?'scale('+scale+')':'')+(rotate?'rotate('+rotate+(noCenter?'':' '+textX+' '+textY)+')':'');};lib.ensureUniformFontSize=function(gd,baseFont){var out=lib.extendFlat({},baseFont);out.size=Math.max(baseFont.size,gd._fullLayout.uniformtext.minsize||0);return out;};},{\"../constants/numerical\":711,\"./anchor_utils\":716,\"./angles\":717,\"./array\":718,\"./clean_number\":719,\"./clear_responsive\":721,\"./coerce\":722,\"./dates\":723,\"./dom\":724,\"./extend\":726,\"./filter_unique\":727,\"./filter_visible\":728,\"./geometry2d\":731,\"./identity\":734,\"./is_plain_object\":736,\"./keyed_container\":737,\"./localize\":738,\"./loggers\":739,\"./make_trace_groups\":740,\"./matrix\":741,\"./mod\":742,\"./nested_property\":743,\"./noop\":744,\"./notifier\":745,\"./push_unique\":749,\"./regex\":751,\"./relative_attr\":752,\"./relink_private\":753,\"./search\":754,\"./stats\":757,\"./throttle\":760,\"./to_log_range\":761,\"d3\":165,\"fast-isnumeric\":237}],736:[function(_dereq_,module,exports){/**\n* Copyright 2012-2020, Plotly, Inc.\n* All rights reserved.\n*\n* This source code is licensed under the MIT license found in the\n* LICENSE file in the root directory of this source tree.\n*/'use strict';// more info: http://stackoverflow.com/questions/18531624/isplainobject-thing\nmodule.exports=function isPlainObject(obj){// We need to be a little less strict in the `imagetest` container because\n// of how async image requests are handled.\n//\n// N.B. isPlainObject(new Constructor()) will return true in `imagetest`\nif(window&&window.process&&window.process.versions){return Object.prototype.toString.call(obj)==='[object Object]';}return Object.prototype.toString.call(obj)==='[object Object]'&&Object.getPrototypeOf(obj)===Object.prototype;};},{}],737:[function(_dereq_,module,exports){/**\n* Copyright 2012-2020, Plotly, Inc.\n* All rights reserved.\n*\n* This source code is licensed under the MIT license found in the\n* LICENSE file in the root directory of this source tree.\n*/'use strict';var nestedProperty=_dereq_('./nested_property');var SIMPLE_PROPERTY_REGEX=/^\\w*$/;// bitmask for deciding what's updated. Sometimes the name needs to be updated,\n// sometimes the value needs to be updated, and sometimes both do. This is just\n// a simple way to track what's updated such that it's a simple OR operation to\n// assimilate new updates.\n//\n// The only exception is the UNSET bit that tracks when we need to explicitly\n// unset and remove the property. This concrn arises because of the special\n// way in which nestedProperty handles null/undefined. When you specify `null`,\n// it prunes any unused items in the tree. I ran into some issues with it getting\n// null vs undefined confused, so UNSET is just a bit that forces the property\n// update to send `null`, removing the property explicitly rather than setting\n// it to undefined.\nvar NONE=0;var NAME=1;var VALUE=2;var BOTH=3;var UNSET=4;module.exports=function keyedContainer(baseObj,path,keyName,valueName){keyName=keyName||'name';valueName=valueName||'value';var i,arr,baseProp;var changeTypes={};if(path&&path.length){baseProp=nestedProperty(baseObj,path);arr=baseProp.get();}else{arr=baseObj;}path=path||'';// Construct an index:\nvar indexLookup={};if(arr){for(i=0;i2){// This object contains more than just the key/value, so unset\n// the value without modifying the entry otherwise:\nchangeTypes[idx]=changeTypes[idx]|VALUE;return obj.set(name,null);}if(isSimpleValueProp){for(i=idx;i1){var messages=['LOG:'];for(i=0;i1){var lines=[];for(i=0;i'),'long');}};loggers.warn=function(){var i;if(dfltConfig.logging>0){var messages=['WARN:'];for(i=0;i0){var lines=[];for(i=0;i'),'stick');}};loggers.error=function(){var i;if(dfltConfig.logging>0){var messages=['ERROR:'];for(i=0;i0){var lines=[];for(i=0;i'),'stick');}};/*\n * Robust apply, for IE9 where console.log doesn't support\n * apply like other functions do\n */function apply(f,args){if(f&&f.apply){try{// `this` should always be console, since here we're always\n// applying a method of the console object.\nf.apply(console,args);return;}catch(e){/* in case apply failed, fall back on the code below */}}// no apply - just try calling the function on each arg independently\nfor(var i=0;id/2?v-Math.round(v/d)*d:v;}module.exports={mod:mod,modHalf:modHalf};},{}],743:[function(_dereq_,module,exports){/**\n* Copyright 2012-2020, Plotly, Inc.\n* All rights reserved.\n*\n* This source code is licensed under the MIT license found in the\n* LICENSE file in the root directory of this source tree.\n*/'use strict';var isNumeric=_dereq_('fast-isnumeric');var isArrayOrTypedArray=_dereq_('./array').isArrayOrTypedArray;/**\n * convert a string s (such as 'xaxis.range[0]')\n * representing a property of nested object into set and get methods\n * also return the string and object so we don't have to keep track of them\n * allows [-1] for an array index, to set a property inside all elements\n * of an array\n * eg if obj = {arr: [{a: 1}, {a: 2}]}\n * you can do p = nestedProperty(obj, 'arr[-1].a')\n * but you cannot set the array itself this way, to do that\n * just set the whole array.\n * eg if obj = {arr: [1, 2, 3]}\n * you can't do nestedProperty(obj, 'arr[-1]').set(5)\n * but you can do nestedProperty(obj, 'arr').set([5, 5, 5])\n */module.exports=function nestedProperty(container,propStr){if(isNumeric(propStr))propStr=String(propStr);else if(typeof propStr!=='string'||propStr.substr(propStr.length-4)==='[-1]'){throw'bad property string';}var j=0;var propParts=propStr.split('.');var indexed;var indices;var i;// check for parts of the nesting hierarchy that are numbers (ie array elements)\nwhile(j/g);for(var i=0;ixmax||y===BADNUM||yymax){// pt is outside the bounding box of polygon\nreturn false;}if(omitFirstEdge&&rectFirstEdgeTest(pt))return false;return true;}function contains(pt,omitFirstEdge){var x=pt[0];var y=pt[1];if(x===BADNUM||xxmax||y===BADNUM||yymax){// pt is outside the bounding box of polygon\nreturn false;}var imax=pts.length;var x1=pts[0][0];var y1=pts[0][1];var crossings=0;var i;var x0;var y0;var xmini;var ycross;for(i=1;iMath.max(x0,x1)||y>Math.max(y0,y1)){// outside the bounding box of this segment, it's only a crossing\n// if it's below the box.\ncontinue;}else if(ysegmentSquared||Math.abs(dot(part,unitPerp))>tolerance)return true;}return false;};/**\n * Make a filtering polygon, to minimize the number of segments\n *\n * @param pts Array of [x, y] pairs (must start with at least 1 pair)\n * @param tolerance the maximum deviation from straight allowed for\n * removing points to simplify the polygon\n *\n * @returns Object {addPt, raw, filtered}\n * addPt is a function(pt: [x, y] pair) to add a raw point and\n * continue filtering\n * raw is all the input points\n * filtered is the resulting filtered Array of [x, y] pairs\n */polygon.filter=function filter(pts,tolerance){var ptsFiltered=[pts[0]];var doneRawIndex=0;var doneFilteredIndex=0;function addPt(pt){pts.push(pt);var prevFilterLen=ptsFiltered.length;var iLast=doneRawIndex;ptsFiltered.splice(doneFilteredIndex+1);for(var i=iLast+1;i1){var lastPt=pts.pop();addPt(lastPt);}return{addPt:addPt,raw:pts,filtered:ptsFiltered};};},{\"../constants/numerical\":711,\"./matrix\":741}],748:[function(_dereq_,module,exports){(function(global){/**\n* Copyright 2012-2020, Plotly, Inc.\n* All rights reserved.\n*\n* This source code is licensed under the MIT license found in the\n* LICENSE file in the root directory of this source tree.\n*/'use strict';var showNoWebGlMsg=_dereq_('./show_no_webgl_msg');// Note that this module should be ONLY required into\n// files corresponding to regl trace modules\n// so that bundles with non-regl only don't include\n// regl and all its bytes.\nvar createRegl=_dereq_('regl');/**\n * Idempotent version of createRegl. Create regl instances\n * in the correct canvases with the correct attributes and\n * options\n *\n * @param {DOM node or object} gd : graph div object\n * @param {array} extensions : list of extension to pass to createRegl\n *\n * @return {boolean} true if all createRegl calls succeeded, false otherwise\n */module.exports=function prepareRegl(gd,extensions){var fullLayout=gd._fullLayout;var success=true;fullLayout._glcanvas.each(function(d){if(d.regl)return;// only parcoords needs pick layer\nif(d.pick&&!fullLayout._has('parcoords'))return;try{d.regl=createRegl({canvas:this,attributes:{antialias:!d.pick,preserveDrawingBuffer:true},pixelRatio:gd._context.plotGlPixelRatio||global.devicePixelRatio,extensions:extensions||[]});}catch(e){success=false;}if(!d.regl)success=false;if(success){this.addEventListener('webglcontextlost',function(event){if(gd&&gd.emit){gd.emit('plotly_webglcontextlost',{event:event,layer:d.key});}},false);}});if(!success){showNoWebGlMsg({container:fullLayout._glcontainer.node()});}return success;};}).call(this,typeof global!==\"undefined\"?global:typeof self!==\"undefined\"?self:typeof window!==\"undefined\"?window:{});},{\"./show_no_webgl_msg\":756,\"regl\":499}],749:[function(_dereq_,module,exports){/**\n* Copyright 2012-2020, Plotly, Inc.\n* All rights reserved.\n*\n* This source code is licensed under the MIT license found in the\n* LICENSE file in the root directory of this source tree.\n*/'use strict';/**\n * Push array with unique items\n *\n * Ignores falsy items, except 0 so we can use it to construct arrays of indices.\n *\n * @param {array} array\n * array to be filled\n * @param {any} item\n * item to be or not to be inserted\n * @return {array}\n * ref to array (now possibly containing one more item)\n *\n */module.exports=function pushUnique(array,item){if(item instanceof RegExp){var itemStr=item.toString();for(var i=0;idfltConfig.queueLength){gd.undoQueue.queue.shift();gd.undoQueue.index--;}};/**\n * Begin a sequence of undoQueue changes\n *\n * @param gd\n */queue.startSequence=function(gd){gd.undoQueue=gd.undoQueue||{index:0,queue:[],sequence:false};gd.undoQueue.sequence=true;gd.undoQueue.beginSequence=true;};/**\n * Stop a sequence of undoQueue changes\n *\n * Call this *after* you're sure your undo chain has ended\n *\n * @param gd\n */queue.stopSequence=function(gd){gd.undoQueue=gd.undoQueue||{index:0,queue:[],sequence:false};gd.undoQueue.sequence=false;gd.undoQueue.beginSequence=false;};/**\n * Move one step back in the undo queue, and undo the object there.\n *\n * @param gd\n */queue.undo=function undo(gd){var queueObj,i;if(gd.framework&&gd.framework.isPolar){gd.framework.undo();return;}if(gd.undoQueue===undefined||isNaN(gd.undoQueue.index)||gd.undoQueue.index<=0){return;}// index is pointing to next *forward* queueObj, point to the one we're undoing\ngd.undoQueue.index--;// get the queueObj for instructions on how to undo\nqueueObj=gd.undoQueue.queue[gd.undoQueue.index];// this sequence keeps things from adding to the queue during undo/redo\ngd.undoQueue.inSequence=true;for(i=0;i=gd.undoQueue.queue.length){return;}// get the queueObj for instructions on how to undo\nqueueObj=gd.undoQueue.queue[gd.undoQueue.index];// this sequence keeps things from adding to the queue during undo/redo\ngd.undoQueue.inSequence=true;for(i=0;i] or . - to ascend\n// the attribute tree. the remaining attrString is in match[1]\nvar ASCEND=/^(.*)(\\.[^\\.\\[\\]]+|\\[\\d\\])$/;// SIMPLEATTR: is this an un-nested attribute? (no dots or brackets)\nvar SIMPLEATTR=/^[^\\.\\[\\]]+$/;/*\n * calculate a relative attribute string, similar to a relative path\n *\n * @param {string} baseAttr:\n * an attribute string, such as 'annotations[3].x'. The \"current location\"\n * is the attribute string minus the last component ('annotations[3]')\n * @param {string} relativeAttr:\n * a route to the desired attribute string, using '^' to ascend\n *\n * @return {string} attrString:\n * for example:\n * relativeAttr('annotations[3].x', 'y') = 'annotations[3].y'\n * relativeAttr('annotations[3].x', '^[2].z') = 'annotations[2].z'\n * relativeAttr('annotations[3].x', '^^margin') = 'margin'\n * relativeAttr('annotations[3].x', '^^margin.r') = 'margin.r'\n */module.exports=function(baseAttr,relativeAttr){while(relativeAttr){var match=baseAttr.match(ASCEND);if(match)baseAttr=match[1];else if(baseAttr.match(SIMPLEATTR))baseAttr='';else throw new Error('bad relativeAttr call:'+[baseAttr,relativeAttr]);if(relativeAttr.charAt(0)==='^')relativeAttr=relativeAttr.slice(1);else break;}if(baseAttr&&relativeAttr.charAt(0)!=='['){return baseAttr+'.'+relativeAttr;}return baseAttr+relativeAttr;};},{}],753:[function(_dereq_,module,exports){/**\n* Copyright 2012-2020, Plotly, Inc.\n* All rights reserved.\n*\n* This source code is licensed under the MIT license found in the\n* LICENSE file in the root directory of this source tree.\n*/'use strict';var isArrayOrTypedArray=_dereq_('./array').isArrayOrTypedArray;var isPlainObject=_dereq_('./is_plain_object');/**\n * Relink private _keys and keys with a function value from one container\n * to the new container.\n * Relink means copying if object is pass-by-value and adding a reference\n * if object is pass-by-ref.\n * This prevents deepCopying massive structures like a webgl context.\n */module.exports=function relinkPrivateKeys(toContainer,fromContainer){for(var k in fromContainer){var fromVal=fromContainer[k];var toVal=toContainer[k];if(toVal===fromVal){continue;}if(k.charAt(0)==='_'||typeof fromVal==='function'){// if it already exists at this point, it's something\n// that we recreate each time around, so ignore it\nif(k in toContainer)continue;toContainer[k]=fromVal;}else if(isArrayOrTypedArray(fromVal)&&isArrayOrTypedArray(toVal)&&isPlainObject(fromVal[0])){// filter out data_array items that can contain user objects\n// most of the time the toVal === fromVal check will catch these early\n// but if the user makes new ones we also don't want to recurse in.\nif(k==='customdata'||k==='ids')continue;// recurse into arrays containers\nvar minLen=Math.min(fromVal.length,toVal.length);for(var j=0;j1?(bins[n2-1]-bins[0])/(n2-1):1;var n,test;if(binSize>=0){test=linelow?lessThan:lessOrEqual;}else{test=linelow?greaterOrEqual:greaterThan;}val+=binSize*roundingError*(linelow?-1:1)*(binSize>=0?1:-1);// c is just to avoid infinite loops if there's an error\nwhile(n190)loggers.log('Long binary search...');return n1-1;}};function lessThan(a,b){return ab;}function greaterOrEqual(a,b){return a>=b;}exports.sorterAsc=function(a,b){return a-b;};exports.sorterDes=function(a,b){return b-a;};/**\n * find distinct values in an array, lumping together ones that appear to\n * just be off by a rounding error\n * return the distinct values and the minimum difference between any two\n */exports.distinctVals=function(valsIn){var vals=valsIn.slice();// otherwise we sort the original array...\nvals.sort(exports.sorterAsc);var l=vals.length-1;var minDiff=vals[l]-vals[0]||1;var errDiff=minDiff/(l||1)/10000;var v2=[vals[0]];for(var i=0;ivals[i]+errDiff){minDiff=Math.min(minDiff,vals[i+1]-vals[i]);v2.push(vals[i+1]);}}return{vals:v2,minDiff:minDiff};};/**\n * return the smallest element from (sorted) array arrayIn that's bigger than val,\n * or (reverse) the largest element smaller than val\n * used to find the best tick given the minimum (non-rounded) tick\n * particularly useful for date/time where things are not powers of 10\n * binary search is probably overkill here...\n */exports.roundUp=function(val,arrayIn,reverse){var low=0;var high=arrayIn.length-1;var mid;var c=0;var dlow=reverse?0:1;var dhigh=reverse?1:0;var rounded=reverse?Math.ceil:Math.floor;// c is just to avoid infinite loops if there's an error\nwhile(low0)notReversed=1;if(notOrdered&¬Reversed)return array.sort(sortFn);}return notReversed?array:array.reverse();};/**\n * find index in array 'arr' that minimizes 'fn'\n *\n * @param {array} arr : array where to search\n * @param {fn (optional)} fn : function to minimize,\n * if not given, fn is the identity function\n * @return {integer}\n */exports.findIndexOfMin=function(arr,fn){fn=fn||identity;var min=Infinity;var ind;for(var i=0;ia.length)len=a.length;if(!isNumeric(v))v=false;if(isArrayOrTypedArray(a[0])){b=new Array(len);for(i=0;iarr.length-1)return arr[arr.length-1];var frac=n%1;return frac*arr[Math.ceil(n)]+(1-frac)*arr[Math.floor(n)];};},{\"./array\":718,\"fast-isnumeric\":237}],758:[function(_dereq_,module,exports){/**\n* Copyright 2012-2020, Plotly, Inc.\n* All rights reserved.\n*\n* This source code is licensed under the MIT license found in the\n* LICENSE file in the root directory of this source tree.\n*/'use strict';var rgba=_dereq_('color-normalize');function str2RgbaArray(color){if(!color)return[0,0,0,1];return rgba(color);}module.exports=str2RgbaArray;},{\"color-normalize\":122}],759:[function(_dereq_,module,exports){/**\n* Copyright 2012-2020, Plotly, Inc.\n* All rights reserved.\n*\n* This source code is licensed under the MIT license found in the\n* LICENSE file in the root directory of this source tree.\n*/'use strict';/* global MathJax:false */var d3=_dereq_('d3');var Lib=_dereq_('../lib');var xmlnsNamespaces=_dereq_('../constants/xmlns_namespaces');var LINE_SPACING=_dereq_('../constants/alignment').LINE_SPACING;// text converter\nfunction getSize(_selection,_dimension){return _selection.node().getBoundingClientRect()[_dimension];}var FIND_TEX=/([^$]*)([$]+[^$]*[$]+)([^$]*)/;exports.convertToTspans=function(_context,gd,_callback){var str=_context.text();// Until we get tex integrated more fully (so it can be used along with non-tex)\n// allow some elements to prohibit it by attaching 'data-notex' to the original\nvar tex=!_context.attr('data-notex')&&typeof MathJax!=='undefined'&&str.match(FIND_TEX);var parent=d3.select(_context.node().parentNode);if(parent.empty())return;var svgClass=_context.attr('class')?_context.attr('class').split(' ')[0]:'text';svgClass+='-math';parent.selectAll('svg.'+svgClass).remove();parent.selectAll('g.'+svgClass+'-group').remove();_context.style('display',null).attr({// some callers use data-unformatted *from the element* in 'cancel'\n// so we need it here even if we're going to turn it into math\n// these two (plus style and text-anchor attributes) form the key we're\n// going to use for Drawing.bBox\n'data-unformatted':str,'data-math':'N'});function showText(){if(!parent.empty()){svgClass=_context.attr('class')+'-math';parent.select('svg.'+svgClass).remove();}_context.text('').style('white-space','pre');var hasLink=buildSVGText(_context.node(),str);if(hasLink){// at least in Chrome, pointer-events does not seem\n// to be honored in children of elements\n// so if we have an anchor, we have to make the\n// whole element respond\n_context.style('pointer-events','all');}exports.positionText(_context);if(_callback)_callback.call(_context);}if(tex){(gd&&gd._promises||[]).push(new Promise(function(resolve){_context.style('display','none');var fontSize=parseInt(_context.node().style.fontSize,10);var config={fontSize:fontSize};texToSVG(tex[2],config,function(_svgEl,_glyphDefs,_svgBBox){parent.selectAll('svg.'+svgClass).remove();parent.selectAll('g.'+svgClass+'-group').remove();var newSvg=_svgEl&&_svgEl.select('svg');if(!newSvg||!newSvg.node()){showText();resolve();return;}var mathjaxGroup=parent.append('g').classed(svgClass+'-group',true).attr({'pointer-events':'none','data-unformatted':str,'data-math':'Y'});mathjaxGroup.node().appendChild(newSvg.node());// stitch the glyph defs\nif(_glyphDefs&&_glyphDefs.node()){newSvg.node().insertBefore(_glyphDefs.node().cloneNode(true),newSvg.node().firstChild);}newSvg.attr({'class':svgClass,height:_svgBBox.height,preserveAspectRatio:'xMinYMin meet'}).style({overflow:'visible','pointer-events':'none'});var fill=_context.node().style.fill||'black';var g=newSvg.select('g');g.attr({fill:fill,stroke:fill});var newSvgW=getSize(g,'width');var newSvgH=getSize(g,'height');var newX=+_context.attr('x')-newSvgW*{start:0,middle:0.5,end:1}[_context.attr('text-anchor')||'start'];// font baseline is about 1/4 fontSize below centerline\nvar textHeight=fontSize||getSize(_context,'height');var dy=-textHeight/4;if(svgClass[0]==='y'){mathjaxGroup.attr({transform:'rotate('+[-90,+_context.attr('x'),+_context.attr('y')]+') translate('+[-newSvgW/2,dy-newSvgH/2]+')'});newSvg.attr({x:+_context.attr('x'),y:+_context.attr('y')});}else if(svgClass[0]==='l'){newSvg.attr({x:_context.attr('x'),y:dy-newSvgH/2});}else if(svgClass[0]==='a'&&svgClass.indexOf('atitle')!==0){newSvg.attr({x:0,y:dy});}else{newSvg.attr({x:newX,y:+_context.attr('y')+dy-newSvgH/2});}if(_callback)_callback.call(_context,mathjaxGroup);resolve(mathjaxGroup);});}));}else showText();return _context;};// MathJax\nvar LT_MATCH=/(<|<|<)/g;var GT_MATCH=/(>|>|>)/g;function cleanEscapesForTex(s){return s.replace(LT_MATCH,'\\\\lt ').replace(GT_MATCH,'\\\\gt ');}function texToSVG(_texString,_config,_callback){var originalRenderer,originalConfig,originalProcessSectionDelay,tmpDiv;MathJax.Hub.Queue(function(){originalConfig=Lib.extendDeepAll({},MathJax.Hub.config);originalProcessSectionDelay=MathJax.Hub.processSectionDelay;if(MathJax.Hub.processSectionDelay!==undefined){// MathJax 2.5+\nMathJax.Hub.processSectionDelay=0;}return MathJax.Hub.Config({messageStyle:'none',tex2jax:{inlineMath:[['$','$'],['\\\\(','\\\\)']]},displayAlign:'left'});},function(){// Get original renderer\noriginalRenderer=MathJax.Hub.config.menuSettings.renderer;if(originalRenderer!=='SVG'){return MathJax.Hub.setRenderer('SVG');}},function(){var randomID='math-output-'+Lib.randstr({},64);tmpDiv=d3.select('body').append('div').attr({id:randomID}).style({visibility:'hidden',position:'absolute'}).style({'font-size':_config.fontSize+'px'}).text(cleanEscapesForTex(_texString));return MathJax.Hub.Typeset(tmpDiv.node());},function(){var glyphDefs=d3.select('body').select('#MathJax_SVG_glyphs');if(tmpDiv.select('.MathJax_SVG').empty()||!tmpDiv.select('svg').node()){Lib.log('There was an error in the tex syntax.',_texString);_callback();}else{var svgBBox=tmpDiv.select('svg').node().getBoundingClientRect();_callback(tmpDiv.select('.MathJax_SVG'),glyphDefs,svgBBox);}tmpDiv.remove();if(originalRenderer!=='SVG'){return MathJax.Hub.setRenderer(originalRenderer);}},function(){if(originalProcessSectionDelay!==undefined){MathJax.Hub.processSectionDelay=originalProcessSectionDelay;}return MathJax.Hub.Config(originalConfig);});}var TAG_STYLES={// would like to use baseline-shift for sub/sup but FF doesn't support it\n// so we need to use dy along with the uber hacky shift-back-to\n// baseline below\nsup:'font-size:70%',sub:'font-size:70%',b:'font-weight:bold',i:'font-style:italic',a:'cursor:pointer',span:'',em:'font-style:italic;font-weight:bold'};// baseline shifts for sub and sup\nvar SHIFT_DY={sub:'0.3em',sup:'-0.6em'};// reset baseline by adding a tspan (empty except for a zero-width space)\n// with dy of -70% * SHIFT_DY (because font-size=70%)\nvar RESET_DY={sub:'-0.21em',sup:'0.42em'};var ZERO_WIDTH_SPACE='\\u200b';/*\n * Whitelist of protocols in user-supplied urls. Mostly we want to avoid javascript\n * and related attack vectors. The empty items are there for IE, that in various\n * versions treats relative paths as having different flavors of no protocol, while\n * other browsers have these explicitly inherit the protocol of the page they're in.\n */var PROTOCOLS=['http:','https:','mailto:','',undefined,':'];var NEWLINES=exports.NEWLINES=/(\\r\\n?|\\n)/g;var SPLIT_TAGS=/(<[^<>]*>)/;var ONE_TAG=/<(\\/?)([^ >]*)(\\s+(.*))?>/i;var BR_TAG=//i;exports.BR_TAG_ALL=//gi;/*\n * style and href: pull them out of either single or double quotes. Also\n * - target: (_blank|_self|_parent|_top|framename)\n * note that you can't use target to get a popup but if you use popup,\n * a `framename` will be passed along as the name of the popup window.\n * per the spec, cannot contain whitespace.\n * for backward compatibility we default to '_blank'\n * - popup: a custom one for us to enable popup (new window) links. String\n * for window.open -> strWindowFeatures, like 'menubar=yes,width=500,height=550'\n * note that at least in Chrome, you need to give at least one property\n * in this string or the page will open in a new tab anyway. We follow this\n * convention and will not make a popup if this string is empty.\n * per the spec, cannot contain whitespace.\n *\n * Because we hack in other attributes with style (sub & sup), drop any trailing\n * semicolon in user-supplied styles so we can consistently append the tag-dependent style\n *\n * These are for tag attributes; Chrome anyway will convert entities in\n * attribute values, but not in attribute names\n * you can test this by for example:\n * > p = document.createElement('p')\n * > p.innerHTML = 'Hi'\n * > p.innerHTML\n * <- 'Hi'\n */var STYLEMATCH=/(^|[\\s\"'])style\\s*=\\s*(\"([^\"]*);?\"|'([^']*);?')/i;var HREFMATCH=/(^|[\\s\"'])href\\s*=\\s*(\"([^\"]*)\"|'([^']*)')/i;var TARGETMATCH=/(^|[\\s\"'])target\\s*=\\s*(\"([^\"\\s]*)\"|'([^'\\s]*)')/i;var POPUPMATCH=/(^|[\\s\"'])popup\\s*=\\s*(\"([\\w=,]*)\"|'([\\w=,]*)')/i;// dedicated matcher for these quoted regexes, that can return their results\n// in two different places\nfunction getQuotedMatch(_str,re){if(!_str)return null;var match=_str.match(re);var result=match&&(match[3]||match[4]);return result&&convertEntities(result);}var COLORMATCH=/(^|;)\\s*color:/;/**\n * Strip string of tags\n *\n * @param {string} _str : input string\n * @param {object} opts :\n * - len {number} max length of output string\n * - allowedTags {array} list of pseudo-html tags to NOT strip\n * @return {string}\n */exports.plainText=function(_str,opts){opts=opts||{};var len=opts.len!==undefined&&opts.len!==-1?opts.len:Infinity;var allowedTags=opts.allowedTags!==undefined?opts.allowedTags:['br'];var ellipsis='...';var eLen=ellipsis.length;var oldParts=_str.split(SPLIT_TAGS);var newParts=[];var prevTag='';var l=0;for(var i=0;ieLen){newParts.push(p.substr(0,pLen2-eLen)+ellipsis);}else{newParts.push(p.substr(0,pLen2));}break;}prevTag='';}}return newParts.join('');};/*\n * N.B. HTML entities are listed without the leading '&' and trailing ';'\n * https://www.freeformatter.com/html-entities.html\n *\n * FWIW if we wanted to support the full set, it has 2261 entries:\n * https://www.w3.org/TR/html5/entities.json\n * though I notice that some of these are duplicates and/or are missing \";\"\n * eg: \"&\", \"&\", \"&\", and \"&\" all map to \"&\"\n * We no longer need to include numeric entities here, these are now handled\n * by String.fromCodePoint/fromCharCode\n *\n * Anyway the only ones that are really important to allow are the HTML special\n * chars <, >, and &, because these ones can trigger special processing if not\n * replaced by the corresponding entity.\n */var entityToUnicode={mu:'μ',amp:'&',lt:'<',gt:'>',nbsp:' ',times:'×',plusmn:'±',deg:'°'};// NOTE: in general entities can contain uppercase too (so [a-zA-Z]) but all the\n// ones we support use only lowercase. If we ever change that, update the regex.\nvar ENTITY_MATCH=/&(#\\d+|#x[\\da-fA-F]+|[a-z]+);/g;function convertEntities(_str){return _str.replace(ENTITY_MATCH,function(fullMatch,innerMatch){var outChar;if(innerMatch.charAt(0)==='#'){// cannot use String.fromCodePoint in IE\noutChar=fromCodePoint(innerMatch.charAt(1)==='x'?parseInt(innerMatch.substr(2),16):parseInt(innerMatch.substr(1),10));}else outChar=entityToUnicode[innerMatch];// as in regular HTML, if we didn't decode the entity just\n// leave the raw text in place.\nreturn outChar||fullMatch;});}exports.convertEntities=convertEntities;function fromCodePoint(code){// Don't allow overflow. In Chrome this turns into � but I feel like it's\n// more useful to just not convert it at all.\nif(code>0x10FFFF)return;var stringFromCodePoint=String.fromCodePoint;if(stringFromCodePoint)return stringFromCodePoint(code);// IE doesn't have String.fromCodePoint\n// see https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/fromCodePoint\nvar stringFromCharCode=String.fromCharCode;if(code<=0xFFFF)return stringFromCharCode(code);return stringFromCharCode((code>>10)+0xD7C0,code%0x400+0xDC00);}/*\n * buildSVGText: convert our pseudo-html into SVG tspan elements, and attach these\n * to containerNode\n *\n * @param {svg text element} containerNode: the node to insert this text into\n * @param {string} str: the pseudo-html string to convert to svg\n *\n * @returns {bool}: does the result contain any links? We need to handle the text element\n * somewhat differently if it does, so just keep track of this when it happens.\n */function buildSVGText(containerNode,str){/*\n * Normalize behavior between IE and others wrt newlines and whitespace:pre\n * this combination makes IE barf https://github.com/plotly/plotly.js/issues/746\n * Chrome and FF display \\n, \\r, or \\r\\n as a space in this mode.\n * I feel like at some point we turned these into
but currently we don't so\n * I'm just going to cement what we do now in Chrome and FF\n */str=str.replace(NEWLINES,' ');var hasLink=false;// as we're building the text, keep track of what elements we're nested inside\n// nodeStack will be an array of {node, type, style, href, target, popup}\n// where only type: 'a' gets the last 3 and node is only added when it's created\nvar nodeStack=[];var currentNode;var currentLine=-1;function newLine(){currentLine++;var lineNode=document.createElementNS(xmlnsNamespaces.svg,'tspan');d3.select(lineNode).attr({class:'line',dy:currentLine*LINE_SPACING+'em'});containerNode.appendChild(lineNode);currentNode=lineNode;var oldNodeStack=nodeStack;nodeStack=[{node:lineNode}];if(oldNodeStack.length>1){for(var i=1;i.',str);return;}var innerNode=nodeStack.pop();if(type!==innerNode.type){Lib.log('Start tag <'+innerNode.type+'> doesnt match end tag <'+type+'>. Pretending it did match.',str);}currentNode=nodeStack[nodeStack.length-1].node;}var hasLines=BR_TAG.test(str);if(hasLines)newLine();else{currentNode=containerNode;nodeStack=[{node:containerNode}];}var parts=str.split(SPLIT_TAGS);for(var i=0;icache.ts+minInterval){exec();return;}cache.timer=setTimeout(function(){exec();cache.timer=null;},minInterval);};exports.done=function(id){var cache=timerCache[id];if(!cache||!cache.timer)return Promise.resolve();return new Promise(function(resolve){var previousOnDone=cache.onDone;cache.onDone=function onDone(){if(previousOnDone)previousOnDone();resolve();cache.onDone=null;};});};/**\n * Clear the throttle cache for one or all timers\n * @param {optional string} id:\n * if provided, clear just this timer\n * if omitted, clear all timers (mainly useful for testing)\n */exports.clear=function(id){if(id){_clearTimeout(timerCache[id]);delete timerCache[id];}else{for(var idi in timerCache)exports.clear(idi);}};function _clearTimeout(cache){if(cache&&cache.timer!==null){clearTimeout(cache.timer);cache.timer=null;}}},{}],761:[function(_dereq_,module,exports){/**\n* Copyright 2012-2020, Plotly, Inc.\n* All rights reserved.\n*\n* This source code is licensed under the MIT license found in the\n* LICENSE file in the root directory of this source tree.\n*/'use strict';var isNumeric=_dereq_('fast-isnumeric');/**\n * convert a linear value into a logged value, folding negative numbers into\n * the given range\n */module.exports=function toLogRange(val,range){if(val>0)return Math.log(val)/Math.LN10;// move a negative value reference to a log axis - just put the\n// result at the lowest range value on the plot (or if the range also went negative,\n// one millionth of the top of the range)\nvar newVal=Math.log(Math.min(range[0],range[1]))/Math.LN10;if(!isNumeric(newVal))newVal=Math.log(Math.max(range[0],range[1]))/Math.LN10-6;return newVal;};},{\"fast-isnumeric\":237}],762:[function(_dereq_,module,exports){/**\n* Copyright 2012-2020, Plotly, Inc.\n* All rights reserved.\n*\n* This source code is licensed under the MIT license found in the\n* LICENSE file in the root directory of this source tree.\n*/'use strict';var topojsonUtils=module.exports={};var locationmodeToLayer=_dereq_('../plots/geo/constants').locationmodeToLayer;var topojsonFeature=_dereq_('topojson-client').feature;topojsonUtils.getTopojsonName=function(geoLayout){return[geoLayout.scope.replace(/ /g,'-'),'_',geoLayout.resolution.toString(),'m'].join('');};topojsonUtils.getTopojsonPath=function(topojsonURL,topojsonName){return topojsonURL+topojsonName+'.json';};topojsonUtils.getTopojsonFeatures=function(trace,topojson){var layer=locationmodeToLayer[trace.locationmode];var obj=topojson.objects[layer];return topojsonFeature(topojson,obj).features;};},{\"../plots/geo/constants\":813,\"topojson-client\":538}],763:[function(_dereq_,module,exports){/**\n* Copyright 2012-2020, Plotly, Inc.\n* All rights reserved.\n*\n* This source code is licensed under the MIT license found in the\n* LICENSE file in the root directory of this source tree.\n*/'use strict';module.exports={moduleType:'locale',name:'en-US',dictionary:{'Click to enter Colorscale title':'Click to enter Colorscale title'},format:{date:'%m/%d/%Y'}};},{}],764:[function(_dereq_,module,exports){/**\n* Copyright 2012-2020, Plotly, Inc.\n* All rights reserved.\n*\n* This source code is licensed under the MIT license found in the\n* LICENSE file in the root directory of this source tree.\n*/'use strict';module.exports={moduleType:'locale',name:'en',dictionary:{'Click to enter Colorscale title':'Click to enter Colourscale title'},format:{days:['Sunday','Monday','Tuesday','Wednesday','Thursday','Friday','Saturday'],shortDays:['Sun','Mon','Tue','Wed','Thu','Fri','Sat'],months:['January','February','March','April','May','June','July','August','September','October','November','December'],shortMonths:['Jan','Feb','Mar','Apr','May','Jun','Jul','Aug','Sep','Oct','Nov','Dec'],periods:['AM','PM'],dateTime:'%a %b %e %X %Y',date:'%d/%m/%Y',time:'%H:%M:%S',decimal:'.',thousands:',',grouping:[3],currency:['$',''],year:'%Y',month:'%b %Y',dayMonth:'%b %-d',dayMonthYear:'%b %-d, %Y'}};},{}],765:[function(_dereq_,module,exports){/**\n* Copyright 2012-2020, Plotly, Inc.\n* All rights reserved.\n*\n* This source code is licensed under the MIT license found in the\n* LICENSE file in the root directory of this source tree.\n*/'use strict';var Registry=_dereq_('../registry');/*\n * containerArrayMatch: does this attribute string point into a\n * layout container array?\n *\n * @param {String} astr: an attribute string, like *annotations[2].text*\n *\n * @returns {Object | false} Returns false if `astr` doesn't match a container\n * array. If it does, returns:\n * {array: {String}, index: {Number}, property: {String}}\n * ie the attribute string for the array, the index within the array (or ''\n * if the whole array) and the property within that (or '' if the whole array\n * or the whole object)\n */module.exports=function containerArrayMatch(astr){var rootContainers=Registry.layoutArrayContainers;var regexpContainers=Registry.layoutArrayRegexes;var rootPart=astr.split('[')[0];var arrayStr;var match;// look for regexp matches first, because they may be nested inside root matches\n// eg updatemenus[i].buttons is nested inside updatemenus\nfor(var i=0;i0){Lib.log('Clearing previous rejected promises from queue.');}gd._promises=[];};// make a few changes to the layout right away\n// before it gets used for anything\n// backward compatibility and cleanup of nonstandard options\nexports.cleanLayout=function(layout){var i,j;if(!layout)layout={};// cannot have (x|y)axis1, numbering goes axis, axis2, axis3...\nif(layout.xaxis1){if(!layout.xaxis)layout.xaxis=layout.xaxis1;delete layout.xaxis1;}if(layout.yaxis1){if(!layout.yaxis)layout.yaxis=layout.yaxis1;delete layout.yaxis1;}if(layout.scene1){if(!layout.scene)layout.scene=layout.scene1;delete layout.scene1;}var axisAttrRegex=(Plots.subplotsRegistry.cartesian||{}).attrRegex;var polarAttrRegex=(Plots.subplotsRegistry.polar||{}).attrRegex;var ternaryAttrRegex=(Plots.subplotsRegistry.ternary||{}).attrRegex;var sceneAttrRegex=(Plots.subplotsRegistry.gl3d||{}).attrRegex;var keys=Object.keys(layout);for(i=0;i tickmode\nif(ax.autotick!==undefined){if(ax.tickmode===undefined){ax.tickmode=ax.autotick?'auto':'linear';}delete ax.autotick;}cleanTitle(ax);}else if(polarAttrRegex&&polarAttrRegex.test(key)){// modifications for polar\nvar polar=layout[key];cleanTitle(polar.radialaxis);}else if(ternaryAttrRegex&&ternaryAttrRegex.test(key)){// modifications for ternary\nvar ternary=layout[key];cleanTitle(ternary.aaxis);cleanTitle(ternary.baxis);cleanTitle(ternary.caxis);}else if(sceneAttrRegex&&sceneAttrRegex.test(key)){// modifications for 3D scenes\nvar scene=layout[key];// clean old Camera coords\nvar cameraposition=scene.cameraposition;if(Array.isArray(cameraposition)&&cameraposition[0].length===4){var rotation=cameraposition[0];var center=cameraposition[1];var radius=cameraposition[2];var mat=m4FromQuat([],rotation);var eye=[];for(j=0;j<3;++j){eye[j]=center[j]+radius*mat[2+4*j];}scene.camera={eye:{x:eye[0],y:eye[1],z:eye[2]},center:{x:center[0],y:center[1],z:center[2]},up:{x:0,y:0,z:1}// we just ignore calculating camera z up in this case\n};delete scene.cameraposition;}// clean axis titles\ncleanTitle(scene.xaxis);cleanTitle(scene.yaxis);cleanTitle(scene.zaxis);}}var annotationsLen=Array.isArray(layout.annotations)?layout.annotations.length:0;for(i=0;i3){legend.x=1.02;legend.xanchor='left';}else if(legend.x<-2){legend.x=-0.02;legend.xanchor='right';}if(legend.y>3){legend.y=1.02;legend.yanchor='bottom';}else if(legend.y<-2){legend.y=-0.02;legend.yanchor='top';}}// clean plot title\ncleanTitle(layout);/*\n * Moved from rotate -> orbit for dragmode\n */if(layout.dragmode==='rotate')layout.dragmode='orbit';// sanitize rgb(fractions) and rgba(fractions) that old tinycolor\n// supported, but new tinycolor does not because they're not valid css\nColor.clean(layout);// clean the layout container in layout.template\nif(layout.template&&layout.template.layout){exports.cleanLayout(layout.template.layout);}return layout;};function cleanAxRef(container,attr){var valIn=container[attr];var axLetter=attr.charAt(0);if(valIn&&valIn!=='paper'){container[attr]=cleanId(valIn,axLetter);}}/**\n * Cleans up old title attribute structure (flat) in favor of the new one (nested).\n *\n * @param {Object} titleContainer - an object potentially including deprecated title attributes\n */function cleanTitle(titleContainer){if(titleContainer){// title -> title.text\n// (although title used to be a string attribute,\n// numbers are accepted as well)\nif(typeof titleContainer.title==='string'||typeof titleContainer.title==='number'){titleContainer.title={text:titleContainer.title};}rewireAttr('titlefont','font');rewireAttr('titleposition','position');rewireAttr('titleside','side');rewireAttr('titleoffset','offset');}function rewireAttr(oldAttrName,newAttrName){var oldAttrSet=titleContainer[oldAttrName];var newAttrSet=titleContainer.title&&titleContainer.title[newAttrName];if(oldAttrSet&&!newAttrSet){// Ensure title object exists\nif(!titleContainer.title){titleContainer.title={};}titleContainer.title[newAttrName]=titleContainer[oldAttrName];delete titleContainer[oldAttrName];}}}/*\n * cleanData: Make a few changes to the data for backward compatibility\n * before it gets used for anything. Modifies the data traces users provide.\n *\n * Important: if you're going to add something here that modifies a data array,\n * update it in place so the new array === the old one.\n */exports.cleanData=function(data){for(var tracei=0;traceiscale, reversescl->reversescale\nif('scl'in trace&&!('colorscale'in trace)){trace.colorscale=trace.scl;delete trace.scl;}if('reversescl'in trace&&!('reversescale'in trace)){trace.reversescale=trace.reversescl;delete trace.reversescl;}// axis ids x1 -> x, y1-> y\nif(trace.xaxis)trace.xaxis=cleanId(trace.xaxis,'x');if(trace.yaxis)trace.yaxis=cleanId(trace.yaxis,'y');// scene ids scene1 -> scene\nif(traceIs(trace,'gl3d')&&trace.scene){trace.scene=Plots.subplotsRegistry.gl3d.cleanId(trace.scene);}if(!traceIs(trace,'pie-like')&&!traceIs(trace,'bar-like')){if(Array.isArray(trace.textposition)){for(i=0;i0)return attr.substr(0,tail);}/*\n * hasParent: does an attribute object contain a parent of the given attribute?\n * for example, given 'images[2].x' do we also have 'images' or 'images[2]'?\n *\n * @param {Object} aobj\n * update object, whose keys are attribute strings and values are their new settings\n * @param {string} attr\n * the attribute string to test against\n * @returns {Boolean}\n * is a parent of attr present in aobj?\n */exports.hasParent=function(aobj,attr){var attrParent=getParent(attr);while(attrParent){if(attrParent in aobj)return true;attrParent=getParent(attrParent);}return false;};/**\n * Empty out types for all axes containing these traces so we auto-set them again\n *\n * @param {object} gd\n * @param {[integer]} traces: trace indices to search for axes to clear the types of\n * @param {object} layoutUpdate: any update being done concurrently to the layout,\n * which may supercede clearing the axis types\n */var axLetters=['x','y','z'];exports.clearAxisTypes=function(gd,traces,layoutUpdate){for(var i=0;i relayout calls -> edits we get here\n * (as prepared in _relayout):\n *\n * add an empty obj -> {'annotations[2]': 'add'} -> {2: {'': 'add'}}\n * add a specific obj -> {'annotations[2]': {attrs}} -> {2: {'': {attrs}}}\n * delete an obj -> {'annotations[2]': 'remove'} -> {2: {'': 'remove'}}\n * -> {'annotations[2]': null} -> {2: {'': null}}\n * delete the whole array -> {'annotations': 'remove'} -> {'': {'': 'remove'}}\n * -> {'annotations': null} -> {'': {'': null}}\n * edit an object -> {'annotations[2].text': 'boo'} -> {2: {'text': 'boo'}}\n *\n * You can combine many edits to different objects. Objects are added and edited\n * in ascending order, then removed in descending order.\n * For example, starting with [a, b, c], if you want to:\n * - replace b with d:\n * {'annotations[1]': d, 'annotations[2]': null} (b is item 2 after adding d)\n * - add a new item d between a and b, and edit b:\n * {'annotations[1]': d, 'annotations[2].x': newX} (b is item 2 after adding d)\n * - delete b and edit c:\n * {'annotations[1]': null, 'annotations[2].x': newX} (c is edited before b is removed)\n *\n * You CANNOT combine adding/deleting an item at index `i` with edits to the same index `i`\n * You CANNOT combine replacing/deleting the whole array with anything else (for the same array).\n *\n * @param {HTMLDivElement} gd\n * the DOM element of the graph container div\n * @param {Lib.nestedProperty} componentType: the array we are editing\n * @param {Object} edits\n * the changes to make; keys are indices to edit, values are themselves objects:\n * {attr: newValue} of changes to make to that index (with add/remove behavior\n * in special values of the empty attr)\n * @param {Object} flags\n * the flags for which actions we're going to perform to display these (and\n * any other) changes. If we're already `recalc`ing, we don't need to redraw\n * individual items\n * @param {function} _nestedProperty\n * a (possibly modified for gui edits) nestedProperty constructor\n * The modified version takes a 3rd argument, for a prefix to the attribute\n * string necessary for storing GUI edits\n *\n * @returns {bool} `true` if it managed to complete drawing of the changes\n * `false` would mean the parent should replot.\n */exports.applyContainerArrayChanges=function applyContainerArrayChanges(gd,np,edits,flags,_nestedProperty){var componentType=np.astr;var supplyComponentDefaults=Registry.getComponentMethod(componentType,'supplyLayoutDefaults');var draw=Registry.getComponentMethod(componentType,'draw');var drawOne=Registry.getComponentMethod(componentType,'drawOne');var replotLater=flags.replot||flags.recalc||supplyComponentDefaults===noop||draw===noop;var layout=gd.layout;var fullLayout=gd._fullLayout;if(edits['']){if(Object.keys(edits).length>1){Loggers.warn('Full array edits are incompatible with other edits',componentType);}var fullVal=edits[''][''];if(isRemoveVal(fullVal))np.set(null);else if(Array.isArray(fullVal))np.set(fullVal);else{Loggers.warn('Unrecognized full array edit value',componentType,fullVal);return true;}if(replotLater)return false;supplyComponentDefaults(layout,fullLayout);draw(gd);return true;}var componentNums=Object.keys(edits).map(Number).sort(sorterAsc);var componentArrayIn=np.get();var componentArray=componentArrayIn||[];// componentArrayFull is used just to keep splices in line between\n// full and input arrays, so private keys can be copied over after\n// redoing supplyDefaults\n// TODO: this assumes componentArray is in gd.layout - which will not be\n// true after we extend this to restyle\nvar componentArrayFull=_nestedProperty(fullLayout,componentType).get();var deletes=[];var firstIndexChange=-1;var maxIndex=componentArray.length;var i;var j;var componentNum;var objEdits;var objKeys;var objVal;var adding,prefix;// first make the add and edit changes\nfor(i=0;icomponentArray.length-(adding?0:1)){Loggers.warn('index out of range',componentType,componentNum);continue;}if(objVal!==undefined){if(objKeys.length>1){Loggers.warn('Insertion & removal are incompatible with edits to the same index.',componentType,componentNum);}if(isRemoveVal(objVal)){deletes.push(componentNum);}else if(adding){if(objVal==='add')objVal={};componentArray.splice(componentNum,0,objVal);if(componentArrayFull)componentArrayFull.splice(componentNum,0,{});}else{Loggers.warn('Unrecognized full object edit value',componentType,componentNum,objVal);}if(firstIndexChange===-1)firstIndexChange=componentNum;}else{for(j=0;j=0;i--){componentArray.splice(deletes[i],1);// TODO: this drops private keys that had been stored in componentArrayFull\n// does this have any ill effects?\nif(componentArrayFull)componentArrayFull.splice(deletes[i],1);}if(!componentArray.length)np.set(null);else if(!componentArrayIn)np.set(componentArray);if(replotLater)return false;supplyComponentDefaults(layout,fullLayout);// finally draw all the components we need to\n// if we added or removed any, redraw all after it\nif(drawOne!==noop){var indicesToDraw;if(firstIndexChange===-1){// there's no re-indexing to do, so only redraw components that changed\nindicesToDraw=componentNums;}else{// in case the component array was shortened, we still need do call\n// drawOne on the latter items so they get properly removed\nmaxIndex=Math.max(componentArray.length,maxIndex);indicesToDraw=[];for(i=0;i=firstIndexChange)break;indicesToDraw.push(componentNum);}for(i=firstIndexChange;i href, used to make robust clipPath URLs\nvar base=d3.select('base');gd._context._baseUrl=base.size()&&base.attr('href')?window.location.href.split('#')[0]:'';}var context=gd._context;var i,keys,key;if(config){keys=Object.keys(config);for(i=0;i=gd.data.length||index<-gd.data.length){throw new Error(arrayName+' must be valid indices for gd.data.');}// check that indices aren't repeated\nif(indices.indexOf(index,i+1)>-1||index>=0&&indices.indexOf(-gd.data.length+index)>-1||index<0&&indices.indexOf(gd.data.length+index)>-1){throw new Error('each index in '+arrayName+' must be unique.');}}}/**\n * Private function used by Plotly.moveTraces to check input args\n *\n * @param gd\n * @param currentIndices\n * @param newIndices\n */function checkMoveTracesArgs(gd,currentIndices,newIndices){// check that gd has attribute 'data' and 'data' is array\nif(!Array.isArray(gd.data)){throw new Error('gd.data must be an array.');}// validate currentIndices array\nif(typeof currentIndices==='undefined'){throw new Error('currentIndices is a required argument.');}else if(!Array.isArray(currentIndices)){currentIndices=[currentIndices];}assertIndexArray(gd,currentIndices,'currentIndices');// validate newIndices array if it exists\nif(typeof newIndices!=='undefined'&&!Array.isArray(newIndices)){newIndices=[newIndices];}if(typeof newIndices!=='undefined'){assertIndexArray(gd,newIndices,'newIndices');}// check currentIndices and newIndices are the same length if newIdices exists\nif(typeof newIndices!=='undefined'&¤tIndices.length!==newIndices.length){throw new Error('current and new indices must be of equal length.');}}/**\n * A private function to reduce the type checking clutter in addTraces.\n *\n * @param gd\n * @param traces\n * @param newIndices\n */function checkAddTracesArgs(gd,traces,newIndices){var i,value;// check that gd has attribute 'data' and 'data' is array\nif(!Array.isArray(gd.data)){throw new Error('gd.data must be an array.');}// make sure traces exists\nif(typeof traces==='undefined'){throw new Error('traces must be defined.');}// make sure traces is an array\nif(!Array.isArray(traces)){traces=[traces];}// make sure each value in traces is an object\nfor(i=0;i=0&&maxp=0&&maxp-1){replace(key,key.replace('titlefont','title.font'));}else if(key.indexOf('titleposition')>-1){replace(key,key.replace('titleposition','title.position'));}else if(key.indexOf('titleside')>-1){replace(key,key.replace('titleside','title.side'));}else if(key.indexOf('titleoffset')>-1){replace(key,key.replace('titleoffset','title.offset'));}}function replace(oldAttrStr,newAttrStr){aobj[newAttrStr]=aobj[oldAttrStr];delete aobj[oldAttrStr];}}/**\n * relayout: update layout attributes of an existing plot\n *\n * Can be called two ways:\n *\n * Signature 1:\n * @param {String | HTMLDivElement} gd\n * the id or dom element of the graph container div\n * @param {String} astr\n * attribute string (like `'xaxis.range[0]'`) to update\n * @param {*} val\n * value to give this attribute\n *\n * Signature 2:\n * @param {String | HTMLDivElement} gd\n * (as in signature 1)\n * @param {Object} aobj\n * attribute object `{astr1: val1, astr2: val2 ...}`\n * allows setting multiple attributes simultaneously\n */function relayout(gd,astr,val){gd=Lib.getGraphDiv(gd);helpers.clearPromiseQueue(gd);if(gd.framework&&gd.framework.isPolar){return Promise.resolve(gd);}var aobj={};if(typeof astr==='string'){aobj[astr]=val;}else if(Lib.isPlainObject(astr)){aobj=Lib.extendFlat({},astr);}else{Lib.warn('Relayout fail.',astr,val);return Promise.reject();}if(Object.keys(aobj).length)gd.changed=true;var specs=_relayout(gd,aobj);var flags=specs.flags;// clear calcdata if required\nif(flags.calc)gd.calcdata=undefined;// fill in redraw sequence\n// even if we don't have anything left in aobj,\n// something may have happened within relayout that we\n// need to wait for\nvar seq=[Plots.previousPromises];if(flags.layoutReplot){seq.push(subroutines.layoutReplot);}else if(Object.keys(aobj).length){axRangeSupplyDefaultsByPass(gd,flags,specs)||Plots.supplyDefaults(gd);if(flags.legend)seq.push(subroutines.doLegend);if(flags.layoutstyle)seq.push(subroutines.layoutStyles);if(flags.axrange)addAxRangeSequence(seq,specs.rangesAltered);if(flags.ticks)seq.push(subroutines.doTicksRelayout);if(flags.modebar)seq.push(subroutines.doModeBar);if(flags.camera)seq.push(subroutines.doCamera);if(flags.colorbars)seq.push(subroutines.doColorBars);seq.push(emitAfterPlot);}seq.push(Plots.rehover,Plots.redrag);Queue.add(gd,relayout,[gd,specs.undoit],relayout,[gd,specs.redoit]);var plotDone=Lib.syncOrAsync(seq,gd);if(!plotDone||!plotDone.then)plotDone=Promise.resolve(gd);return plotDone.then(function(){gd.emit('plotly_relayout',specs.eventData);return gd;});}// Optimization mostly for large splom traces where\n// Plots.supplyDefaults can take > 100ms\nfunction axRangeSupplyDefaultsByPass(gd,flags,specs){var fullLayout=gd._fullLayout;if(!flags.axrange)return false;for(var k in flags){if(k!=='axrange'&&flags[k])return false;}for(var axId in specs.rangesAltered){var axName=Axes.id2name(axId);var axIn=gd.layout[axName];var axOut=fullLayout[axName];axOut.autorange=axIn.autorange;axOut.range=axIn.range.slice();axOut.cleanRange();if(axOut._matchGroup){for(var axId2 in axOut._matchGroup){if(axId2!==axId){var ax2=fullLayout[Axes.id2name(axId2)];ax2.autorange=axOut.autorange;ax2.range=axOut.range.slice();ax2._input.range=axOut.range.slice();}}}}return true;}function addAxRangeSequence(seq,rangesAltered){// N.B. leave as sequence of subroutines (for now) instead of\n// subroutine of its own so that finalDraw always gets\n// executed after drawData\nvar drawAxes=rangesAltered?function(gd){var axIds=[];var skipTitle=true;for(var id in rangesAltered){var ax=Axes.getFromId(gd,id);axIds.push(id);if(ax._matchGroup){for(var id2 in ax._matchGroup){if(!rangesAltered[id2]){axIds.push(id2);}}}if(ax.automargin)skipTitle=false;}return Axes.draw(gd,axIds,{skipTitle:skipTitle});}:function(gd){return Axes.draw(gd,'redraw');};seq.push(clearSelect,subroutines.doAutoRangeAndConstraints,drawAxes,subroutines.drawData,subroutines.finalDraw);}var AX_RANGE_RE=/^[xyz]axis[0-9]*\\.range(\\[[0|1]\\])?$/;var AX_AUTORANGE_RE=/^[xyz]axis[0-9]*\\.autorange$/;var AX_DOMAIN_RE=/^[xyz]axis[0-9]*\\.domain(\\[[0|1]\\])?$/;function _relayout(gd,aobj){var layout=gd.layout;var fullLayout=gd._fullLayout;var guiEditFlag=fullLayout._guiEditing;var layoutNP=makeNP(fullLayout._preGUI,guiEditFlag);var keys=Object.keys(aobj);var axes=Axes.list(gd);var eventData=Lib.extendDeepAll({},aobj);var arrayEdits={};var arrayStr,i,j;cleanDeprecatedAttributeKeys(aobj);keys=Object.keys(aobj);// look for 'allaxes', split out into all axes\n// in case of 3D the axis are nested within a scene which is held in _id\nfor(i=0;i0&&typeof p.parts[pend]!=='string')pend--;// last property in chain (leaf node)\nvar pleaf=p.parts[pend];// leaf plus immediate parent\nvar pleafPlus=p.parts[pend-1]+'.'+pleaf;// trunk nodes (everything except the leaf)\nvar ptrunk=p.parts.slice(0,pend).join('.');var parentIn=nestedProperty(gd.layout,ptrunk).get();var parentFull=nestedProperty(fullLayout,ptrunk).get();var vOld=p.get();if(vi===undefined)continue;redoit[ai]=vi;// axis reverse is special - it is its own inverse\n// op and has no flag.\nundoit[ai]=pleaf==='reverse'?vi:undefinedToNull(vOld);var valObject=PlotSchema.getLayoutValObject(fullLayout,p.parts);if(valObject&&valObject.impliedEdits&&vi!==null){for(var impliedKey in valObject.impliedEdits){doextra(Lib.relativeAttr(ai,impliedKey),valObject.impliedEdits[impliedKey]);}}// Setting width or height to null must reset the graph's width / height\n// back to its initial value as computed during the first pass in Plots.plotAutoSize.\n//\n// To do so, we must manually set them back here using the _initialAutoSize cache.\n// can't use impliedEdits for this because behavior depends on vi\nif(['width','height'].indexOf(ai)!==-1){if(vi){doextra('autosize',null);// currently we don't support autosize one dim only - so\n// explicitly set the other one. Note that doextra will\n// ignore this if the same relayout call also provides oppositeAttr\nvar oppositeAttr=ai==='height'?'width':'height';doextra(oppositeAttr,fullLayout[oppositeAttr]);}else{fullLayout[ai]=gd._initialAutoSize[ai];}}else if(ai==='autosize'){// depends on vi here too, so again can't use impliedEdits\ndoextra('width',vi?null:fullLayout.width);doextra('height',vi?null:fullLayout.height);}else if(pleafPlus.match(AX_RANGE_RE)){// check autorange vs range\nrecordAlteredAxis(pleafPlus);nestedProperty(fullLayout,ptrunk+'._inputRange').set(null);}else if(pleafPlus.match(AX_AUTORANGE_RE)){recordAlteredAxis(pleafPlus);nestedProperty(fullLayout,ptrunk+'._inputRange').set(null);var axFull=nestedProperty(fullLayout,ptrunk).get();if(axFull._inputDomain){// if we're autoranging and this axis has a constrained domain,\n// reset it so we don't get locked into a shrunken size\naxFull._input.domain=axFull._inputDomain.slice();}}else if(pleafPlus.match(AX_DOMAIN_RE)){nestedProperty(fullLayout,ptrunk+'._inputDomain').set(null);}// toggling axis type between log and linear: we need to convert\n// positions for components that are still using linearized values,\n// not data values like newer components.\n// previously we did this for log <-> not-log, but now only do it\n// for log <-> linear\nif(pleaf==='type'){var ax=parentIn;var toLog=parentFull.type==='linear'&&vi==='log';var fromLog=parentFull.type==='log'&&vi==='linear';if(toLog||fromLog){if(!ax||!ax.range){// 2D never gets here, but 3D does\n// I don't think this is needed, but left here in case there\n// are edge cases I'm not thinking of.\ndoextra(ptrunk+'.autorange',true);}else if(!parentFull.autorange){// toggling log without autorange: need to also recalculate ranges\n// because log axes use linearized values for range endpoints\nvar r0=ax.range[0];var r1=ax.range[1];if(toLog){// if both limits are negative, autorange\nif(r0<=0&&r1<=0){doextra(ptrunk+'.autorange',true);}// if one is negative, set it 6 orders below the other.\nif(r0<=0)r0=r1/1e6;else if(r1<=0)r1=r0/1e6;// now set the range values as appropriate\ndoextra(ptrunk+'.range[0]',Math.log(r0)/Math.LN10);doextra(ptrunk+'.range[1]',Math.log(r1)/Math.LN10);}else{doextra(ptrunk+'.range[0]',Math.pow(10,r0));doextra(ptrunk+'.range[1]',Math.pow(10,r1));}}else if(toLog){// just make sure the range is positive and in the right\n// order, it'll get recalculated later\nax.range=ax.range[1]>ax.range[0]?[1,2]:[2,1];}// clear polar view initial stash for radial range so that\n// value get recomputed in correct units\nif(Array.isArray(fullLayout._subplots.polar)&&fullLayout._subplots.polar.length&&fullLayout[p.parts[0]]&&p.parts[1]==='radialaxis'){delete fullLayout[p.parts[0]]._subplot.viewInitial['radialaxis.range'];}// Annotations and images also need to convert to/from linearized coords\n// Shapes do not need this :)\nRegistry.getComponentMethod('annotations','convertCoords')(gd,parentFull,vi,doextra);Registry.getComponentMethod('images','convertCoords')(gd,parentFull,vi,doextra);}else{// any other type changes: the range from the previous type\n// will not make sense, so autorange it.\ndoextra(ptrunk+'.autorange',true);doextra(ptrunk+'.range',null);}nestedProperty(fullLayout,ptrunk+'._inputRange').set(null);}else if(pleaf.match(AX_NAME_PATTERN)){var fullProp=nestedProperty(fullLayout,ai).get();var newType=(vi||{}).type;// This can potentially cause strange behavior if the autotype is not\n// numeric (linear, because we don't auto-log) but the previous type\n// was log. That's a very strange edge case though\nif(!newType||newType==='-')newType='linear';Registry.getComponentMethod('annotations','convertCoords')(gd,fullProp,newType,doextra);Registry.getComponentMethod('images','convertCoords')(gd,fullProp,newType,doextra);}// alter gd.layout\n// collect array component edits for execution all together\n// so we can ensure consistent behavior adding/removing items\n// and order-independence for add/remove/edit all together in\n// one relayout call\nvar containerArrayMatch=manageArrays.containerArrayMatch(ai);if(containerArrayMatch){arrayStr=containerArrayMatch.array;i=containerArrayMatch.index;var propStr=containerArrayMatch.property;var updateValObject=valObject||{editType:'calc'};if(i!==''&&propStr===''){// special handling of undoit if we're adding or removing an element\n// ie 'annotations[2]' which can be {...} (add) or null,\n// does not work when replacing the entire array\nif(manageArrays.isAddVal(vi)){undoit[ai]=null;}else if(manageArrays.isRemoveVal(vi)){undoit[ai]=(nestedProperty(layout,arrayStr).get()||[])[i];}else{Lib.warn('unrecognized full object value',aobj);}}editTypes.update(flags,updateValObject);// prepare the edits object we'll send to applyContainerArrayChanges\nif(!arrayEdits[arrayStr])arrayEdits[arrayStr]={};var objEdits=arrayEdits[arrayStr][i];if(!objEdits)objEdits=arrayEdits[arrayStr][i]={};objEdits[propStr]=vi;delete aobj[ai];}else if(pleaf==='reverse'){// handle axis reversal explicitly, as there's no 'reverse' attribute\nif(parentIn.range)parentIn.range.reverse();else{doextra(ptrunk+'.autorange',true);parentIn.range=[1,0];}if(parentFull.autorange)flags.calc=true;else flags.plot=true;}else{if(fullLayout._has('scatter-like')&&fullLayout._has('regl')&&ai==='dragmode'&&(vi==='lasso'||vi==='select')&&!(vOld==='lasso'||vOld==='select')){flags.plot=true;}else if(fullLayout._has('gl2d')){flags.plot=true;}else if(valObject)editTypes.update(flags,valObject);else flags.calc=true;p.set(vi);}}// now we've collected component edits - execute them all together\nfor(arrayStr in arrayEdits){var finished=manageArrays.applyContainerArrayChanges(gd,layoutNP(layout,arrayStr),arrayEdits[arrayStr],flags,layoutNP);if(!finished)flags.plot=true;}// figure out if we need to recalculate axis constraints\nvar constraints=fullLayout._axisConstraintGroups||[];for(axId in rangesAltered){for(i=0;i1){parts.pop();newRev=nestedProperty(container,parts.join('.')+'.uirevision').get();if(newRev!==undefined)return newRev;}return container.uirevision;}function getFullTraceIndexFromUid(uid,fullData){for(var i=0;i=transitionOpts.length){return transitionOpts[0];}else{return transitionOpts[i];}}else{return transitionOpts;}}function getFrameOpts(i){if(Array.isArray(frameOpts)){if(i>=frameOpts.length){return frameOpts[0];}else{return frameOpts[i];}}else{return frameOpts;}}// Execute a callback after the wrapper function has been called n times.\n// This is used to defer the resolution until a transition has resovled *and*\n// the frame has completed. If it's not done this way, then we get a race\n// condition in which the animation might resolve before a transition is complete\n// or vice versa.\nfunction callbackOnNthTime(cb,n){var cnt=0;return function(){if(cb&&++cnt===n){return cb();}};}return new Promise(function(resolve,reject){function discardExistingFrames(){if(trans._frameQueue.length===0){return;}while(trans._frameQueue.length){var next=trans._frameQueue.pop();if(next.onInterrupt){next.onInterrupt();}}gd.emit('plotly_animationinterrupted',[]);}function queueFrames(frameList){if(frameList.length===0)return;for(var i=0;itrans._timeToNext){nextFrame();}};doFrame();}// This is an animate-local counter that helps match up option input list\n// items with the particular frame.\nvar configCounter=0;function setTransitionConfig(frame){if(Array.isArray(transitionOpts)){if(configCounter>=transitionOpts.length){frame.transitionOpts=transitionOpts[configCounter];}else{frame.transitionOpts=transitionOpts[0];}}else{frame.transitionOpts=transitionOpts;}configCounter++;return frame;}// Disambiguate what's sort of frames have been received\nvar i,frame;var frameList=[];var allFrames=frameOrGroupNameOrFrameList===undefined||frameOrGroupNameOrFrameList===null;var isFrameArray=Array.isArray(frameOrGroupNameOrFrameList);var isSingleFrame=!allFrames&&!isFrameArray&&Lib.isPlainObject(frameOrGroupNameOrFrameList);if(isSingleFrame){// In this case, a simple object has been passed to animate.\nframeList.push({type:'object',data:setTransitionConfig(Lib.extendFlat({},frameOrGroupNameOrFrameList))});}else if(allFrames||['string','number'].indexOf(typeof frameOrGroupNameOrFrameList)!==-1){// In this case, null or undefined has been passed so that we want to\n// animate *all* currently defined frames\nfor(i=0;i0&&idxidx){filteredFrameList.push(frame);}}frameList=filteredFrameList;}}if(frameList.length>0){queueFrames(frameList);}else{// This is the case where there were simply no frames. It's a little strange\n// since there's not much to do:\ngd.emit('plotly_animated');resolve();}});}/**\n * Register new frames\n *\n * @param {string id or DOM element} gd\n * the id or DOM element of the graph container div\n *\n * @param {array of objects} frameList\n * list of frame definitions, in which each object includes any of:\n * - name: {string} name of frame to add\n * - data: {array of objects} trace data\n * - layout {object} layout definition\n * - traces {array} trace indices\n * - baseframe {string} name of frame from which this frame gets defaults\n *\n * @param {array of integers} indices\n * an array of integer indices matching the respective frames in `frameList`. If not\n * provided, an index will be provided in serial order. If already used, the frame\n * will be overwritten.\n */function addFrames(gd,frameList,indices){gd=Lib.getGraphDiv(gd);if(frameList===null||frameList===undefined){return Promise.resolve();}if(!Lib.isPlotDiv(gd)){throw new Error('This element is not a Plotly plot: '+gd+'. It\\'s likely that you\\'ve failed '+'to create a plot before adding frames. For more details, see '+'https://plotly.com/javascript/animations/');}var i,frame,j,idx;var _frames=gd._transitionData._frames;var _frameHash=gd._transitionData._frameHash;if(!Array.isArray(frameList)){throw new Error('addFrames failure: frameList must be an Array of frame definitions'+frameList);}// Create a sorted list of insertions since we run into lots of problems if these\n// aren't in ascending order of index:\n//\n// Strictly for sorting. Make sure this is guaranteed to never collide with any\n// already-exisisting indices:\nvar bigIndex=_frames.length+frameList.length*2;var insertions=[];var _frameHashLocal={};for(i=frameList.length-1;i>=0;i--){if(!Lib.isPlainObject(frameList[i]))continue;// The entire logic for checking for this type of name collision can be removed once we migrate to ES6 and\n// use a Map instead of an Object instance, as Map keys aren't converted to strings.\nvar lookupName=frameList[i].name;var name=(_frameHash[lookupName]||_frameHashLocal[lookupName]||{}).name;var newName=frameList[i].name;var collisionPresent=_frameHash[name]||_frameHashLocal[name];if(name&&newName&&typeof newName==='number'&&collisionPresent&&numericNameWarningCountb.index)return-1;if(a.index=0;i--){frame=insertions[i].frame;if(typeof frame.name==='number'){Lib.warn('Warning: addFrames accepts frames with numeric names, but the numbers are'+'implicitly cast to strings');}if(!frame.name){// Repeatedly assign a default name, incrementing the counter each time until\n// we get a name that's not in the hashed lookup table:\nwhile(_frameHash[frame.name='frame '+gd._transitionData._counter++]);}if(_frameHash[frame.name]){// If frame is present, overwrite its definition:\nfor(j=0;j<_frames.length;j++){if((_frames[j]||{}).name===frame.name)break;}ops.push({type:'replace',index:j,value:frame});revops.unshift({type:'replace',index:j,value:_frames[j]});}else{// Otherwise insert it at the end of the list:\nidx=Math.max(0,Math.min(insertions[i].index,frameCount));ops.push({type:'insert',index:idx,value:frame});revops.unshift({type:'delete',index:idx});frameCount++;}}var undoFunc=Plots.modifyFrames;var redoFunc=Plots.modifyFrames;var undoArgs=[gd,revops];var redoArgs=[gd,ops];if(Queue)Queue.add(gd,undoFunc,undoArgs,redoFunc,redoArgs);return Plots.modifyFrames(gd,ops);}/**\n * Delete frame\n *\n * @param {string id or DOM element} gd\n * the id or DOM element of the graph container div\n *\n * @param {array of integers} frameList\n * list of integer indices of frames to be deleted\n */function deleteFrames(gd,frameList){gd=Lib.getGraphDiv(gd);if(!Lib.isPlotDiv(gd)){throw new Error('This element is not a Plotly plot: '+gd);}var i,idx;var _frames=gd._transitionData._frames;var ops=[];var revops=[];if(!frameList){frameList=[];for(i=0;i<_frames.length;i++){frameList.push(i);}}frameList=frameList.slice();frameList.sort();for(i=frameList.length-1;i>=0;i--){idx=frameList[i];ops.push({type:'delete',index:idx});revops.unshift({type:'insert',index:idx,value:_frames[idx]});}var undoFunc=Plots.modifyFrames;var redoFunc=Plots.modifyFrames;var undoArgs=[gd,revops];var redoArgs=[gd,ops];if(Queue)Queue.add(gd,undoFunc,undoArgs,redoFunc,redoArgs);return Plots.modifyFrames(gd,ops);}/**\n * Purge a graph container div back to its initial pre-Plotly.plot state\n *\n * @param {string id or DOM element} gd\n * the id or DOM element of the graph container div\n */function purge(gd){gd=Lib.getGraphDiv(gd);var fullLayout=gd._fullLayout||{};var fullData=gd._fullData||[];// remove gl contexts\nPlots.cleanPlot([],{},fullData,fullLayout);// purge properties\nPlots.purge(gd);// purge event emitter methods\nEvents.purge(gd);// remove plot container\nif(fullLayout._container)fullLayout._container.remove();// in contrast to Plotly.Plots.purge which does NOT clear _context!\ndelete gd._context;return gd;}// -------------------------------------------------------\n// makePlotFramework: Create the plot container and axes\n// -------------------------------------------------------\nfunction makePlotFramework(gd){var gd3=d3.select(gd);var fullLayout=gd._fullLayout;// Plot container\nfullLayout._container=gd3.selectAll('.plot-container').data([0]);fullLayout._container.enter().insert('div',':first-child').classed('plot-container',true).classed('plotly',true);// Make the svg container\nfullLayout._paperdiv=fullLayout._container.selectAll('.svg-container').data([0]);fullLayout._paperdiv.enter().append('div').classed('svg-container',true).style('position','relative');// Make the graph containers\n// start fresh each time we get here, so we know the order comes out\n// right, rather than enter/exit which can muck up the order\n// TODO: sort out all the ordering so we don't have to\n// explicitly delete anything\n// FIXME: parcoords reuses this object, not the best pattern\nfullLayout._glcontainer=fullLayout._paperdiv.selectAll('.gl-container').data([{}]);fullLayout._glcontainer.enter().append('div').classed('gl-container',true);fullLayout._paperdiv.selectAll('.main-svg').remove();fullLayout._paperdiv.select('.modebar-container').remove();fullLayout._paper=fullLayout._paperdiv.insert('svg',':first-child').classed('main-svg',true);fullLayout._toppaper=fullLayout._paperdiv.append('svg').classed('main-svg',true);fullLayout._modebardiv=fullLayout._paperdiv.append('div');fullLayout._hoverpaper=fullLayout._paperdiv.append('svg').classed('main-svg',true);if(!fullLayout._uid){var otherUids={};d3.selectAll('defs').each(function(){if(this.id)otherUids[this.id.split('-')[1]]=1;});fullLayout._uid=Lib.randstr(otherUids);}fullLayout._paperdiv.selectAll('.main-svg').attr(xmlnsNamespaces.svgAttrs);fullLayout._defs=fullLayout._paper.append('defs').attr('id','defs-'+fullLayout._uid);fullLayout._clips=fullLayout._defs.append('g').classed('clips',true);fullLayout._topdefs=fullLayout._toppaper.append('defs').attr('id','topdefs-'+fullLayout._uid);fullLayout._topclips=fullLayout._topdefs.append('g').classed('clips',true);fullLayout._bgLayer=fullLayout._paper.append('g').classed('bglayer',true);fullLayout._draggers=fullLayout._paper.append('g').classed('draglayer',true);// lower shape/image layer - note that this is behind\n// all subplots data/grids but above the backgrounds\n// except inset subplots, whose backgrounds are drawn\n// inside their own group so that they appear above\n// the data for the main subplot\n// lower shapes and images which are fully referenced to\n// a subplot still get drawn within the subplot's group\n// so they will work correctly on insets\nvar layerBelow=fullLayout._paper.append('g').classed('layer-below',true);fullLayout._imageLowerLayer=layerBelow.append('g').classed('imagelayer',true);fullLayout._shapeLowerLayer=layerBelow.append('g').classed('shapelayer',true);// single cartesian layer for the whole plot\nfullLayout._cartesianlayer=fullLayout._paper.append('g').classed('cartesianlayer',true);// single polar layer for the whole plot\nfullLayout._polarlayer=fullLayout._paper.append('g').classed('polarlayer',true);// single ternary layer for the whole plot\nfullLayout._ternarylayer=fullLayout._paper.append('g').classed('ternarylayer',true);// single geo layer for the whole plot\nfullLayout._geolayer=fullLayout._paper.append('g').classed('geolayer',true);// single funnelarea layer for the whole plot\nfullLayout._funnelarealayer=fullLayout._paper.append('g').classed('funnelarealayer',true);// single pie layer for the whole plot\nfullLayout._pielayer=fullLayout._paper.append('g').classed('pielayer',true);// single treemap layer for the whole plot\nfullLayout._treemaplayer=fullLayout._paper.append('g').classed('treemaplayer',true);// single sunburst layer for the whole plot\nfullLayout._sunburstlayer=fullLayout._paper.append('g').classed('sunburstlayer',true);// single indicator layer for the whole plot\nfullLayout._indicatorlayer=fullLayout._toppaper.append('g').classed('indicatorlayer',true);// fill in image server scrape-svg\nfullLayout._glimages=fullLayout._paper.append('g').classed('glimages',true);// lastly upper shapes, info (legend, annotations) and hover layers go on top\n// these are in a different svg element normally, but get collapsed into a single\n// svg when exporting (after inserting 3D)\n// upper shapes/images are only those drawn above the whole plot, including subplots\nvar layerAbove=fullLayout._toppaper.append('g').classed('layer-above',true);fullLayout._imageUpperLayer=layerAbove.append('g').classed('imagelayer',true);fullLayout._shapeUpperLayer=layerAbove.append('g').classed('shapelayer',true);fullLayout._infolayer=fullLayout._toppaper.append('g').classed('infolayer',true);fullLayout._menulayer=fullLayout._toppaper.append('g').classed('menulayer',true);fullLayout._zoomlayer=fullLayout._toppaper.append('g').classed('zoomlayer',true);fullLayout._hoverlayer=fullLayout._hoverpaper.append('g').classed('hoverlayer',true);// Make the modebar container\nfullLayout._modebardiv.classed('modebar-container',true).style('position','absolute').style('top','0px').style('right','0px');gd.emit('plotly_framework');}exports.animate=animate;exports.addFrames=addFrames;exports.deleteFrames=deleteFrames;exports.addTraces=addTraces;exports.deleteTraces=deleteTraces;exports.extendTraces=extendTraces;exports.moveTraces=moveTraces;exports.prependTraces=prependTraces;exports.newPlot=newPlot;exports.plot=plot;exports.purge=purge;exports.react=react;exports.redraw=redraw;exports.relayout=relayout;exports.restyle=restyle;exports.setPlotConfig=setPlotConfig;exports.update=update;exports._guiRelayout=guiEdit(relayout);exports._guiRestyle=guiEdit(restyle);exports._guiUpdate=guiEdit(update);exports._storeDirectGUIEdit=_storeDirectGUIEdit;},{\"../components/color\":602,\"../components/drawing\":624,\"../constants/xmlns_namespaces\":712,\"../lib\":735,\"../lib/events\":725,\"../lib/queue\":750,\"../lib/svg_text_utils\":759,\"../plots/cartesian/axes\":783,\"../plots/cartesian/constants\":789,\"../plots/cartesian/graph_interact\":792,\"../plots/cartesian/select\":802,\"../plots/plots\":846,\"../plots/polar/legacy\":854,\"../registry\":866,\"./edit_types\":766,\"./helpers\":767,\"./manage_arrays\":769,\"./plot_config\":771,\"./plot_schema\":772,\"./subroutines\":774,\"d3\":165,\"fast-isnumeric\":237,\"has-hover\":413}],771:[function(_dereq_,module,exports){/**\n* Copyright 2012-2020, Plotly, Inc.\n* All rights reserved.\n*\n* This source code is licensed under the MIT license found in the\n* LICENSE file in the root directory of this source tree.\n*/'use strict';/**\n * This will be transferred over to gd and overridden by\n * config args to Plotly.plot.\n *\n * The defaults are the appropriate settings for plotly.js,\n * so we get the right experience without any config argument.\n *\n * N.B. the config options are not coerced using Lib.coerce so keys\n * like `valType` and `values` are only set for documentation purposes\n * at the moment.\n */var configAttributes={staticPlot:{valType:'boolean',dflt:false},plotlyServerURL:{valType:'string',dflt:''},editable:{valType:'boolean',dflt:false},edits:{annotationPosition:{valType:'boolean',dflt:false},annotationTail:{valType:'boolean',dflt:false},annotationText:{valType:'boolean',dflt:false},axisTitleText:{valType:'boolean',dflt:false},colorbarPosition:{valType:'boolean',dflt:false},colorbarTitleText:{valType:'boolean',dflt:false},legendPosition:{valType:'boolean',dflt:false},legendText:{valType:'boolean',dflt:false},shapePosition:{valType:'boolean',dflt:false},titleText:{valType:'boolean',dflt:false}},autosizable:{valType:'boolean',dflt:false},responsive:{valType:'boolean',dflt:false},fillFrame:{valType:'boolean',dflt:false},frameMargins:{valType:'number',dflt:0,min:0,max:0.5},scrollZoom:{valType:'flaglist',flags:['cartesian','gl3d','geo','mapbox'],extras:[true,false],dflt:'gl3d+geo+mapbox'},doubleClick:{valType:'enumerated',values:[false,'reset','autosize','reset+autosize'],dflt:'reset+autosize'},doubleClickDelay:{valType:'number',dflt:300,min:0},showAxisDragHandles:{valType:'boolean',dflt:true},showAxisRangeEntryBoxes:{valType:'boolean',dflt:true},showTips:{valType:'boolean',dflt:true},showLink:{valType:'boolean',dflt:false},linkText:{valType:'string',dflt:'Edit chart',noBlank:true},sendData:{valType:'boolean',dflt:true},showSources:{valType:'any',dflt:false},displayModeBar:{valType:'enumerated',values:['hover',true,false],dflt:'hover'},showSendToCloud:{valType:'boolean',dflt:false},showEditInChartStudio:{valType:'boolean',dflt:false},modeBarButtonsToRemove:{valType:'any',dflt:[]},modeBarButtonsToAdd:{valType:'any',dflt:[]},modeBarButtons:{valType:'any',dflt:false},toImageButtonOptions:{valType:'any',dflt:{}},displaylogo:{valType:'boolean',dflt:true},watermark:{valType:'boolean',dflt:false},plotGlPixelRatio:{valType:'number',dflt:2,min:1,max:4},setBackground:{valType:'any',dflt:'transparent'},topojsonURL:{valType:'string',noBlank:true,dflt:'https://cdn.plot.ly/'},mapboxAccessToken:{valType:'string',dflt:null},logging:{valType:'integer',min:0,max:2,dflt:1},notifyOnLogging:{valType:'integer',min:0,max:2,dflt:0},queueLength:{valType:'integer',min:0,dflt:0},globalTransforms:{valType:'any',dflt:[]},locale:{valType:'string',dflt:'en-US'},locales:{valType:'any',dflt:{}}};var dfltConfig={};function crawl(src,target){for(var k in src){var obj=src[k];if(obj.valType){target[k]=obj.dflt;}else{if(!target[k]){target[k]={};}crawl(obj,target[k]);}}}crawl(configAttributes,dfltConfig);module.exports={configAttributes:configAttributes,dfltConfig:dfltConfig};},{}],772:[function(_dereq_,module,exports){/**\n* Copyright 2012-2020, Plotly, Inc.\n* All rights reserved.\n*\n* This source code is licensed under the MIT license found in the\n* LICENSE file in the root directory of this source tree.\n*/'use strict';var Registry=_dereq_('../registry');var Lib=_dereq_('../lib');var baseAttributes=_dereq_('../plots/attributes');var baseLayoutAttributes=_dereq_('../plots/layout_attributes');var frameAttributes=_dereq_('../plots/frame_attributes');var animationAttributes=_dereq_('../plots/animation_attributes');var configAttributes=_dereq_('./plot_config').configAttributes;// polar attributes are not part of the Registry yet\nvar polarAreaAttrs=_dereq_('../plots/polar/legacy/area_attributes');var polarAxisAttrs=_dereq_('../plots/polar/legacy/axis_attributes');var editTypes=_dereq_('./edit_types');var extendFlat=Lib.extendFlat;var extendDeepAll=Lib.extendDeepAll;var isPlainObject=Lib.isPlainObject;var isArrayOrTypedArray=Lib.isArrayOrTypedArray;var nestedProperty=Lib.nestedProperty;var valObjectMeta=Lib.valObjectMeta;var IS_SUBPLOT_OBJ='_isSubplotObj';var IS_LINKED_TO_ARRAY='_isLinkedToArray';var ARRAY_ATTR_REGEXPS='_arrayAttrRegexps';var DEPRECATED='_deprecated';var UNDERSCORE_ATTRS=[IS_SUBPLOT_OBJ,IS_LINKED_TO_ARRAY,ARRAY_ATTR_REGEXPS,DEPRECATED];exports.IS_SUBPLOT_OBJ=IS_SUBPLOT_OBJ;exports.IS_LINKED_TO_ARRAY=IS_LINKED_TO_ARRAY;exports.DEPRECATED=DEPRECATED;exports.UNDERSCORE_ATTRS=UNDERSCORE_ATTRS;/** Outputs the full plotly.js plot schema\n *\n * @return {object}\n * - defs\n * - traces\n * - layout\n * - transforms\n * - frames\n * - animations\n * - config\n */exports.get=function(){var traces={};Registry.allTypes.concat('area').forEach(function(type){traces[type]=getTraceAttributes(type);});var transforms={};Object.keys(Registry.transformsRegistry).forEach(function(type){transforms[type]=getTransformAttributes(type);});return{defs:{valObjects:valObjectMeta,metaKeys:UNDERSCORE_ATTRS.concat(['description','role','editType','impliedEdits']),editType:{traces:editTypes.traces,layout:editTypes.layout},impliedEdits:{}},traces:traces,layout:getLayoutAttributes(),transforms:transforms,frames:getFramesAttributes(),animation:formatAttributes(animationAttributes),config:formatAttributes(configAttributes)};};/**\n * Crawl the attribute tree, recursively calling a callback function\n *\n * @param {object} attrs\n * The node of the attribute tree (e.g. the root) from which recursion originates\n * @param {Function} callback\n * A callback function with the signature:\n * @callback callback\n * @param {object} attr an attribute\n * @param {String} attrName name string\n * @param {object[]} attrs all the attributes\n * @param {Number} level the recursion level, 0 at the root\n * @param {String} fullAttrString full attribute name (ie 'marker.line')\n * @param {Number} [specifiedLevel]\n * The level in the tree, in order to let the callback function detect descend or backtrack,\n * typically unsupplied (implied 0), just used by the self-recursive call.\n * The necessity arises because the tree traversal is not controlled by callback return values.\n * The decision to not use callback return values for controlling tree pruning arose from\n * the goal of keeping the crawler backwards compatible. Observe that one of the pruning conditions\n * precedes the callback call.\n * @param {string} [attrString]\n * the path to the current attribute, as an attribute string (ie 'marker.line')\n * typically unsupplied, but you may supply it if you want to disambiguate which attrs tree you\n * are starting from\n *\n * @return {object} transformOut\n * copy of transformIn that contains attribute defaults\n */exports.crawl=function(attrs,callback,specifiedLevel,attrString){var level=specifiedLevel||0;attrString=attrString||'';Object.keys(attrs).forEach(function(attrName){var attr=attrs[attrName];if(UNDERSCORE_ATTRS.indexOf(attrName)!==-1)return;var fullAttrString=(attrString?attrString+'.':'')+attrName;callback(attr,attrName,attrs,level,fullAttrString);if(exports.isValObject(attr))return;if(isPlainObject(attr)&&attrName!=='impliedEdits'){exports.crawl(attr,callback,level+1,fullAttrString);}});};/** Is object a value object (or a container object)?\n *\n * @param {object} obj\n * @return {boolean}\n * returns true for a valid value object and\n * false for tree nodes in the attribute hierarchy\n */exports.isValObject=function(obj){return obj&&obj.valType!==undefined;};/**\n * Find all data array attributes in a given trace object - including\n * `arrayOk` attributes.\n *\n * @param {object} trace\n * full trace object that contains a reference to `_module.attributes`\n *\n * @return {array} arrayAttributes\n * list of array attributes for the given trace\n */exports.findArrayAttributes=function(trace){var arrayAttributes=[];var stack=[];var isArrayStack=[];var baseContainer,baseAttrName;function callback(attr,attrName,attrs,level){stack=stack.slice(0,level).concat([attrName]);isArrayStack=isArrayStack.slice(0,level).concat([attr&&attr._isLinkedToArray]);var splittableAttr=attr&&(attr.valType==='data_array'||attr.arrayOk===true)&&!(stack[level-1]==='colorbar'&&(attrName==='ticktext'||attrName==='tickvals'));// Manually exclude 'colorbar.tickvals' and 'colorbar.ticktext' for now\n// which are declared as `valType: 'data_array'` but scale independently of\n// the coordinate arrays.\n//\n// Down the road, we might want to add a schema field (e.g `uncorrelatedArray: true`)\n// to distinguish attributes of the likes.\nif(!splittableAttr)return;crawlIntoTrace(baseContainer,0,'');}function crawlIntoTrace(container,i,astrPartial){var item=container[stack[i]];var newAstrPartial=astrPartial+stack[i];if(i===stack.length-1){if(isArrayOrTypedArray(item)){arrayAttributes.push(baseAttrName+newAstrPartial);}}else{if(isArrayStack[i]){if(Array.isArray(item)){for(var j=0;j=transforms.length){return false;}moduleAttrs=(Registry.transformsRegistry[transforms[tNum].type]||{}).attributes;valObject=moduleAttrs&&moduleAttrs[parts[2]];i=3;// start recursing only inside the transform\n}else if(trace.type==='area'){valObject=polarAreaAttrs[head];}else{// first look in the module for this trace\n// components have already merged their trace attributes in here\nvar _module=trace._module;if(!_module)_module=(Registry.modules[trace.type||baseAttributes.type.dflt]||{})._module;if(!_module)return false;moduleAttrs=_module.attributes;valObject=moduleAttrs&&moduleAttrs[head];// then look in the subplot attributes\nif(!valObject){var subplotModule=_module.basePlotModule;if(subplotModule&&subplotModule.attributes){valObject=subplotModule.attributes[head];}}// finally look in the global attributes\nif(!valObject)valObject=baseAttributes[head];}return recurseIntoValObject(valObject,parts,i);};/*\n * Find the valObject for one layout attribute\n *\n * @param {array} parts\n * an array of parts, like ['annotations', 1, 'x']\n * typically from nestedProperty(...).parts\n *\n * @return {object|false}\n * the valObject for this attribute, or the last found parent\n * in some cases the innermost valObject will not exist, for example\n * `valType: 'any'` attributes where we might set a part of the attribute.\n * In that case, stop at the deepest valObject we *do* find.\n */exports.getLayoutValObject=function(fullLayout,parts){var valObject=layoutHeadAttr(fullLayout,parts[0]);return recurseIntoValObject(valObject,parts,1);};function layoutHeadAttr(fullLayout,head){var i,key,_module,attributes;// look for attributes of the subplot types used on the plot\nvar basePlotModules=fullLayout._basePlotModules;if(basePlotModules){var out;for(i=0;i=items.length)return false;if(valObject.dimensions===2){i++;if(parts.length===i)return valObject;var index2=parts[i];if(!isIndex(index2))return false;valObject=items[index][index2];}else valObject=items[index];}else{valObject=items;}}}return valObject;}// note: this is different from Lib.isIndex, this one doesn't accept numeric\n// strings, only actual numbers.\nfunction isIndex(val){return val===Math.round(val)&&val>=0;}function getTraceAttributes(type){var _module,basePlotModule;if(type==='area'){_module={attributes:polarAreaAttrs};basePlotModule={};}else{_module=Registry.modules[type]._module,basePlotModule=_module.basePlotModule;}var attributes={};// make 'type' the first attribute in the object\nattributes.type=null;var copyBaseAttributes=extendDeepAll({},baseAttributes);var copyModuleAttributes=extendDeepAll({},_module.attributes);// prune global-level trace attributes that are already defined in a trace\nexports.crawl(copyModuleAttributes,function(attr,attrName,attrs,level,fullAttrString){nestedProperty(copyBaseAttributes,fullAttrString).set(undefined);// Prune undefined attributes\nif(attr===undefined)nestedProperty(copyModuleAttributes,fullAttrString).set(undefined);});// base attributes (same for all trace types)\nextendDeepAll(attributes,copyBaseAttributes);// prune-out base attributes based on trace module categories\nif(Registry.traceIs(type,'noOpacity')){delete attributes.opacity;}if(!Registry.traceIs(type,'showLegend')){delete attributes.showlegend;delete attributes.legendgroup;}if(Registry.traceIs(type,'noHover')){delete attributes.hoverinfo;delete attributes.hoverlabel;}if(!_module.selectPoints){delete attributes.selectedpoints;}// module attributes\nextendDeepAll(attributes,copyModuleAttributes);// subplot attributes\nif(basePlotModule.attributes){extendDeepAll(attributes,basePlotModule.attributes);}// 'type' gets overwritten by baseAttributes; reset it here\nattributes.type=type;var out={meta:_module.meta||{},categories:_module.categories||{},animatable:Boolean(_module.animatable),type:type,attributes:formatAttributes(attributes)};// trace-specific layout attributes\nif(_module.layoutAttributes){var layoutAttributes={};extendDeepAll(layoutAttributes,_module.layoutAttributes);out.layoutAttributes=formatAttributes(layoutAttributes);}// drop anim:true in non-animatable modules\nif(!_module.animatable){exports.crawl(out,function(attr){if(exports.isValObject(attr)&&'anim'in attr){delete attr.anim;}});}return out;}function getLayoutAttributes(){var layoutAttributes={};var key,_module;// global layout attributes\nextendDeepAll(layoutAttributes,baseLayoutAttributes);// add base plot module layout attributes\nfor(key in Registry.subplotsRegistry){_module=Registry.subplotsRegistry[key];if(!_module.layoutAttributes)continue;if(Array.isArray(_module.attr)){for(var i=0;i<_module.attr.length;i++){handleBasePlotModule(layoutAttributes,_module,_module.attr[i]);}}else{var astr=_module.attr==='subplot'?_module.name:_module.attr;handleBasePlotModule(layoutAttributes,_module,astr);}}// polar layout attributes\nlayoutAttributes=assignPolarLayoutAttrs(layoutAttributes);// add registered components layout attributes\nfor(key in Registry.componentsRegistry){_module=Registry.componentsRegistry[key];var schema=_module.schema;if(schema&&(schema.subplots||schema.layout)){/*\n * Components with defined schema have already been merged in at register time\n * but a few components define attributes that apply only to xaxis\n * not yaxis (rangeselector, rangeslider) - delete from y schema.\n * Note that the input attributes for xaxis/yaxis are the same object\n * so it's not possible to only add them to xaxis from the start.\n * If we ever have such asymmetry the other way, or anywhere else,\n * we will need to extend both this code and mergeComponentAttrsToSubplot\n * (which will not find yaxis only for example)\n */var subplots=schema.subplots;if(subplots&&subplots.xaxis&&!subplots.yaxis){for(var xkey in subplots.xaxis){delete layoutAttributes.yaxis[xkey];}}}else if(_module.name==='colorscale'){extendDeepAll(layoutAttributes,_module.layoutAttributes);}else if(_module.layoutAttributes){// older style without schema need to be explicitly merged in now\ninsertAttrs(layoutAttributes,_module.layoutAttributes,_module.name);}}return{layoutAttributes:formatAttributes(layoutAttributes)};}function getTransformAttributes(type){var _module=Registry.transformsRegistry[type];var attributes=extendDeepAll({},_module.attributes);// add registered components transform attributes\nObject.keys(Registry.componentsRegistry).forEach(function(k){var _module=Registry.componentsRegistry[k];if(_module.schema&&_module.schema.transforms&&_module.schema.transforms[type]){Object.keys(_module.schema.transforms[type]).forEach(function(v){insertAttrs(attributes,_module.schema.transforms[type][v],v);});}});return{attributes:formatAttributes(attributes)};}function getFramesAttributes(){var attrs={frames:extendDeepAll({},frameAttributes)};formatAttributes(attrs);return attrs.frames;}function formatAttributes(attrs){mergeValTypeAndRole(attrs);formatArrayContainers(attrs);stringify(attrs);return attrs;}function mergeValTypeAndRole(attrs){function makeSrcAttr(attrName){return{valType:'string',editType:'none'};}function callback(attr,attrName,attrs){if(exports.isValObject(attr)){if(attr.valType==='data_array'){// all 'data_array' attrs have role 'data'\nattr.role='data';// all 'data_array' attrs have a corresponding 'src' attr\nattrs[attrName+'src']=makeSrcAttr(attrName);}else if(attr.arrayOk===true){// all 'arrayOk' attrs have a corresponding 'src' attr\nattrs[attrName+'src']=makeSrcAttr(attrName);}}else if(isPlainObject(attr)){// all attrs container objects get role 'object'\nattr.role='object';}}exports.crawl(attrs,callback);}function formatArrayContainers(attrs){function callback(attr,attrName,attrs){if(!attr)return;var itemName=attr[IS_LINKED_TO_ARRAY];if(!itemName)return;delete attr[IS_LINKED_TO_ARRAY];attrs[attrName]={items:{}};attrs[attrName].items[itemName]=attr;attrs[attrName].role='object';}exports.crawl(attrs,callback);}// this can take around 10ms and should only be run from PlotSchema.get(),\n// to ensure JSON.stringify(PlotSchema.get()) gives the intended result.\nfunction stringify(attrs){function walk(attr){for(var k in attr){if(isPlainObject(attr[k])){walk(attr[k]);}else if(Array.isArray(attr[k])){for(var i=0;i {}\nif(attr[k]instanceof RegExp){attr[k]=attr[k].toString();}}}}walk(attrs);}function assignPolarLayoutAttrs(layoutAttributes){extendFlat(layoutAttributes,{radialaxis:polarAxisAttrs.radialaxis,angularaxis:polarAxisAttrs.angularaxis});extendFlat(layoutAttributes,polarAxisAttrs.layout);return layoutAttributes;}function handleBasePlotModule(layoutAttributes,_module,astr){var np=nestedProperty(layoutAttributes,astr);var attrs=extendDeepAll({},_module.layoutAttributes);attrs[IS_SUBPLOT_OBJ]=true;np.set(attrs);}function insertAttrs(baseAttrs,newAttrs,astr){var np=nestedProperty(baseAttrs,astr);np.set(extendDeepAll(np.get()||{},newAttrs));}},{\"../lib\":735,\"../plots/animation_attributes\":778,\"../plots/attributes\":780,\"../plots/frame_attributes\":812,\"../plots/layout_attributes\":837,\"../plots/polar/legacy/area_attributes\":852,\"../plots/polar/legacy/axis_attributes\":853,\"../registry\":866,\"./edit_types\":766,\"./plot_config\":771}],773:[function(_dereq_,module,exports){/**\n* Copyright 2012-2020, Plotly, Inc.\n* All rights reserved.\n*\n* This source code is licensed under the MIT license found in the\n* LICENSE file in the root directory of this source tree.\n*/'use strict';var Lib=_dereq_('../lib');var plotAttributes=_dereq_('../plots/attributes');var TEMPLATEITEMNAME='templateitemname';var templateAttrs={name:{valType:'string',editType:'none'}};templateAttrs[TEMPLATEITEMNAME]={valType:'string',editType:'calc'};/**\n * templatedArray: decorate an attributes object with templating (and array)\n * properties.\n *\n * @param {string} name: the singular form of the array name. Sets\n * `_isLinkedToArray` to this, so the schema knows to treat this as an array.\n * @param {object} attrs: the item attributes. Since all callers are expected\n * to be constructing this object on the spot, we mutate it here for\n * performance, rather than extending a new object with it.\n *\n * @returns {object}: the decorated `attrs` object\n */exports.templatedArray=function(name,attrs){attrs._isLinkedToArray=name;attrs.name=templateAttrs.name;attrs[TEMPLATEITEMNAME]=templateAttrs[TEMPLATEITEMNAME];return attrs;};/**\n * traceTemplater: logic for matching traces to trace templates\n *\n * @param {object} dataTemplate: collection of {traceType: [{template}, ...]}\n * ie each type the template applies to contains a list of template objects,\n * to be provided cyclically to data traces of that type.\n *\n * @returns {object}: {newTrace}, a function:\n * newTrace(traceIn): that takes the input traceIn, coerces its type, then\n * uses that type to find the next template to apply. returns the output\n * traceOut with template attached, ready to continue supplyDefaults.\n */exports.traceTemplater=function(dataTemplate){var traceCounts={};var traceType,typeTemplates;for(traceType in dataTemplate){typeTemplates=dataTemplate[traceType];if(Array.isArray(typeTemplates)&&typeTemplates.length){traceCounts[traceType]=0;}}function newTrace(traceIn){traceType=Lib.coerce(traceIn,{},plotAttributes,'type');var traceOut={type:traceType,_template:null};if(traceType in traceCounts){typeTemplates=dataTemplate[traceType];// cycle through traces in the template set for this type\nvar typei=traceCounts[traceType]%typeTemplates.length;traceCounts[traceType]++;traceOut._template=typeTemplates[typei];}else{// TODO: anything we should do for types missing from the template?\n// try to apply some other type? Or just bail as we do here?\n// Actually I think yes, we should apply other types; would be nice\n// if all scatter* could inherit from each other, and if histogram\n// could inherit from bar, etc... but how to specify this? And do we\n// compose them, or if a type is present require it to be complete?\n// Actually this could apply to layout too - 3D annotations\n// inheriting from 2D, axes of different types inheriting from each\n// other...\n}return traceOut;}return{newTrace:newTrace// TODO: function to figure out what's left & what didn't work\n};};/**\n * newContainer: Create a new sub-container inside `container` and propagate any\n * applicable template to it. If there's no template, still propagates\n * `undefined` so relinkPrivate will not retain an old template!\n *\n * @param {object} container: the outer container, should already have _template\n * if there *is* a template for this plot\n * @param {string} name: the key of the new container to make\n * @param {string} baseName: if applicable, a base attribute to take the\n * template from, ie for xaxis3 the base would be xaxis\n *\n * @returns {object}: an object for inclusion _full*, empty except for the\n * appropriate template piece\n */exports.newContainer=function(container,name,baseName){var template=container._template;var part=template&&(template[name]||baseName&&template[baseName]);if(!Lib.isPlainObject(part))part=null;var out=container[name]={_template:part};return out;};/**\n * arrayTemplater: special logic for templating both defaults and specific items\n * in a container array (annotations etc)\n *\n * @param {object} container: the outer container, should already have _template\n * if there *is* a template for this plot\n * @param {string} name: the name of the array to template (ie 'annotations')\n * will be used to find default ('annotationdefaults' object) and specific\n * ('annotations' array) template specs.\n * @param {string} inclusionAttr: the attribute determining this item's\n * inclusion in the output, usually 'visible' or 'enabled'\n *\n * @returns {object}: {newItem, defaultItems}, both functions:\n * newItem(itemIn): create an output item, bare except for the correct\n * template and name(s), as the base for supplyDefaults\n * defaultItems(): to be called after all newItem calls, return any\n * specific template items that have not already beeen included,\n * also as bare output items ready for supplyDefaults.\n */exports.arrayTemplater=function(container,name,inclusionAttr){var template=container._template;var defaultsTemplate=template&&template[arrayDefaultKey(name)];var templateItems=template&&template[name];if(!Array.isArray(templateItems)||!templateItems.length){templateItems=[];}var usedNames={};function newItem(itemIn){// include name and templateitemname in the output object for ALL\n// container array items. Note: you could potentially use different\n// name and templateitemname, if you're using one template to make\n// another template. templateitemname would be the name in the original\n// template, and name is the new \"subclassed\" item name.\nvar out={name:itemIn.name,_input:itemIn};var templateItemName=out[TEMPLATEITEMNAME]=itemIn[TEMPLATEITEMNAME];// no itemname: use the default template\nif(!validItemName(templateItemName)){out._template=defaultsTemplate;return out;}// look for an item matching this itemname\n// note these do not inherit from the default template, only the item.\nfor(var i=0;i=lengthIn&&(itemOut._input||{})._templateitemname;if(templateItemName)index=lengthIn;var itemStr=containerStr+'['+index+']';var update;function resetUpdate(){update={};if(templateItemName){update[itemStr]={};update[itemStr][TEMPLATEITEMNAME]=templateItemName;}}resetUpdate();function modifyBase(attr,value){update[attr]=value;}function modifyItem(attr,value){if(templateItemName){// we're making a new object: edit that object\nLib.nestedProperty(update[itemStr],attr).set(value);}else{// we're editing an existing object: include *just* the edit\nupdate[itemStr+'.'+attr]=value;}}function getUpdateObj(){var updateOut=update;resetUpdate();return updateOut;}function applyUpdate(attr,value){if(attr)modifyItem(attr,value);var updateToApply=getUpdateObj();for(var key in updateToApply){Lib.nestedProperty(parentIn,key).set(updateToApply[key]);}}return{modifyBase:modifyBase,modifyItem:modifyItem,getUpdateObj:getUpdateObj,applyUpdate:applyUpdate};};},{\"../lib\":735,\"../plots/attributes\":780}],774:[function(_dereq_,module,exports){/**\n* Copyright 2012-2020, Plotly, Inc.\n* All rights reserved.\n*\n* This source code is licensed under the MIT license found in the\n* LICENSE file in the root directory of this source tree.\n*/'use strict';var d3=_dereq_('d3');var Registry=_dereq_('../registry');var Plots=_dereq_('../plots/plots');var Lib=_dereq_('../lib');var clearGlCanvases=_dereq_('../lib/clear_gl_canvases');var Color=_dereq_('../components/color');var Drawing=_dereq_('../components/drawing');var Titles=_dereq_('../components/titles');var ModeBar=_dereq_('../components/modebar');var Axes=_dereq_('../plots/cartesian/axes');var alignmentConstants=_dereq_('../constants/alignment');var axisConstraints=_dereq_('../plots/cartesian/constraints');var enforceAxisConstraints=axisConstraints.enforce;var cleanAxisConstraints=axisConstraints.clean;var doAutoRange=_dereq_('../plots/cartesian/autorange').doAutoRange;var SVG_TEXT_ANCHOR_START='start';var SVG_TEXT_ANCHOR_MIDDLE='middle';var SVG_TEXT_ANCHOR_END='end';exports.layoutStyles=function(gd){return Lib.syncOrAsync([Plots.doAutoMargin,lsInner],gd);};function overlappingDomain(xDomain,yDomain,domains){for(var i=0;i=xDomain[1]||existingX[1]<=xDomain[0]){continue;}if(existingY[0]yDomain[0]){return true;}}return false;}function lsInner(gd){var fullLayout=gd._fullLayout;var gs=fullLayout._size;var pad=gs.p;var axList=Axes.list(gd,'',true);var i,subplot,plotinfo,ax,xa,ya;fullLayout._paperdiv.style({width:gd._context.responsive&&fullLayout.autosize&&!gd._context._hasZeroWidth&&!gd.layout.width?'100%':fullLayout.width+'px',height:gd._context.responsive&&fullLayout.autosize&&!gd._context._hasZeroHeight&&!gd.layout.height?'100%':fullLayout.height+'px'}).selectAll('.main-svg').call(Drawing.setSize,fullLayout.width,fullLayout.height);gd._context.setBackground(gd,fullLayout.paper_bgcolor);exports.drawMainTitle(gd);ModeBar.manage(gd);// _has('cartesian') means SVG specifically, not GL2D - but GL2D\n// can still get here because it makes some of the SVG structure\n// for shared features like selections.\nif(!fullLayout._has('cartesian')){return Plots.previousPromises(gd);}function getLinePosition(ax,counterAx,side){var lwHalf=ax._lw/2;if(ax._id.charAt(0)==='x'){if(!counterAx)return gs.t+gs.h*(1-(ax.position||0))+lwHalf%1;else if(side==='top')return counterAx._offset-pad-lwHalf;return counterAx._offset+counterAx._length+pad+lwHalf;}if(!counterAx)return gs.l+gs.w*(ax.position||0)+lwHalf%1;else if(side==='right')return counterAx._offset+counterAx._length+pad+lwHalf;return counterAx._offset-pad-lwHalf;}// some preparation of axis position info\nfor(i=0;idataCount){errorList.push({code:'unused',traceType:traceType,templateCount:templateCount,dataCount:dataCount});}else if(dataCount>templateCount){errorList.push({code:'reused',traceType:traceType,templateCount:templateCount,dataCount:dataCount});}}}// _template: false is when someone tried to modify an array item\n// but there was no template with matching name\nfunction crawlForMissingTemplates(obj,path){for(var key in obj){if(key.charAt(0)==='_')continue;var val=obj[key];var nextPath=getNextPath(obj,key,path);if(isPlainObject(val)){if(Array.isArray(obj)&&val._template===false&&val.templateitemname){errorList.push({code:'missing',path:nextPath,templateitemname:val.templateitemname});}crawlForMissingTemplates(val,nextPath);}else if(Array.isArray(val)&&hasPlainObject(val)){crawlForMissingTemplates(val,nextPath);}}}crawlForMissingTemplates({data:fullData,layout:fullLayout},'');if(errorList.length)return errorList.map(format);};function hasPlainObject(arr){for(var i=0;i\n * or an id to an existing graph

\n * @param {object} opts (see above)\n * @return {promise}\n */function toImage(gd,opts){opts=opts||{};var data;var layout;var config;var fullLayout;if(Lib.isPlainObject(gd)){data=gd.data||[];layout=gd.layout||{};config=gd.config||{};fullLayout={};}else{gd=Lib.getGraphDiv(gd);data=Lib.extendDeep([],gd.data);layout=Lib.extendDeep({},gd.layout);config=gd._context;fullLayout=gd._fullLayout||{};}function isImpliedOrValid(attr){return!(attr in opts)||Lib.validate(opts[attr],attrs[attr]);}if(!isImpliedOrValid('width')&&opts.width!==null||!isImpliedOrValid('height')&&opts.height!==null){throw new Error('Height and width should be pixel values.');}if(!isImpliedOrValid('format')){throw new Error('Image format is not jpeg, png, svg or webp.');}var fullOpts={};function coerce(attr,dflt){return Lib.coerce(opts,fullOpts,attrs,attr,dflt);}var format=coerce('format');var width=coerce('width');var height=coerce('height');var scale=coerce('scale');var setBackground=coerce('setBackground');var imageDataOnly=coerce('imageDataOnly');// put the cloned div somewhere off screen before attaching to DOM\nvar clonedGd=document.createElement('div');clonedGd.style.position='absolute';clonedGd.style.left='-5000px';document.body.appendChild(clonedGd);// extend layout with image options\nvar layoutImage=Lib.extendFlat({},layout);if(width){layoutImage.width=width;}else if(opts.width===null&&isNumeric(fullLayout.width)){layoutImage.width=fullLayout.width;}if(height){layoutImage.height=height;}else if(opts.height===null&&isNumeric(fullLayout.height)){layoutImage.height=fullLayout.height;}// extend config for static plot\nvar configImage=Lib.extendFlat({},config,{_exportedPlot:true,staticPlot:true,setBackground:setBackground});var redrawFunc=helpers.getRedrawFunc(clonedGd);function wait(){return new Promise(function(resolve){setTimeout(resolve,helpers.getDelay(clonedGd._fullLayout));});}function convert(){return new Promise(function(resolve,reject){var svg=toSVG(clonedGd,format,scale);var width=clonedGd._fullLayout.width;var height=clonedGd._fullLayout.height;function cleanup(){plotApi.purge(clonedGd);document.body.removeChild(clonedGd);}if(format==='full-json'){var json=plots.graphJson(clonedGd,false,'keepdata','object',true,true);json.version=version;json=JSON.stringify(json);cleanup();if(imageDataOnly){return resolve(json);}else{return resolve(helpers.encodeJSON(json));}}cleanup();if(format==='svg'){if(imageDataOnly){return resolve(svg);}else{return resolve(helpers.encodeSVG(svg));}}var canvas=document.createElement('canvas');canvas.id=Lib.randstr();svgToImg({format:format,width:width,height:height,scale:scale,canvas:canvas,svg:svg,// ask svgToImg to return a Promise\n// rather than EventEmitter\n// leave EventEmitter for backward\n// compatibility\npromise:true}).then(resolve).catch(reject);});}function urlToImageData(url){if(imageDataOnly){return url.replace(helpers.IMAGE_URL_PREFIX,'');}else{return url;}}return new Promise(function(resolve,reject){plotApi.plot(clonedGd,data,layoutImage,configImage).then(redrawFunc).then(wait).then(convert).then(function(url){resolve(urlToImageData(url));}).catch(function(err){reject(err);});});}module.exports=toImage;},{\"../lib\":735,\"../plots/plots\":846,\"../snapshot/helpers\":870,\"../snapshot/svgtoimg\":872,\"../snapshot/tosvg\":874,\"../version\":1322,\"./plot_api\":770,\"fast-isnumeric\":237}],777:[function(_dereq_,module,exports){/**\n* Copyright 2012-2020, Plotly, Inc.\n* All rights reserved.\n*\n* This source code is licensed under the MIT license found in the\n* LICENSE file in the root directory of this source tree.\n*/'use strict';var Lib=_dereq_('../lib');var Plots=_dereq_('../plots/plots');var PlotSchema=_dereq_('./plot_schema');var dfltConfig=_dereq_('./plot_config').dfltConfig;var isPlainObject=Lib.isPlainObject;var isArray=Array.isArray;var isArrayOrTypedArray=Lib.isArrayOrTypedArray;/**\n * Validate a data array and layout object.\n *\n * @param {array} data\n * @param {object} layout\n *\n * @return {array} array of error objects each containing:\n * - {string} code\n * error code ('object', 'array', 'schema', 'unused', 'invisible' or 'value')\n * - {string} container\n * container where the error occurs ('data' or 'layout')\n * - {number} trace\n * trace index of the 'data' container where the error occurs\n * - {array} path\n * nested path to the key that causes the error\n * - {string} astr\n * attribute string variant of 'path' compatible with Plotly.restyle and\n * Plotly.relayout.\n * - {string} msg\n * error message (shown in console in logger config argument is enable)\n */module.exports=function validate(data,layout){var schema=PlotSchema.get();var errorList=[];var gd={_context:Lib.extendFlat({},dfltConfig)};var dataIn,layoutIn;if(isArray(data)){gd.data=Lib.extendDeep([],data);dataIn=data;}else{gd.data=[];dataIn=[];errorList.push(format('array','data'));}if(isPlainObject(layout)){gd.layout=Lib.extendDeep({},layout);layoutIn=layout;}else{gd.layout={};layoutIn={};if(arguments.length>1){errorList.push(format('object','layout'));}}// N.B. dataIn and layoutIn are in general not the same as\n// gd.data and gd.layout after supplyDefaults as some attributes\n// in gd.data and gd.layout (still) get mutated during this step.\nPlots.supplyDefaults(gd);var dataOut=gd._fullData;var len=dataIn.length;for(var i=0;ivalOut.length){list.push(format('unused',base,p.concat(valOut.length)));}var len=valOut.length;var arrayItems=Array.isArray(items);if(arrayItems)len=Math.min(len,items.length);var m,n,item,valInPart,valOutPart;if(nestedSchema.dimensions===2){for(n=0;nvalOut[n].length){list.push(format('unused',base,p.concat(n,valOut[n].length)));}var len2=valOut[n].length;for(m=0;m<(arrayItems?Math.min(len2,items[n].length):len2);m++){item=arrayItems?items[n][m]:items;valInPart=valIn[n][m];valOutPart=valOut[n][m];if(!Lib.validate(valInPart,item)){list.push(format('value',base,p.concat(n,m),valInPart));}else if(valOutPart!==valInPart&&valOutPart!==+valInPart){list.push(format('dynamic',base,p.concat(n,m),valInPart,valOutPart));}}}else{list.push(format('array',base,p.concat(n),valIn[n]));}}}else{for(n=0;n0){dp=axLen-getPad(minpt)-getPad(maxpt);if(dp>minSpan){if(dv/dp>mbest){minbest=minpt;maxbest=maxpt;mbest=dv/dp;}}else if(dv/axLen>mbest){// in case of padding longer than the axis\n// at least include the unpadded data values.\nminbest={val:minpt.val,pad:0};maxbest={val:maxpt.val,pad:0};mbest=dv/axLen;}}}}function getMaxPad(prev,pt){return Math.max(prev,getPad(pt));}if(minmin===maxmax){var lower=minmin-1;var upper=minmin+1;if(toZero){if(minmin===0){// The only value we have on this axis is 0, and we want to\n// autorange so zero is one end.\n// In principle this could be [0, 1] or [-1, 0] but usually\n// 'tozero' pins 0 to the low end, so follow that.\nnewRange=[0,1];}else{var maxPad=(minmin>0?maxArray:minArray).reduce(getMaxPad,0);// we're pushing a single value away from the edge due to its\n// padding, with the other end clamped at zero\n// 0.5 means don't push it farther than the center.\nvar rangeEnd=minmin/(1-Math.min(0.5,maxPad/axLen));newRange=minmin>0?[0,rangeEnd]:[rangeEnd,0];}}else if(nonNegative){newRange=[Math.max(0,lower),Math.max(1,upper)];}else{newRange=[lower,upper];}}else{if(toZero){if(minbest.val>=0){minbest={val:0,pad:0};}if(maxbest.val<=0){maxbest={val:0,pad:0};}}else if(nonNegative){if(minbest.val-mbest*getPad(minbest)<0){minbest={val:0,pad:0};}if(maxbest.val<=0){maxbest={val:1,pad:0};}}// in case it changed again...\nmbest=(maxbest.val-minbest.val-calcBreaksLength(minpt.val,maxpt.val))/(axLen-getPad(minbest)-getPad(maxbest));newRange=[minbest.val-mbest*getPad(minbest),maxbest.val+mbest*getPad(maxbest)];}// maintain reversal\nif(axReverse)newRange.reverse();return Lib.simpleMap(newRange,ax.l2r||Number);}/*\n * calculate the pixel padding for ax._min and ax._max entries with\n * optional extrapad as 5% of the total axis length\n */function makePadFn(ax){// 5% padding for points that specify extrapad: true\nvar extrappad=ax._length/20;// domain-constrained axes: base extrappad on the unconstrained\n// domain so it's consistent as the domain changes\nif(ax.constrain==='domain'&&ax._inputDomain){extrappad*=(ax._inputDomain[1]-ax._inputDomain[0])/(ax.domain[1]-ax.domain[0]);}return function getPad(pt){return pt.pad+(pt.extrapad?extrappad:0);};}function concatExtremes(gd,ax){var axId=ax._id;var fullData=gd._fullData;var fullLayout=gd._fullLayout;var minArray=[];var maxArray=[];var i,j,d;function _concat(cont,indices){for(i=0;i0?opts.ppadplus:opts.ppadminus)||opts.ppad||0);var ppadminus=makePadAccessor((ax._m>0?opts.ppadminus:opts.ppadplus)||opts.ppad||0);var vpadplus=makePadAccessor(opts.vpadplus||opts.vpad);var vpadminus=makePadAccessor(opts.vpadminus||opts.vpad);if(!hasArrayOption){// with no arrays other than `data` we don't need to consider\n// every point, only the extreme data points\nvmin=Infinity;vmax=-Infinity;if(isLog){for(i=0;i0)vmin=v;if(v>vmax&&v-FP_SAFE)vmin=v;if(v>vmax&&v=iMax;i--)addItem(i);return{min:minArray,max:maxArray,opts:opts};}function collapseMinArray(array,newVal,newPad,opts){collapseArray(array,newVal,newPad,opts,lessOrEqual);}function collapseMaxArray(array,newVal,newPad,opts){collapseArray(array,newVal,newPad,opts,greaterOrEqual);}/**\n * collapseArray\n *\n * Takes items from 'array' and compares them to 'newVal', 'newPad'.\n *\n * @param {array} array:\n * current set of min or max extremes\n * @param {number} newVal:\n * new value to compare against\n * @param {number} newPad:\n * pad value associated with 'newVal'\n * @param {object} opts:\n * - tozero {boolean}\n * - extrapad {number}\n * @param {function} atLeastAsExtreme:\n * comparison function, use\n * - lessOrEqual for min 'array' and\n * - greaterOrEqual for max 'array'\n *\n * In practice, 'array' is either\n * - 'extremes[ax._id].min' or\n * - 'extremes[ax._id].max\n * found in traces and layout items that affect autorange.\n *\n * Since we don't yet know the relationship between pixels and values\n * (that's what we're trying to figure out!) AND we don't yet know how\n * many pixels `extrapad` represents (it's going to be 5% of the length,\n * but we don't want to have to redo calc just because length changed)\n * two point must satisfy three criteria simultaneously for one to supersede the other:\n * - at least as extreme a `val`\n * - at least as big a `pad`\n * - an unpadded point cannot supersede a padded point, but any other combination can\n *\n * Then:\n * - If the item supersedes the new point, set includeThis false\n * - If the new pt supersedes the item, delete it from 'array'\n */function collapseArray(array,newVal,newPad,opts,atLeastAsExtreme){var tozero=opts.tozero;var extrapad=opts.extrapad;var includeThis=true;for(var j=0;j=newPad&&(v.extrapad||!extrapad)){includeThis=false;break;}else if(atLeastAsExtreme(newVal,v.val)&&v.pad<=newPad&&(extrapad||!v.extrapad)){array.splice(j,1);j--;}}if(includeThis){var clipAtZero=tozero&&newVal===0;array.push({val:newVal,pad:clipAtZero?0:newPad,extrapad:clipAtZero?false:extrapad});}}// In order to stop overflow errors, don't consider points\n// too close to the limits of js floating point\nfunction goodNumber(v){return isNumeric(v)&&Math.abs(v)=v1;}},{\"../../constants/numerical\":711,\"../../lib\":735,\"../../registry\":866,\"fast-isnumeric\":237}],783:[function(_dereq_,module,exports){/**\n* Copyright 2012-2020, Plotly, Inc.\n* All rights reserved.\n*\n* This source code is licensed under the MIT license found in the\n* LICENSE file in the root directory of this source tree.\n*/'use strict';var d3=_dereq_('d3');var isNumeric=_dereq_('fast-isnumeric');var Plots=_dereq_('../../plots/plots');var Registry=_dereq_('../../registry');var Lib=_dereq_('../../lib');var svgTextUtils=_dereq_('../../lib/svg_text_utils');var Titles=_dereq_('../../components/titles');var Color=_dereq_('../../components/color');var Drawing=_dereq_('../../components/drawing');var axAttrs=_dereq_('./layout_attributes');var cleanTicks=_dereq_('./clean_ticks');var constants=_dereq_('../../constants/numerical');var ONEAVGYEAR=constants.ONEAVGYEAR;var ONEAVGMONTH=constants.ONEAVGMONTH;var ONEDAY=constants.ONEDAY;var ONEHOUR=constants.ONEHOUR;var ONEMIN=constants.ONEMIN;var ONESEC=constants.ONESEC;var MINUS_SIGN=constants.MINUS_SIGN;var BADNUM=constants.BADNUM;var alignmentConstants=_dereq_('../../constants/alignment');var MID_SHIFT=alignmentConstants.MID_SHIFT;var CAP_SHIFT=alignmentConstants.CAP_SHIFT;var LINE_SPACING=alignmentConstants.LINE_SPACING;var OPPOSITE_SIDE=alignmentConstants.OPPOSITE_SIDE;var axes=module.exports={};axes.setConvert=_dereq_('./set_convert');var autoType=_dereq_('./axis_autotype');var axisIds=_dereq_('./axis_ids');axes.id2name=axisIds.id2name;axes.name2id=axisIds.name2id;axes.cleanId=axisIds.cleanId;axes.list=axisIds.list;axes.listIds=axisIds.listIds;axes.getFromId=axisIds.getFromId;axes.getFromTrace=axisIds.getFromTrace;var autorange=_dereq_('./autorange');axes.getAutoRange=autorange.getAutoRange;axes.findExtremes=autorange.findExtremes;/*\n * find the list of possible axes to reference with an xref or yref attribute\n * and coerce it to that list\n *\n * attr: the attribute we're generating a reference for. Should end in 'x' or 'y'\n * but can be prefixed, like 'ax' for annotation's arrow x\n * dflt: the default to coerce to, or blank to use the first axis (falling back on\n * extraOption if there is no axis)\n * extraOption: aside from existing axes with this letter, what non-axis value is allowed?\n * Only required if it's different from `dflt`\n */axes.coerceRef=function(containerIn,containerOut,gd,attr,dflt,extraOption){var axLetter=attr.charAt(attr.length-1);var axlist=gd._fullLayout._subplots[axLetter+'axis'];var refAttr=attr+'ref';var attrDef={};if(!dflt)dflt=axlist[0]||extraOption;if(!extraOption)extraOption=dflt;// data-ref annotations are not supported in gl2d yet\nattrDef[refAttr]={valType:'enumerated',values:axlist.concat(extraOption?[extraOption]:[]),dflt:dflt};// xref, yref\nreturn Lib.coerce(containerIn,containerOut,attrDef,refAttr);};/*\n * coerce position attributes (range-type) that can be either on axes or absolute\n * (paper or pixel) referenced. The biggest complication here is that we don't know\n * before looking at the axis whether the value must be a number or not (it may be\n * a date string), so we can't use the regular valType='number' machinery\n *\n * axRef (string): the axis this position is referenced to, or:\n * paper: fraction of the plot area\n * pixel: pixels relative to some starting position\n * attr (string): the attribute in containerOut we are coercing\n * dflt (number): the default position, as a fraction or pixels. If the attribute\n * is to be axis-referenced, this will be converted to an axis data value\n *\n * Also cleans the values, since the attribute definition itself has to say\n * valType: 'any' to handle date axes. This allows us to accept:\n * - for category axes: category names, and convert them here into serial numbers.\n * Note that this will NOT work for axis range endpoints, because we don't know\n * the category list yet (it's set by ax.makeCalcdata during calc)\n * but it works for component (note, shape, images) positions.\n * - for date axes: JS Dates or milliseconds, and convert to date strings\n * - for other types: coerce them to numbers\n */axes.coercePosition=function(containerOut,gd,coerce,axRef,attr,dflt){var cleanPos,pos;if(axRef==='paper'||axRef==='pixel'){cleanPos=Lib.ensureNumber;pos=coerce(attr,dflt);}else{var ax=axes.getFromId(gd,axRef);dflt=ax.fraction2r(dflt);pos=coerce(attr,dflt);cleanPos=ax.cleanPos;}containerOut[attr]=cleanPos(pos);};axes.cleanPosition=function(pos,gd,axRef){var cleanPos=axRef==='paper'||axRef==='pixel'?Lib.ensureNumber:axes.getFromId(gd,axRef).cleanPos;return cleanPos(pos);};axes.redrawComponents=function(gd,axIds){axIds=axIds?axIds:axes.listIds(gd);var fullLayout=gd._fullLayout;function _redrawOneComp(moduleName,methodName,stashName,shortCircuit){var method=Registry.getComponentMethod(moduleName,methodName);var stash={};for(var i=0;i use setConvert method\nif(ax)return{d2c:ax.d2c,c2d:ax.c2d};// special case for 'ids'\n// -> cast to String\nif(d2cTarget==='ids')return{d2c:toString,c2d:toString};// otherwise (e.g. numeric-array of 'marker.color' or 'marker.size')\n// -> cast to Number\nreturn{d2c:toNum,c2d:toNum};};function toNum(v){return+v;}function toString(v){return String(v);}axes.getDataToCoordFunc=function(gd,trace,target,targetArray){return getDataConversions(gd,trace,target,targetArray).d2c;};// get counteraxis letter for this axis (name or id)\n// this can also be used as the id for default counter axis\naxes.counterLetter=function(id){var axLetter=id.charAt(0);if(axLetter==='x')return'y';if(axLetter==='y')return'x';};// incorporate a new minimum difference and first tick into\n// forced\n// note that _forceTick0 is linearized, so needs to be turned into\n// a range value for setting tick0\naxes.minDtick=function(ax,newDiff,newFirst,allow){// doesn't make sense to do forced min dTick on log or category axes,\n// and the plot itself may decide to cancel (ie non-grouped bars)\nif(['log','category','multicategory'].indexOf(ax.type)!==-1||!allow){ax._minDtick=0;}else if(ax._minDtick===undefined){// undefined means there's nothing there yet\nax._minDtick=newDiff;ax._forceTick0=newFirst;}else if(ax._minDtick){if((ax._minDtick/newDiff+1e-6)%1<2e-6&&// existing minDtick is an integer multiple of newDiff\n// (within rounding err)\n// and forceTick0 can be shifted to newFirst\n((newFirst-ax._forceTick0)/newDiff%1+1.000001)%1<2e-6){ax._minDtick=newDiff;ax._forceTick0=newFirst;}else if((newDiff/ax._minDtick+1e-6)%1>2e-6||// if the converse is true (newDiff is a multiple of minDtick and\n// newFirst can be shifted to forceTick0) then do nothing - same\n// forcing stands. Otherwise, cancel forced minimum\n((newFirst-ax._forceTick0)/ax._minDtick%1+1.000001)%1>2e-6){ax._minDtick=0;}}};// save a copy of the initial axis ranges in fullLayout\n// use them in mode bar and dblclick events\naxes.saveRangeInitial=function(gd,overwrite){var axList=axes.list(gd,'',true);var hasOneAxisChanged=false;for(var i=0;i50% within 1% of bin edges) or all data points integral\n// and offset the bins accordingly\nif(typeof finalSize==='number'){binStart=autoShiftNumericBins(binStart,data,dummyAx,dataMin,dataMax);bincount=1+Math.floor((dataMax-binStart)/finalSize);binEnd=binStart+bincount*finalSize;}else{// month ticks - should be the only nonlinear kind we have at this point.\n// dtick (as supplied by axes.autoTick) only has nonlinear values on\n// date and log axes, but even if you display a histogram on a log axis\n// we bin it on a linear axis (which one could argue against, but that's\n// a separate issue)\nif(dummyAx.dtick.charAt(0)==='M'){binStart=autoShiftMonthBins(binStart,data,finalSize,dataMin,calendar);}// calculate the endpoint for nonlinear ticks - you have to\n// just increment until you're done\nbinEnd=binStart;bincount=0;while(binEnd<=dataMax){binEnd=axes.tickIncrement(binEnd,finalSize,false,calendar);bincount++;}}return{start:ax.c2r(binStart,0,calendar),end:ax.c2r(binEnd,0,calendar),size:finalSize,_dataSpan:dataMax-dataMin};};function autoShiftNumericBins(binStart,data,ax,dataMin,dataMax){var edgecount=0;var midcount=0;var intcount=0;var blankCount=0;function nearEdge(v){// is a value within 1% of a bin edge?\nreturn(1+(v-binStart)*100/ax.dtick)%100<2;}for(var i=0;idataCount*0.3||nearEdge(dataMin)||nearEdge(dataMax)){// lots of points at the edge, not many in the middle\n// shift half a bin\nvar binshift=ax.dtick/2;binStart+=binStart+binshiftthreshold){var numMonths=Number(dtick.substr(1));if(stats.exactYears>threshold&&numMonths%12===0){// The exact middle of a non-leap-year is 1.5 days into July\n// so if we start the bins here, all but leap years will\n// get hover-labeled as exact years.\nbinStart=axes.tickIncrement(binStart,'M6','reverse')+ONEDAY*1.5;}else if(stats.exactMonths>threshold){// Months are not as clean, but if we shift half the *longest*\n// month (31/2 days) then 31-day months will get labeled exactly\n// and shorter months will get labeled with the correct month\n// but shifted 12-36 hours into it.\nbinStart=axes.tickIncrement(binStart,'M1','reverse')+ONEDAY*15.5;}else{// Shifting half a day is exact, but since these are month bins it\n// will always give a somewhat odd-looking label, until we do something\n// smarter like showing the bin boundaries (or the bounds of the actual\n// data in each bin)\nbinStart-=ONEDAY/2;}var nextBinStart=axes.tickIncrement(binStart,dtick);if(nextBinStart<=dataMin)return nextBinStart;}return binStart;}// ----------------------------------------------------\n// Ticks and grids\n// ----------------------------------------------------\n// ensure we have tick0, dtick, and tick rounding calculated\naxes.prepTicks=function(ax){var rng=Lib.simpleMap(ax.range,ax.r2l);// calculate max number of (auto) ticks to display based on plot size\nif(ax.tickmode==='auto'||!ax.dtick){var nt=ax.nticks;var minPx;if(!nt){if(ax.type==='category'||ax.type==='multicategory'){minPx=ax.tickfont?(ax.tickfont.size||12)*1.2:15;nt=ax._length/minPx;}else{minPx=ax._id.charAt(0)==='y'?40:80;nt=Lib.constrain(ax._length/minPx,4,9)+1;}// radial axes span half their domain,\n// multiply nticks value by two to get correct number of auto ticks.\nif(ax._name==='radialaxis')nt*=2;}// add a couple of extra digits for filling in ticks when we\n// have explicit tickvals without tick text\nif(ax.tickmode==='array')nt*=100;ax._roughDTick=(Math.abs(rng[1]-rng[0])-(ax._lBreaks||0))/nt;axes.autoTicks(ax,ax._roughDTick);// check for a forced minimum dtick\nif(ax._minDtick>0&&ax.dtick=endTick:x<=endTick;x=axes.tickIncrement(x,ax.dtick,axrev,ax.calendar)){// prevent infinite loops - no more than one tick per pixel,\n// and make sure each value is different from the previous\nif(tickVals.length>maxTicks||x===xPrevious)break;xPrevious=x;var minor=false;if(isDLog&&x!==(x|0)){minor=true;}tickVals.push({minor:minor,value:x});}}generateTicks();if(ax.rangebreaks){// replace ticks inside breaks that would get a tick\nif(ax.tickmode==='auto'){for(var t=0;t=brk.min&&value2){var tf2=2*(ax.tickfont?ax.tickfont.size:12);var newTickVals=[];var prevPos;var dir=axrev?1:-1;var first=axrev?0:len-1;var last=axrev?len-1:0;for(var q=first;dir*q<=dir*last;q+=dir){// apply reverse loop to pick greater values in breaks first\nvar pos=ax.c2p(tickVals[q].value);if(prevPos===undefined||Math.abs(pos-prevPos)>tf2){prevPos=pos;newTickVals.push(tickVals[q]);}}tickVals=newTickVals.reverse();}}// If same angle over a full circle, the last tick vals is a duplicate.\n// TODO must do something similar for angular date axes.\nif(isAngular(ax)&&Math.abs(rng[1]-rng[0])===360){tickVals.pop();}// save the last tick as well as first, so we can\n// show the exponent only on the last one\nax._tmax=(tickVals[tickVals.length-1]||{}).value;// for showing the rest of a date when the main tick label is only the\n// latter part: ax._prevDateHead holds what we showed most recently.\n// Start with it cleared and mark that we're in calcTicks (ie calculating a\n// whole string of these so we should care what the previous date head was!)\nax._prevDateHead='';ax._inCalcTicks=true;var ticksOut=new Array(tickVals.length);for(var i=0;itickMin&&vali half of the final unit - so precalculate twice the rough val\nvar roughX2=2*roughDTick;if(roughX2>ONEAVGYEAR){roughDTick/=ONEAVGYEAR;base=getBase(10);ax.dtick='M'+12*roundDTick(roughDTick,base,roundBase10);}else if(roughX2>ONEAVGMONTH){roughDTick/=ONEAVGMONTH;ax.dtick='M'+roundDTick(roughDTick,1,roundBase24);}else if(roughX2>ONEDAY){ax.dtick=roundDTick(roughDTick,ONEDAY,ax._hasDayOfWeekBreaks?[1,7,14]:roundDays);// get week ticks on sunday\n// this will also move the base tick off 2000-01-01 if dtick is\n// 2 or 3 days... but that's a weird enough case that we'll ignore it.\nax.tick0=Lib.dateTick0(ax.calendar,true);}else if(roughX2>ONEHOUR){ax.dtick=roundDTick(roughDTick,ONEHOUR,roundBase24);}else if(roughX2>ONEMIN){ax.dtick=roundDTick(roughDTick,ONEMIN,roundBase60);}else if(roughX2>ONESEC){ax.dtick=roundDTick(roughDTick,ONESEC,roundBase60);}else{// milliseconds\nbase=getBase(10);ax.dtick=roundDTick(roughDTick,base,roundBase10);}}else if(ax.type==='log'){ax.tick0=0;var rng=Lib.simpleMap(ax.range,ax.r2l);if(roughDTick>0.7){// only show powers of 10\nax.dtick=Math.ceil(roughDTick);}else if(Math.abs(rng[1]-rng[0])<1){// span is less than one power of 10\nvar nt=1.5*Math.abs((rng[1]-rng[0])/roughDTick);// ticks on a linear scale, labeled fully\nroughDTick=Math.abs(Math.pow(10,rng[1])-Math.pow(10,rng[0]))/nt;base=getBase(10);ax.dtick='L'+roundDTick(roughDTick,base,roundBase10);}else{// include intermediates between powers of 10,\n// labeled with small digits\n// ax.dtick = \"D2\" (show 2 and 5) or \"D1\" (show all digits)\nax.dtick=roughDTick>0.3?'D2':'D1';}}else if(ax.type==='category'||ax.type==='multicategory'){ax.tick0=0;ax.dtick=Math.ceil(Math.max(roughDTick,1));}else if(isAngular(ax)){ax.tick0=0;base=1;ax.dtick=roundDTick(roughDTick,base,roundAngles);}else{// auto ticks always start at 0\nax.tick0=0;base=getBase(10);ax.dtick=roundDTick(roughDTick,base,roundBase10);}// prevent infinite loops\nif(ax.dtick===0)ax.dtick=1;// TODO: this is from log axis histograms with autorange off\nif(!isNumeric(ax.dtick)&&typeof ax.dtick!=='string'){var olddtick=ax.dtick;ax.dtick=1;throw'ax.dtick error: '+String(olddtick);}};// after dtick is already known, find tickround = precision\n// to display in tick labels\n// for numeric ticks, integer # digits after . to round to\n// for date ticks, the last date part to show (y,m,d,H,M,S)\n// or an integer # digits past seconds\nfunction autoTickRound(ax){var dtick=ax.dtick;ax._tickexponent=0;if(!isNumeric(dtick)&&typeof dtick!=='string'){dtick=1;}if(ax.type==='category'||ax.type==='multicategory'){ax._tickround=null;}if(ax.type==='date'){// If tick0 is unusual, give tickround a bit more information\n// not necessarily *all* the information in tick0 though, if it's really odd\n// minimal string length for tick0: 'd' is 10, 'M' is 16, 'S' is 19\n// take off a leading minus (year < 0) and i (intercalary month) so length is consistent\nvar tick0ms=ax.r2l(ax.tick0);var tick0str=ax.l2r(tick0ms).replace(/(^-|i)/g,'');var tick0len=tick0str.length;if(String(dtick).charAt(0)==='M'){// any tick0 more specific than a year: alway show the full date\nif(tick0len>10||tick0str.substr(5)!=='01-01')ax._tickround='d';// show the month unless ticks are full multiples of a year\nelse ax._tickround=+dtick.substr(1)%12===0?'y':'m';}else if(dtick>=ONEDAY&&tick0len<=10||dtick>=ONEDAY*15)ax._tickround='d';else if(dtick>=ONEMIN&&tick0len<=16||dtick>=ONEHOUR)ax._tickround='M';else if(dtick>=ONESEC&&tick0len<=19||dtick>=ONEMIN)ax._tickround='S';else{// tickround is a number of digits of fractional seconds\n// of any two adjacent ticks, at least one will have the maximum fractional digits\n// of all possible ticks - so take the max. length of tick0 and the next one\nvar tick1len=ax.l2r(tick0ms+dtick).replace(/^-/,'').length;ax._tickround=Math.max(tick0len,tick1len)-20;// We shouldn't get here... but in case there's a situation I'm\n// not thinking of where tick0str and tick1str are identical or\n// something, fall back on maximum precision\nif(ax._tickround<0)ax._tickround=4;}}else if(isNumeric(dtick)||dtick.charAt(0)==='L'){// linear or log (except D1, D2)\nvar rng=ax.range.map(ax.r2d||Number);if(!isNumeric(dtick))dtick=Number(dtick.substr(1));// 2 digits past largest digit of dtick\nax._tickround=2-Math.floor(Math.log(dtick)/Math.LN10+0.01);var maxend=Math.max(Math.abs(rng[0]),Math.abs(rng[1]));var rangeexp=Math.floor(Math.log(maxend)/Math.LN10+0.01);if(Math.abs(rangeexp)>3){if(isSIFormat(ax.exponentformat)&&!beyondSI(rangeexp)){ax._tickexponent=3*Math.round((rangeexp-1)/3);}else ax._tickexponent=rangeexp;}}else{// D1 or D2 (log)\nax._tickround=null;}}// months and years don't have constant millisecond values\n// (but a year is always 12 months so we only need months)\n// log-scale ticks are also not consistently spaced, except\n// for pure powers of 10\n// numeric ticks always have constant differences, other datetime ticks\n// can all be calculated as constant number of milliseconds\naxes.tickIncrement=function(x,dtick,axrev,calendar){var axSign=axrev?-1:1;// includes linear, all dates smaller than month, and pure 10^n in log\nif(isNumeric(dtick))return x+axSign*dtick;// everything else is a string, one character plus a number\nvar tType=dtick.charAt(0);var dtSigned=axSign*Number(dtick.substr(1));// Dates: months (or years - see Lib.incrementMonth)\nif(tType==='M')return Lib.incrementMonth(x,dtSigned,calendar);// Log scales: Linear, Digits\nelse if(tType==='L')return Math.log(Math.pow(10,x)+dtSigned)/Math.LN10;// log10 of 2,5,10, or all digits (logs just have to be\n// close enough to round)\nelse if(tType==='D'){var tickset=dtick==='D2'?roundLog2:roundLog1;var x2=x+axSign*0.01;var frac=Lib.roundUp(Lib.mod(x2,1),tickset,axrev);return Math.floor(x2)+Math.log(d3.round(Math.pow(10,frac),1))/Math.LN10;}else throw'unrecognized dtick '+String(dtick);};// calculate the first tick on an axis\naxes.tickFirst=function(ax){var r2l=ax.r2l||Number;var rng=Lib.simpleMap(ax.range,r2l);var axrev=rng[1]=0&&p<=ax._length?v:null;};out.xbnd=[inbounds(out.x-0.5),inbounds(out.x+ax.dtick-0.5)];}return out;};/**\n * create text for a hover label on this axis, with special handling of\n * log axes (where negative values can't be displayed but can appear in hover text)\n *\n * @param {object} ax: the axis to format text for\n * @param {number} val: calcdata value to format\n * @param {Optional(number)} val2: a second value to display\n *\n * @returns {string} `val` formatted as a string appropriate to this axis, or\n * `val` and `val2` as a range (ie ' - ') if `val2` is provided and\n * it's different from `val`.\n */axes.hoverLabelText=function(ax,val,val2){if(val2!==BADNUM&&val2!==val){return axes.hoverLabelText(ax,val)+' - '+axes.hoverLabelText(ax,val2);}var logOffScale=ax.type==='log'&&val<=0;var tx=axes.tickText(ax,ax.c2l(logOffScale?-val:val),'hover').text;if(logOffScale){return val===0?'0':MINUS_SIGN+tx;}// TODO: should we do something special if the axis calendar and\n// the data calendar are different? Somehow display both dates with\n// their system names? Right now it will just display in the axis calendar\n// but users could add the other one as text.\nreturn tx;};function tickTextObj(ax,x,text){var tf=ax.tickfont||{};return{x:x,dx:0,dy:0,text:text||'',fontSize:tf.size,font:tf.family,fontColor:tf.color};}function formatDate(ax,out,hover,extraPrecision){var tr=ax._tickround;var fmt=hover&&ax.hoverformat||axes.getTickFormat(ax);if(extraPrecision){// second or sub-second precision: extra always shows max digits.\n// for other fields, extra precision just adds one field.\nif(isNumeric(tr))tr=4;else tr={y:'m',m:'d',d:'M',M:'S',S:4}[tr];}var dateStr=Lib.formatDate(out.x,fmt,tr,ax._dateFormat,ax.calendar,ax._extraFormat);var headStr;var splitIndex=dateStr.indexOf('\\n');if(splitIndex!==-1){headStr=dateStr.substr(splitIndex+1);dateStr=dateStr.substr(0,splitIndex);}if(extraPrecision){// if extraPrecision led to trailing zeros, strip them off\n// actually, this can lead to removing even more zeros than\n// in the original rounding, but that's fine because in these\n// contexts uniformity is not so important (if there's even\n// anything to be uniform with!)\n// can we remove the whole time part?\nif(dateStr==='00:00:00'||dateStr==='00:00'){dateStr=headStr;headStr='';}else if(dateStr.length===8){// strip off seconds if they're zero (zero fractional seconds\n// are already omitted)\n// but we never remove minutes and leave just hours\ndateStr=dateStr.replace(/:00$/,'');}}if(headStr){if(hover){// hover puts it all on one line, so headPart works best up front\n// except for year headPart: turn this into \"Jan 1, 2000\" etc.\nif(tr==='d')dateStr+=', '+headStr;else dateStr=headStr+(dateStr?', '+dateStr:'');}else if(!ax._inCalcTicks||headStr!==ax._prevDateHead){dateStr+='
'+headStr;ax._prevDateHead=headStr;}}out.text=dateStr;}function formatLog(ax,out,hover,extraPrecision,hideexp){var dtick=ax.dtick;var x=out.x;var tickformat=ax.tickformat;var dtChar0=typeof dtick==='string'&&dtick.charAt(0);if(hideexp==='never'){// If this is a hover label, then we must *never* hide the exponent\n// for the sake of display, which could give the wrong value by\n// potentially many orders of magnitude. If hideexp was 'never', then\n// it's now succeeded by preventing the other condition from automating\n// this choice. Thus we can unset it so that the axis formatting takes\n// precedence.\nhideexp='';}if(extraPrecision&&dtChar0!=='L'){dtick='L3';dtChar0='L';}if(tickformat||dtChar0==='L'){out.text=numFormat(Math.pow(10,x),ax,hideexp,extraPrecision);}else if(isNumeric(dtick)||dtChar0==='D'&&Lib.mod(x+0.01,1)<0.1){var p=Math.round(x);var absP=Math.abs(p);var exponentFormat=ax.exponentformat;if(exponentFormat==='power'||isSIFormat(exponentFormat)&&beyondSI(p)){if(p===0)out.text=1;else if(p===1)out.text='10';else out.text='10'+(p>1?'':MINUS_SIGN)+absP+'';out.fontSize*=1.25;}else if((exponentFormat==='e'||exponentFormat==='E')&&absP>2){out.text='1'+exponentFormat+(p>0?'+':MINUS_SIGN)+absP;}else{out.text=numFormat(Math.pow(10,x),ax,'','fakehover');if(dtick==='D1'&&ax._id.charAt(0)==='y'){out.dy-=out.fontSize/6;}}}else if(dtChar0==='D'){out.text=String(Math.round(Math.pow(10,Lib.mod(x,1))));out.fontSize*=0.75;}else throw'unrecognized dtick '+String(dtick);// if 9's are printed on log scale, move the 10's away a bit\nif(ax.dtick==='D1'){var firstChar=String(out.text).charAt(0);if(firstChar==='0'||firstChar==='1'){if(ax._id.charAt(0)==='y'){out.dx-=out.fontSize/4;}else{out.dy+=out.fontSize/2;out.dx+=(ax.range[1]>ax.range[0]?1:-1)*out.fontSize*(x<0?0.5:0.25);}}}}function formatCategory(ax,out){var tt=ax._categories[Math.round(out.x)];if(tt===undefined)tt='';out.text=String(tt);}function formatMultiCategory(ax,out,hover){var v=Math.round(out.x);var cats=ax._categories[v]||[];var tt=cats[1]===undefined?'':String(cats[1]);var tt2=cats[0]===undefined?'':String(cats[0]);if(hover){// TODO is this what we want?\nout.text=tt2+' - '+tt;}else{// setup for secondary labels\nout.text=tt;out.text2=tt2;}}function formatLinear(ax,out,hover,extraPrecision,hideexp){if(hideexp==='never'){// If this is a hover label, then we must *never* hide the exponent\n// for the sake of display, which could give the wrong value by\n// potentially many orders of magnitude. If hideexp was 'never', then\n// it's now succeeded by preventing the other condition from automating\n// this choice. Thus we can unset it so that the axis formatting takes\n// precedence.\nhideexp='';}else if(ax.showexponent==='all'&&Math.abs(out.x/ax.dtick)<1e-6){// don't add an exponent to zero if we're showing all exponents\n// so the only reason you'd show an exponent on zero is if it's the\n// ONLY tick to get an exponent (first or last)\nhideexp='hide';}out.text=numFormat(out.x,ax,hideexp,extraPrecision);}function formatAngle(ax,out,hover,extraPrecision,hideexp){if(ax.thetaunit==='radians'&&!hover){var num=out.x/180;if(num===0){out.text='0';}else{var frac=num2frac(num);if(frac[1]>=100){out.text=numFormat(Lib.deg2rad(out.x),ax,hideexp,extraPrecision);}else{var isNeg=out.x<0;if(frac[1]===1){if(frac[0]===1)out.text='π';else out.text=frac[0]+'π';}else{out.text=['',frac[0],'','⁄','',frac[1],'','π'].join('');}if(isNeg)out.text=MINUS_SIGN+out.text;}}}else{out.text=numFormat(out.x,ax,hideexp,extraPrecision);}}// inspired by\n// https://github.com/yisibl/num2fraction/blob/master/index.js\nfunction num2frac(num){function almostEq(a,b){return Math.abs(a-b)<=1e-6;}function findGCD(a,b){return almostEq(b,0)?a:findGCD(b,a%b);}function findPrecision(n){var e=1;while(!almostEq(Math.round(n*e)/e,n)){e*=10;}return e;}var precision=findPrecision(num);var number=num*precision;var gcd=Math.abs(findGCD(number,precision));return[// numerator\nMath.round(number/gcd),// denominator\nMath.round(precision/gcd)];}// format a number (tick value) according to the axis settings\n// new, more reliable procedure than d3.round or similar:\n// add half the rounding increment, then stringify and truncate\n// also automatically switch to sci. notation\nvar SIPREFIXES=['f','p','n','μ','m','','k','M','G','T'];function isSIFormat(exponentFormat){return exponentFormat==='SI'||exponentFormat==='B';}// are we beyond the range of common SI prefixes?\n// 10^-16 -> 1x10^-16\n// 10^-15 -> 1f\n// ...\n// 10^14 -> 100T\n// 10^15 -> 1x10^15\n// 10^16 -> 1x10^16\nfunction beyondSI(exponent){return exponent>14||exponent<-15;}function numFormat(v,ax,fmtoverride,hover){var isNeg=v<0;// max number of digits past decimal point to show\nvar tickRound=ax._tickround;var exponentFormat=fmtoverride||ax.exponentformat||'B';var exponent=ax._tickexponent;var tickformat=axes.getTickFormat(ax);var separatethousands=ax.separatethousands;// special case for hover: set exponent just for this value, and\n// add a couple more digits of precision over tick labels\nif(hover){// make a dummy axis obj to get the auto rounding and exponent\nvar ah={exponentformat:exponentFormat,dtick:ax.showexponent==='none'?ax.dtick:isNumeric(v)?Math.abs(v)||1:1,// if not showing any exponents, don't change the exponent\n// from what we calculate\nrange:ax.showexponent==='none'?ax.range.map(ax.r2d):[0,v||1]};autoTickRound(ah);tickRound=(Number(ah._tickround)||0)+4;exponent=ah._tickexponent;if(ax.hoverformat)tickformat=ax.hoverformat;}if(tickformat)return ax._numFormat(tickformat)(v).replace(/-/g,MINUS_SIGN);// 'epsilon' - rounding increment\nvar e=Math.pow(10,-tickRound)/2;// exponentFormat codes:\n// 'e' (1.2e+6, default)\n// 'E' (1.2E+6)\n// 'SI' (1.2M)\n// 'B' (same as SI except 10^9=B not G)\n// 'none' (1200000)\n// 'power' (1.2x10^6)\n// 'hide' (1.2, use 3rd argument=='hide' to eg\n// only show exponent on last tick)\nif(exponentFormat==='none')exponent=0;// take the sign out, put it back manually at the end\n// - makes cases easier\nv=Math.abs(v);if(v'+signedExponent+'';}else if(exponentFormat==='B'&&exponent===9){v+='B';}else if(isSIFormat(exponentFormat)){v+=SIPREFIXES[exponent/3+5];}}// put sign back in and return\n// replace standard minus character (which is technically a hyphen)\n// with a true minus sign\nif(isNeg)return MINUS_SIGN+v;return v;}axes.getTickFormat=function(ax){var i;function convertToMs(dtick){return typeof dtick!=='string'?dtick:Number(dtick.replace('M',''))*ONEAVGMONTH;}function compareLogTicks(left,right){var priority=['L','D'];if(typeof left===typeof right){if(typeof left==='number'){return left-right;}else{var leftPriority=priority.indexOf(left.charAt(0));var rightPriority=priority.indexOf(right.charAt(0));if(leftPriority===rightPriority){return Number(left.replace(/(L|D)/g,''))-Number(right.replace(/(L|D)/g,''));}else{return leftPriority-rightPriority;}}}else{return typeof left==='number'?1:-1;}}function isProperStop(dtick,range,convert){var convertFn=convert||function(x){return x;};var leftDtick=range[0];var rightDtick=range[1];return(!leftDtick&&typeof leftDtick!=='number'||convertFn(leftDtick)<=convertFn(dtick))&&(!rightDtick&&typeof rightDtick!=='number'||convertFn(rightDtick)>=convertFn(dtick));}function isProperLogStop(dtick,range){var isLeftDtickNull=range[0]===null;var isRightDtickNull=range[1]===null;var isDtickInRangeLeft=compareLogTicks(dtick,range[0])>=0;var isDtickInRangeRight=compareLogTicks(dtick,range[1])<=0;return(isLeftDtickNull||isDtickInRangeLeft)&&(isRightDtickNull||isDtickInRangeRight);}var tickstop,stopi;if(ax.tickformatstops&&ax.tickformatstops.length>0){switch(ax.type){case'date':case'linear':{for(i=0;i0?llbbox.bottom-pos:0,outsideTickLen);}}}if(ax.automargin){push={x:0,y:0,r:0,l:0,t:0,b:0};var domainIndices=[0,1];if(axLetter==='x'){if(s==='b'){push[s]=ax._depth;}else{push[s]=ax._depth=Math.max(llbbox.width>0?pos-llbbox.top:0,outsideTickLen);domainIndices.reverse();}if(llbbox.width>0){var rExtra=llbbox.right-(ax._offset+ax._length);if(rExtra>0){push.xr=1;push.r=rExtra;}var lExtra=ax._offset-llbbox.left;if(lExtra>0){push.xl=0;push.l=lExtra;}}}else{if(s==='l'){push[s]=ax._depth=Math.max(llbbox.height>0?pos-llbbox.left:0,outsideTickLen);}else{push[s]=ax._depth=Math.max(llbbox.height>0?llbbox.right-pos:0,outsideTickLen);domainIndices.reverse();}if(llbbox.height>0){var bExtra=llbbox.bottom-(ax._offset+ax._length);if(bExtra>0){push.yb=0;push.b=bExtra;}var tExtra=ax._offset-llbbox.top;if(tExtra>0){push.yt=1;push.t=tExtra;}}}push[counterLetter]=ax.anchor==='free'?ax.position:ax._anchorAxis.domain[domainIndices[0]];if(ax.title.text!==fullLayout._dfltTitle[axLetter]){push[s]+=approxTitleDepth(ax)+(ax.title.standoff||0);}if(ax.mirror&&ax.anchor!=='free'){mirrorPush={x:0,y:0,r:0,l:0,t:0,b:0};mirrorPush[sMirror]=ax.linewidth;if(ax.mirror&&ax.mirror!==true)mirrorPush[sMirror]+=outsideTickLen;if(ax.mirror===true||ax.mirror==='ticks'){mirrorPush[counterLetter]=ax._anchorAxis.domain[domainIndices[1]];}else if(ax.mirror==='all'||ax.mirror==='allticks'){mirrorPush[counterLetter]=[ax._counterDomainMin,ax._counterDomainMax][domainIndices[1]];}}}if(hasRangeSlider){rangeSliderPush=Registry.getComponentMethod('rangeslider','autoMarginOpts')(gd,ax);}Plots.autoMargin(gd,axAutoMarginID(ax),push);Plots.autoMargin(gd,axMirrorAutoMarginID(ax),mirrorPush);Plots.autoMargin(gd,rangeSliderAutoMarginID(ax),rangeSliderPush);});if(!opts.skipTitle&&!(hasRangeSlider&&ax.side==='bottom')){seq.push(function(){return drawTitle(gd,ax);});}return Lib.syncOrAsync(seq);};function getBoundaryVals(ax,vals){var out=[];var i;// boundaryVals are never used for labels;\n// no need to worry about the other tickTextObj keys\nvar _push=function(d,bndIndex){var xb=d.xbnd[bndIndex];if(xb!==null){out.push(Lib.extendFlat({},d,{x:xb}));}};if(vals.length){for(i=0;i, to make Drawing.bBox\n// retrieve a bbox computed with transform info\n//\n// To improve perf, it would be nice to use `thisLabel.node()`\n// (like in fixLabelOverlaps) instead and use Axes.getPxPosition\n// together with the makeLabelFns outputs and `tickangle`\n// to compute one bbox per (tick value x tick style)\nvar bb=Drawing.bBox(thisLabel.node().parentNode);top=Math.min(top,bb.top);bottom=Math.max(bottom,bb.bottom);left=Math.min(left,bb.left);right=Math.max(right,bb.right);});}else{top=0;bottom=0;left=0;right=0;}return{top:top,bottom:bottom,left:left,right:right,height:bottom-top,width:right-left};}/**\n * Which direction do the 'ax.side' values, and free ticks go?\n *\n * @param {object} ax (full) axis object\n * - {string} _id (starting with 'x' or 'y')\n * - {string} side\n * - {string} ticks\n * @return {array} all entries are either -1 or 1\n * - [0]: sign for top/right ticks (i.e. negative SVG direction)\n * - [1]: sign for bottom/left ticks (i.e. positive SVG direction)\n * - [2]: sign for ticks corresponding to 'ax.side'\n * - [3]: sign for ticks mirroring 'ax.side'\n * - [4]: sign of arrow starting at axis pointing towards margin\n */axes.getTickSigns=function(ax){var axLetter=ax._id.charAt(0);var sideOpposite={x:'top',y:'right'}[axLetter];var main=ax.side===sideOpposite?1:-1;var out=[-1,1,main,-main];// then we flip if outside XOR y axis\nif(ax.ticks!=='inside'===(axLetter==='x')){out=out.map(function(v){return-v;});}// independent of `ticks`; do not flip this one\nif(ax.side){out.push({l:-1,t:-1,r:1,b:1}[ax.side.charAt(0)]);}return out;};/**\n * Make axis translate transform function\n *\n * @param {object} ax (full) axis object\n * - {string} _id\n * - {number} _offset\n * - {fn} l2p\n * @return {fn} function of calcTicks items\n */axes.makeTransFn=function(ax){var axLetter=ax._id.charAt(0);var offset=ax._offset;return axLetter==='x'?function(d){return'translate('+(offset+ax.l2p(d.x))+',0)';}:function(d){return'translate(0,'+(offset+ax.l2p(d.x))+')';};};/**\n * Make axis tick path string\n *\n * @param {object} ax (full) axis object\n * - {string} _id\n * - {number} ticklen\n * - {number} linewidth\n * @param {number} shift along direction of ticklen\n * @param {1 or -1} sgn tick sign\n * @param {number (optional)} len tick length\n * @return {string}\n */axes.makeTickPath=function(ax,shift,sgn,len){len=len!==undefined?len:ax.ticklen;var axLetter=ax._id.charAt(0);var pad=(ax.linewidth||1)/2;return axLetter==='x'?'M0,'+(shift+pad*sgn)+'v'+len*sgn:'M'+(shift+pad*sgn)+',0h'+len*sgn;};/**\n * Make axis tick label x, y and anchor functions\n *\n * @param {object} ax (full) axis object\n * - {string} _id\n * - {string} ticks\n * - {number} ticklen\n * - {string} side\n * - {number} linewidth\n * - {number} tickfont.size\n * - {boolean} showline\n * @param {number} shift\n * @param {number} angle [in degrees] ...\n * @return {object}\n * - {fn} xFn\n * - {fn} yFn\n * - {fn} anchorFn\n * - {fn} heightFn\n * - {number} labelStandoff (gap parallel to ticks)\n * - {number} labelShift (gap perpendicular to ticks)\n */axes.makeLabelFns=function(ax,shift,angle){var axLetter=ax._id.charAt(0);var ticksOnOutsideLabels=ax.tickson!=='boundaries'&&ax.ticks==='outside';var labelStandoff=0;var labelShift=0;if(ticksOnOutsideLabels){labelStandoff+=ax.ticklen;}if(angle&&ax.ticks==='outside'){var rad=Lib.deg2rad(angle);labelStandoff=ax.ticklen*Math.cos(rad)+1;labelShift=ax.ticklen*Math.sin(rad);}if(ax.showticklabels&&(ticksOnOutsideLabels||ax.showline)){labelStandoff+=0.2*ax.tickfont.size;}labelStandoff+=(ax.linewidth||1)/2;var out={labelStandoff:labelStandoff,labelShift:labelShift};var x0,y0,ff,flipIt;if(axLetter==='x'){flipIt=ax.side==='bottom'?1:-1;x0=labelShift*flipIt;y0=shift+labelStandoff*flipIt;ff=ax.side==='bottom'?1:-0.2;out.xFn=function(d){return d.dx+x0;};out.yFn=function(d){return d.dy+y0+d.fontSize*ff;};out.anchorFn=function(d,a){if(!isNumeric(a)||a===0||a===180){return'middle';}return a*flipIt<0?'end':'start';};out.heightFn=function(d,a,h){return a<-60||a>60?-0.5*h:ax.side==='top'?-h:0;};}else if(axLetter==='y'){flipIt=ax.side==='right'?1:-1;x0=labelStandoff;y0=-labelShift*flipIt;ff=Math.abs(ax.tickangle)===90?0.5:0;out.xFn=function(d){return d.dx+shift+(x0+d.fontSize*ff)*flipIt;};out.yFn=function(d){return d.dy+y0+d.fontSize*MID_SHIFT;};out.anchorFn=function(d,a){if(isNumeric(a)&&Math.abs(a)===90){return'middle';}return ax.side==='right'?'start':'end';};out.heightFn=function(d,a,h){a*=ax.side==='left'?1:-1;return a<-30?-h:a<30?-0.5*h:0;};}return out;};function tickDataFn(d){return[d.text,d.x,d.axInfo,d.font,d.fontSize,d.fontColor].join('_');}/**\n * Draw axis ticks\n *\n * @param {DOM element} gd\n * @param {object} ax (full) axis object\n * - {string} _id\n * - {string} ticks\n * - {number} linewidth\n * - {string} tickcolor\n * @param {object} opts\n * - {array of object} vals (calcTicks output-like)\n * - {d3 selection} layer\n * - {string or fn} path\n * - {fn} transFn\n * - {boolean} crisp (set to false to unset crisp-edge SVG rendering)\n */axes.drawTicks=function(gd,ax,opts){opts=opts||{};var cls=ax._id+'tick';var ticks=opts.layer.selectAll('path.'+cls).data(ax.ticks?opts.vals:[],tickDataFn);ticks.exit().remove();ticks.enter().append('path').classed(cls,1).classed('ticks',1).classed('crisp',opts.crisp!==false).call(Color.stroke,ax.tickcolor).style('stroke-width',Drawing.crispRound(gd,ax.tickwidth,1)+'px').attr('d',opts.path);ticks.attr('transform',opts.transFn);};/**\n * Draw axis grid\n *\n * @param {DOM element} gd\n * @param {object} ax (full) axis object\n * - {string} _id\n * - {boolean} showgrid\n * - {string} gridcolor\n * - {string} gridwidth\n * - {boolean} zeroline\n * - {string} type\n * - {string} dtick\n * @param {object} opts\n * - {array of object} vals (calcTicks output-like)\n * - {d3 selection} layer\n * - {object} counterAxis (full axis object corresponding to counter axis)\n * optional - only required if this axis supports zero lines\n * - {string or fn} path\n * - {fn} transFn\n * - {boolean} crisp (set to false to unset crisp-edge SVG rendering)\n */axes.drawGrid=function(gd,ax,opts){opts=opts||{};var cls=ax._id+'grid';var vals=opts.vals;var counterAx=opts.counterAxis;if(ax.showgrid===false){vals=[];}else if(counterAx&&axes.shouldShowZeroLine(gd,ax,counterAx)){var isArrayMode=ax.tickmode==='array';for(var i=0;i1&&p1){for(j=1;jncnt*2;}// are the (x,y)-values in gd.data mostly text?\n// require twice as many DISTINCT categories as distinct numbers\nfunction category(a){// test at most 1000 points\nvar inc=Math.max(1,(a.length-1)/1000);var curvenums=0;var curvecats=0;var seen={};for(var i=0;icurvenums*2;}// very-loose requirements for multicategory,\n// trace modules that should never auto-type to multicategory\n// should be declared with 'noMultiCategory'\nfunction multiCategory(a){return Lib.isArrayOrTypedArray(a[0])&&Lib.isArrayOrTypedArray(a[1]);}},{\"../../constants/numerical\":711,\"../../lib\":735,\"fast-isnumeric\":237}],785:[function(_dereq_,module,exports){/**\n* Copyright 2012-2020, Plotly, Inc.\n* All rights reserved.\n*\n* This source code is licensed under the MIT license found in the\n* LICENSE file in the root directory of this source tree.\n*/'use strict';var isNumeric=_dereq_('fast-isnumeric');var Registry=_dereq_('../../registry');var Lib=_dereq_('../../lib');var handleArrayContainerDefaults=_dereq_('../array_container_defaults');var layoutAttributes=_dereq_('./layout_attributes');var handleTickValueDefaults=_dereq_('./tick_value_defaults');var handleTickMarkDefaults=_dereq_('./tick_mark_defaults');var handleTickLabelDefaults=_dereq_('./tick_label_defaults');var handleCategoryOrderDefaults=_dereq_('./category_order_defaults');var handleLineGridDefaults=_dereq_('./line_grid_defaults');var setConvert=_dereq_('./set_convert');var DAY_OF_WEEK=_dereq_('./constants').WEEKDAY_PATTERN;var HOUR=_dereq_('./constants').HOUR_PATTERN;/**\n * options: object containing:\n *\n * letter: 'x' or 'y'\n * title: name of the axis (ie 'Colorbar') to go in default title\n * font: the default font to inherit\n * outerTicks: boolean, should ticks default to outside?\n * showGrid: boolean, should gridlines be shown by default?\n * noHover: boolean, this axis doesn't support hover effects?\n * noTickson: boolean, this axis doesn't support 'tickson'\n * data: the plot data, used to manage categories\n * bgColor: the plot background color, to calculate default gridline colors\n * calendar:\n * splomStash:\n * visibleDflt: boolean\n * reverseDflt: boolean\n * automargin: boolean\n */module.exports=function handleAxisDefaults(containerIn,containerOut,coerce,options,layoutOut){var letter=options.letter;var font=options.font||{};var splomStash=options.splomStash||{};var visible=coerce('visible',!options.visibleDflt);var axTemplate=containerOut._template||{};var axType=containerOut.type||axTemplate.type||'-';if(axType==='date'){var handleCalendarDefaults=Registry.getComponentMethod('calendars','handleDefaults');handleCalendarDefaults(containerIn,containerOut,'calendar',options.calendar);}setConvert(containerOut,layoutOut);var autorangeDflt=!containerOut.isValidRange(containerIn.range);if(autorangeDflt&&options.reverseDflt)autorangeDflt='reversed';var autoRange=coerce('autorange',autorangeDflt);if(autoRange&&(axType==='linear'||axType==='-'))coerce('rangemode');coerce('range');containerOut.cleanRange();handleCategoryOrderDefaults(containerIn,containerOut,coerce,options);if(axType!=='category'&&!options.noHover)coerce('hoverformat');var dfltColor=coerce('color');// if axis.color was provided, use it for fonts too; otherwise,\n// inherit from global font color in case that was provided.\n// Compare to dflt rather than to containerIn, so we can provide color via\n// template too.\nvar dfltFontColor=dfltColor!==layoutAttributes.color.dflt?dfltColor:font.color;// try to get default title from splom trace, fallback to graph-wide value\nvar dfltTitle=splomStash.label||layoutOut._dfltTitle[letter];handleTickLabelDefaults(containerIn,containerOut,coerce,axType,options,{pass:1});if(!visible)return containerOut;coerce('title.text',dfltTitle);Lib.coerceFont(coerce,'title.font',{family:font.family,size:Math.round(font.size*1.2),color:dfltFontColor});handleTickValueDefaults(containerIn,containerOut,coerce,axType);handleTickLabelDefaults(containerIn,containerOut,coerce,axType,options,{pass:2});handleTickMarkDefaults(containerIn,containerOut,coerce,options);handleLineGridDefaults(containerIn,containerOut,coerce,{dfltColor:dfltColor,bgColor:options.bgColor,showGrid:options.showGrid,attributes:layoutAttributes});if(containerOut.showline||containerOut.ticks)coerce('mirror');if(options.automargin)coerce('automargin');var isMultiCategory=axType==='multicategory';if(!options.noTickson&&(axType==='category'||isMultiCategory)&&(containerOut.ticks||containerOut.showgrid)){var ticksonDflt;if(isMultiCategory)ticksonDflt='boundaries';coerce('tickson',ticksonDflt);}if(isMultiCategory){var showDividers=coerce('showdividers');if(showDividers){coerce('dividercolor');coerce('dividerwidth');}}if(axType==='date'){handleArrayContainerDefaults(containerIn,containerOut,{name:'rangebreaks',inclusionAttr:'enabled',handleItemDefaults:rangebreaksDefaults});if(!containerOut.rangebreaks.length){delete containerOut.rangebreaks;}else{for(var k=0;k=2){var dfltPattern='';var i,q;if(bnds.length===2){for(i=0;i<2;i++){q=indexOfDay(bnds[i]);if(q){dfltPattern=DAY_OF_WEEK;break;}}}var pattern=coerce('pattern',dfltPattern);if(pattern===DAY_OF_WEEK){for(i=0;i<2;i++){q=indexOfDay(bnds[i]);if(q){// convert to integers i.e 'Sunday' --> 0\nitemOut.bounds[i]=bnds[i]=q-1;}}}if(pattern){// ensure types and ranges\nfor(i=0;i<2;i++){q=bnds[i];switch(pattern){case DAY_OF_WEEK:if(!isNumeric(q)){itemOut.enabled=false;return;}q=+q;if(q!==Math.floor(q)||// don't accept fractional days for mow\nq<0||q>=7){itemOut.enabled=false;return;}// use number\nitemOut.bounds[i]=bnds[i]=q;break;case HOUR:if(!isNumeric(q)){itemOut.enabled=false;return;}q=+q;if(q<0||q>24){// accept 24\nitemOut.enabled=false;return;}// use number\nitemOut.bounds[i]=bnds[i]=q;break;}}}if(containerOut.autorange===false){var rng=containerOut.range;// if bounds are bigger than the (set) range, disable break\nif(rng[0]rng[1]){itemOut.enabled=false;return;}}else if(bnds[0]>rng[0]&&bnds[1]letter2?1:-1;return+(id1.substr(1)||1)-+(id2.substr(1)||1);};exports.getAxisGroup=function getAxisGroup(fullLayout,axId){var matchGroups=fullLayout._axisMatchGroups;for(var i=0;i0;// override default 'categoryorder' value when non-empty array is supplied\nvar orderDefault;if(isValidArray)orderDefault='array';var order=coerce('categoryorder',orderDefault);var array;// coerce 'categoryarray' only in array order case\nif(order==='array'){array=coerce('categoryarray');}// cannot set 'categoryorder' to 'array' with an invalid 'categoryarray'\nif(!isValidArray&&order==='array'){order=containerOut.categoryorder='trace';}// set up things for makeCalcdata\nif(order==='trace'){containerOut._initialCategories=[];}else if(order==='array'){containerOut._initialCategories=array.slice();}else{array=findCategories(containerOut,opts).sort();if(order==='category ascending'){containerOut._initialCategories=array;}else if(order==='category descending'){containerOut._initialCategories=array.reverse();}}};},{}],788:[function(_dereq_,module,exports){/**\n* Copyright 2012-2020, Plotly, Inc.\n* All rights reserved.\n*\n* This source code is licensed under the MIT license found in the\n* LICENSE file in the root directory of this source tree.\n*/'use strict';var isNumeric=_dereq_('fast-isnumeric');var Lib=_dereq_('../../lib');var ONEDAY=_dereq_('../../constants/numerical').ONEDAY;/**\n * Return a validated dtick value for this axis\n *\n * @param {any} dtick: the candidate dtick. valid values are numbers and strings,\n * and further constrained depending on the axis type.\n * @param {string} axType: the axis type\n */exports.dtick=function(dtick,axType){var isLog=axType==='log';var isDate=axType==='date';var isCat=axType==='category';var dtickDflt=isDate?ONEDAY:1;if(!dtick)return dtickDflt;if(isNumeric(dtick)){dtick=Number(dtick);if(dtick<=0)return dtickDflt;if(isCat){// category dtick must be positive integers\nreturn Math.max(1,Math.round(dtick));}if(isDate){// date dtick must be at least 0.1ms (our current precision)\nreturn Math.max(0.1,dtick);}return dtick;}if(typeof dtick!=='string'||!(isDate||isLog)){return dtickDflt;}var prefix=dtick.charAt(0);var dtickNum=dtick.substr(1);dtickNum=isNumeric(dtickNum)?Number(dtickNum):0;if(dtickNum<=0||!(// \"M\" gives ticks every (integer) n months\nisDate&&prefix==='M'&&dtickNum===Math.round(dtickNum)||// \"L\" gives ticks linearly spaced in data (not in position) every (float) f\nisLog&&prefix==='L'||// \"D1\" gives powers of 10 with all small digits between, \"D2\" gives only 2 and 5\nisLog&&prefix==='D'&&(dtickNum===1||dtickNum===2))){return dtickDflt;}return dtick;};/**\n * Return a validated tick0 for this axis\n *\n * @param {any} tick0: the candidate tick0. Valid values are numbers and strings,\n * further constrained depending on the axis type\n * @param {string} axType: the axis type\n * @param {string} calendar: for date axes, the calendar to validate/convert with\n * @param {any} dtick: an already valid dtick. Only used for D1 and D2 log dticks,\n * which do not support tick0 at all.\n */exports.tick0=function(tick0,axType,calendar,dtick){if(axType==='date'){return Lib.cleanDate(tick0,Lib.dateTick0(calendar));}if(dtick==='D1'||dtick==='D2'){// D1 and D2 modes ignore tick0 entirely\nreturn undefined;}// Aside from date axes, tick0 must be numeric\nreturn isNumeric(tick0)?Number(tick0):0;};},{\"../../constants/numerical\":711,\"../../lib\":735,\"fast-isnumeric\":237}],789:[function(_dereq_,module,exports){/**\n* Copyright 2012-2020, Plotly, Inc.\n* All rights reserved.\n*\n* This source code is licensed under the MIT license found in the\n* LICENSE file in the root directory of this source tree.\n*/'use strict';var counterRegex=_dereq_('../../lib/regex').counter;module.exports={idRegex:{x:counterRegex('x'),y:counterRegex('y')},attrRegex:counterRegex('[xy]axis'),// axis match regular expression\nxAxisMatch:counterRegex('xaxis'),yAxisMatch:counterRegex('yaxis'),// pattern matching axis ids and names\n// note that this is more permissive than counterRegex, as\n// id2name, name2id, and cleanId accept \"x1\" etc\nAX_ID_PATTERN:/^[xyz][0-9]*$/,AX_NAME_PATTERN:/^[xyz]axis[0-9]*$/,// and for 2D subplots\nSUBPLOT_PATTERN:/^x([0-9]*)y([0-9]*)$/,HOUR_PATTERN:'hour',WEEKDAY_PATTERN:'day of week',// pixels to move mouse before you stop clamping to starting point\nMINDRAG:8,// smallest dimension allowed for a select box\nMINSELECT:12,// smallest dimension allowed for a zoombox\nMINZOOM:20,// width of axis drag regions\nDRAGGERSIZE:20,// max pixels off straight before a lasso select line counts as bent\nBENDPX:1.5,// delay before a redraw (relayout) after smooth panning and zooming\nREDRAWDELAY:50,// throttling limit (ms) for selectPoints calls\nSELECTDELAY:100,// cache ID suffix for throttle\nSELECTID:'-select',// last resort axis ranges for x and y axes if we have no data\nDFLTRANGEX:[-1,6],DFLTRANGEY:[-1,4],// Layers to keep trace types in the right order\n// N.B. each 'unique' plot method must have its own layer\ntraceLayerClasses:['imagelayer','heatmaplayer','contourcarpetlayer','contourlayer','funnellayer','waterfalllayer','barlayer','carpetlayer','violinlayer','boxlayer','ohlclayer','scattercarpetlayer','scatterlayer'],clipOnAxisFalseQuery:['.scatterlayer','.barlayer','.funnellayer','.waterfalllayer'],layerValue2layerClass:{'above traces':'above','below traces':'below'}};},{\"../../lib/regex\":751}],790:[function(_dereq_,module,exports){/**\n* Copyright 2012-2020, Plotly, Inc.\n* All rights reserved.\n*\n* This source code is licensed under the MIT license found in the\n* LICENSE file in the root directory of this source tree.\n*/'use strict';var Lib=_dereq_('../../lib');var id2name=_dereq_('./axis_ids').id2name;var scaleZoom=_dereq_('./scale_zoom');var makePadFn=_dereq_('./autorange').makePadFn;var concatExtremes=_dereq_('./autorange').concatExtremes;var ALMOST_EQUAL=_dereq_('../../constants/numerical').ALMOST_EQUAL;var FROM_BL=_dereq_('../../constants/alignment').FROM_BL;exports.handleConstraintDefaults=function(containerIn,containerOut,coerce,opts){var allAxisIds=opts.allAxisIds;var layoutOut=opts.layoutOut;var scaleanchorDflt=opts.scaleanchorDflt;var constrainDflt=opts.constrainDflt;var constraintGroups=layoutOut._axisConstraintGroups;var matchGroups=layoutOut._axisMatchGroups;var axId=containerOut._id;var axLetter=axId.charAt(0);var splomStash=((layoutOut._splomAxes||{})[axLetter]||{})[axId]||{};var thisID=containerOut._id;var letter=thisID.charAt(0);// coerce the constraint mechanics even if this axis has no scaleanchor\n// because it may be the anchor of another axis.\nvar constrain=coerce('constrain',constrainDflt);Lib.coerce(containerIn,containerOut,{constraintoward:{valType:'enumerated',values:letter==='x'?['left','center','right']:['bottom','middle','top'],dflt:letter==='x'?'center':'middle'}},'constraintoward');var matches,matchOpts;if((containerIn.matches||splomStash.matches)&&!containerOut.fixedrange){matchOpts=getConstraintOpts(matchGroups,thisID,allAxisIds,layoutOut);matches=Lib.coerce(containerIn,containerOut,{matches:{valType:'enumerated',values:matchOpts.linkableAxes||[],dflt:splomStash.matches}},'matches');}// 'matches' wins over 'scaleanchor' (for now)\nvar scaleanchor,scaleOpts;if(!matches&&!(containerOut.fixedrange&&constrain!=='domain')&&(containerIn.scaleanchor||scaleanchorDflt)){scaleOpts=getConstraintOpts(constraintGroups,thisID,allAxisIds,layoutOut,constrain);scaleanchor=Lib.coerce(containerIn,containerOut,{scaleanchor:{valType:'enumerated',values:scaleOpts.linkableAxes||[]}},'scaleanchor',scaleanchorDflt);}if(matches){delete containerOut.constrain;updateConstraintGroups(matchGroups,matchOpts.thisGroup,thisID,matches,1);}else if(allAxisIds.indexOf(containerIn.matches)!==-1){Lib.warn('ignored '+containerOut._name+'.matches: \"'+containerIn.matches+'\" to avoid either an infinite loop '+'or because the target axis has fixed range.');}if(scaleanchor){var scaleratio=coerce('scaleratio');// TODO: I suppose I could do attribute.min: Number.MIN_VALUE to avoid zero,\n// but that seems hacky. Better way to say \"must be a positive number\"?\n// Of course if you use several super-tiny values you could eventually\n// force a product of these to zero and all hell would break loose...\n// Likewise with super-huge values.\nif(!scaleratio)scaleratio=containerOut.scaleratio=1;updateConstraintGroups(constraintGroups,scaleOpts.thisGroup,thisID,scaleanchor,scaleratio);}else if(allAxisIds.indexOf(containerIn.scaleanchor)!==-1){Lib.warn('ignored '+containerOut._name+'.scaleanchor: \"'+containerIn.scaleanchor+'\" to avoid either an infinite loop '+'and possibly inconsistent scaleratios, or because the target '+'axis has fixed range or this axis declares a *matches* constraint.');}};// If this axis is already part of a constraint group, we can't\n// scaleanchor any other axis in that group, or we'd make a loop.\n// Filter allAxisIds to enforce this, also matching axis types.\nfunction getConstraintOpts(groups,thisID,allAxisIds,layoutOut,constrain){var doesNotConstrainRange=constrain!=='range';var thisType=layoutOut[id2name(thisID)].type;var i,j,idj,axj;var linkableAxes=[];for(j=0;jALMOST_EQUAL*maxScale&&!hasAnyDomainConstraint)continue;// now increase any ranges we need to until all normalized scales are equal\nfor(j=0;jouterMin&&newValrangeMax){rangeMax=newVal;}}var domainExpand=(rangeMax-rangeMin)/(2*halfRange);factor/=domainExpand;rangeMin=ax.l2r(rangeMin);rangeMax=ax.l2r(rangeMax);ax.range=ax._input.range=rl0=0){gd._fullLayout._deactivateShape(gd);return;}var clickmode=gd._fullLayout.clickmode;removeZoombox(gd);if(numClicks===2&&!singleEnd)doubleClick();if(isMainDrag){if(clickmode.indexOf('select')>-1){selectOnClick(evt,gd,xaxes,yaxes,plotinfo.id,dragOptions);}if(clickmode.indexOf('event')>-1){Fx.click(gd,evt,plotinfo.id);}}else if(numClicks===1&&singleEnd){var ax=ns?ya0:xa0;var end=ns==='s'||ew==='w'?0:1;var attrStr=ax._name+'.range['+end+']';var initialText=getEndText(ax,end);var hAlign='left';var vAlign='middle';if(ax.fixedrange)return;if(ns){vAlign=ns==='n'?'top':'bottom';if(ax.side==='right')hAlign='right';}else if(ew==='e')hAlign='right';if(gd._context.showAxisRangeEntryBoxes){d3.select(dragger).call(svgTextUtils.makeEditable,{gd:gd,immediate:true,background:gd._fullLayout.paper_bgcolor,text:String(initialText),fill:ax.tickfont?ax.tickfont.color:'#444',horizontalAlign:hAlign,verticalAlign:vAlign}).on('edit',function(text){var v=ax.d2r(text);if(v!==undefined){Registry.call('_guiRelayout',gd,attrStr,v);}});}}}dragElement.init(dragOptions);// x/y px position at start of drag\nvar x0,y0;// bbox object of the zoombox\nvar box;// luminance of bg behind zoombox\nvar lum;// zoombox path outline\nvar path0;// is zoombox dimmed (during drag)\nvar dimmed;// 'x'-only, 'y' or 'xy' zooming\nvar zoomMode;// zoombox d3 selection\nvar zb;// zoombox corner d3 selection\nvar corners;// zoom takes over minDrag, so it also has to take over gd._dragged\nvar zoomDragged;function zoomPrep(e,startX,startY){var dragBBox=dragger.getBoundingClientRect();x0=startX-dragBBox.left;y0=startY-dragBBox.top;box={l:x0,r:x0,w:0,t:y0,b:y0,h:0};lum=gd._hmpixcount?gd._hmlumcount/gd._hmpixcount:tinycolor(gd._fullLayout.plot_bgcolor).getLuminance();path0='M0,0H'+pw+'V'+ph+'H0V0';dimmed=false;zoomMode='xy';zoomDragged=false;zb=makeZoombox(zoomlayer,lum,xs,ys,path0);corners=makeCorners(zoomlayer,xs,ys);}function zoomMove(dx0,dy0){if(gd._transitioningWithDuration){return false;}var x1=Math.max(0,Math.min(pw,dx0+x0));var y1=Math.max(0,Math.min(ph,dy0+y0));var dx=Math.abs(x1-x0);var dy=Math.abs(y1-y0);box.l=Math.min(x0,x1);box.r=Math.max(x0,x1);box.t=Math.min(y0,y1);box.b=Math.max(y0,y1);function noZoom(){zoomMode='';box.r=box.l;box.t=box.b;corners.attr('d','M0,0Z');}if(links.isSubplotConstrained){if(dx>MINZOOM||dy>MINZOOM){zoomMode='xy';if(dx/pw>dy/ph){dy=dx*ph/pw;if(y0>y1)box.t=y0-dy;else box.b=y0+dy;}else{dx=dy*pw/ph;if(x0>x1)box.l=x0-dx;else box.r=x0+dx;}corners.attr('d',xyCorners(box));}else{noZoom();}}else if(matches.isSubplotConstrained){if(dx>MINZOOM||dy>MINZOOM){zoomMode='xy';var r0=Math.min(box.l/pw,(ph-box.b)/ph);var r1=Math.max(box.r/pw,(ph-box.t)/ph);box.l=r0*pw;box.r=r1*pw;box.b=(1-r0)*ph;box.t=(1-r1)*ph;corners.attr('d',xyCorners(box));}else{noZoom();}}else if(!yActive||dy0 is compressing scale (cursor is over the plot,\n// the axis end should move with the cursor)\n// d<0 is expanding (cursor is off the plot, axis end moves\n// nonlinearly so you can expand far)\nfunction dZoom(d){return 1-(d>=0?Math.min(d,0.9):1/(1/Math.max(d,-0.3)+3.222));}function getDragCursor(nsew,dragmode,isMainDrag){if(!nsew)return'pointer';if(nsew==='nsew'){// in this case here, clear cursor and\n// use the cursor style set on \nif(isMainDrag)return'';if(dragmode==='pan')return'move';return'crosshair';}return nsew.toLowerCase()+'-resize';}function makeZoombox(zoomlayer,lum,xs,ys,path0){return zoomlayer.append('path').attr('class','zoombox').style({'fill':lum>0.2?'rgba(0,0,0,0)':'rgba(255,255,255,0)','stroke-width':0}).attr('transform','translate('+xs+', '+ys+')').attr('d',path0+'Z');}function makeCorners(zoomlayer,xs,ys){return zoomlayer.append('path').attr('class','zoombox-corners').style({fill:Color.background,stroke:Color.defaultLine,'stroke-width':1,opacity:0}).attr('transform','translate('+xs+', '+ys+')').attr('d','M0,0Z');}function updateZoombox(zb,corners,box,path0,dimmed,lum){zb.attr('d',path0+'M'+box.l+','+box.t+'v'+box.h+'h'+box.w+'v-'+box.h+'h-'+box.w+'Z');transitionZoombox(zb,corners,dimmed,lum);}function transitionZoombox(zb,corners,dimmed,lum){if(!dimmed){zb.transition().style('fill',lum>0.2?'rgba(0,0,0,0.4)':'rgba(255,255,255,0.3)').duration(200);corners.transition().style('opacity',1).duration(200);}}function removeZoombox(gd){d3.select(gd).selectAll('.zoombox,.js-zoombox-backdrop,.js-zoombox-menu,.zoombox-corners').remove();}function showDoubleClickNotifier(gd){if(SHOWZOOMOUTTIP&&gd.data&&gd._context.showTips){Lib.notifier(Lib._(gd,'Double-click to zoom back out'),'long');SHOWZOOMOUTTIP=false;}}function xCorners(box,y0){return'M'+(box.l-0.5)+','+(y0-MINZOOM-0.5)+'h-3v'+(2*MINZOOM+1)+'h3ZM'+(box.r+0.5)+','+(y0-MINZOOM-0.5)+'h3v'+(2*MINZOOM+1)+'h-3Z';}function yCorners(box,x0){return'M'+(x0-MINZOOM-0.5)+','+(box.t-0.5)+'v-3h'+(2*MINZOOM+1)+'v3ZM'+(x0-MINZOOM-0.5)+','+(box.b+0.5)+'v3h'+(2*MINZOOM+1)+'v-3Z';}function xyCorners(box){var clen=Math.floor(Math.min(box.b-box.t,box.r-box.l,MINZOOM)/2);return'M'+(box.l-3.5)+','+(box.t-0.5+clen)+'h3v'+-clen+'h'+clen+'v-3h-'+(clen+3)+'ZM'+(box.r+3.5)+','+(box.t-0.5+clen)+'h-3v'+-clen+'h'+-clen+'v-3h'+(clen+3)+'ZM'+(box.r+3.5)+','+(box.b+0.5-clen)+'h-3v'+clen+'h'+-clen+'v3h'+(clen+3)+'ZM'+(box.l-3.5)+','+(box.b+0.5-clen)+'h3v'+clen+'h'+clen+'v3h-'+(clen+3)+'Z';}function calcLinks(gd,groups,xaHash,yaHash){var isSubplotConstrained=false;var xLinks={};var yLinks={};var xID,yID,xLinkID,yLinkID;for(var i=0;i cursor style.\n//\n// Note that changing the axis configuration and/or the fixedrange attribute\n// should trigger a full initInteractions.\nexports.updateFx=function(gd){var fullLayout=gd._fullLayout;var cursor=fullLayout.dragmode==='pan'?'move':'crosshair';setCursor(fullLayout._draggers,cursor);};},{\"../../components/dragelement\":621,\"../../components/fx\":642,\"../../lib/setcursor\":755,\"./constants\":789,\"./dragbox\":791,\"d3\":165}],793:[function(_dereq_,module,exports){/**\n* Copyright 2012-2020, Plotly, Inc.\n* All rights reserved.\n*\n* This source code is licensed under the MIT license found in the\n* LICENSE file in the root directory of this source tree.\n*/'use strict';function clearOutlineControllers(gd){var zoomLayer=gd._fullLayout._zoomlayer;if(zoomLayer){zoomLayer.selectAll('.outline-controllers').remove();}}function clearSelect(gd){var zoomLayer=gd._fullLayout._zoomlayer;if(zoomLayer){// until we get around to persistent selections, remove the outline\n// here. The selection itself will be removed when the plot redraws\n// at the end.\nzoomLayer.selectAll('.select-outline').remove();}gd._fullLayout._drawing=false;}module.exports={clearOutlineControllers:clearOutlineControllers,clearSelect:clearSelect};},{}],794:[function(_dereq_,module,exports){/**\n* Copyright 2012-2020, Plotly, Inc.\n* All rights reserved.\n*\n* This source code is licensed under the MIT license found in the\n* LICENSE file in the root directory of this source tree.\n*/'use strict';// in v2 (once log ranges are fixed),\n// we'll be able to p2r here for all axis types\nfunction p2r(ax,v){switch(ax.type){case'log':return ax.p2d(v);case'date':return ax.p2r(v,0,ax.calendar);default:return ax.p2r(v);}}function r2p(ax,v){switch(ax.type){case'log':return ax.d2p(v);case'date':return ax.r2p(v,0,ax.calendar);default:return ax.r2p(v);}}function axValue(ax){var index=ax._id.charAt(0)==='y'?1:0;return function(v){return p2r(ax,v[index]);};}function getTransform(plotinfo){return'translate('+plotinfo.xaxis._offset+','+plotinfo.yaxis._offset+')';}module.exports={p2r:p2r,r2p:r2p,axValue:axValue,getTransform:getTransform};},{}],795:[function(_dereq_,module,exports){/**\n* Copyright 2012-2020, Plotly, Inc.\n* All rights reserved.\n*\n* This source code is licensed under the MIT license found in the\n* LICENSE file in the root directory of this source tree.\n*/'use strict';var Registry=_dereq_('../../registry');var Lib=_dereq_('../../lib');/**\n * Factory function for checking component arrays for subplot references.\n *\n * @param {string} containerArrayName: the top-level array in gd.layout to check\n * If an item in this container is found that references a cartesian x and/or y axis,\n * ensure cartesian is marked as a base plot module and record the axes (and subplot\n * if both refs are axes) in gd._fullLayout\n *\n * @return {function}: with args layoutIn (gd.layout) and layoutOut (gd._fullLayout)\n * as expected of a component includeBasePlot method\n */module.exports=function makeIncludeComponents(containerArrayName){return function includeComponents(layoutIn,layoutOut){var array=layoutIn[containerArrayName];if(!Array.isArray(array))return;var Cartesian=Registry.subplotsRegistry.cartesian;var idRegex=Cartesian.idRegex;var subplots=layoutOut._subplots;var xaList=subplots.xaxis;var yaList=subplots.yaxis;var cartesianList=subplots.cartesian;var hasCartesianOrGL2D=layoutOut._has('cartesian')||layoutOut._has('gl2d');for(var i=0;i so that the new cartesian subplot\n// can have the correct layer ordering\nif(oldFullLayout._hasOnlyLargeSploms&&!newFullLayout._hasOnlyLargeSploms){for(k in oldPlots){plotinfo=oldPlots[k];if(plotinfo.plotgroup)plotinfo.plotgroup.remove();}}var hadGl=oldFullLayout._has&&oldFullLayout._has('gl');var hasGl=newFullLayout._has&&newFullLayout._has('gl');if(hadGl&&!hasGl){for(k in oldPlots){plotinfo=oldPlots[k];if(plotinfo._scene)plotinfo._scene.destroy();}}// delete any titles we don't need anymore\n// check if axis list has changed, and if so clear old titles\nif(oldSubplotList.xaxis&&oldSubplotList.yaxis){var oldAxIDs=axisIds.listIds({_fullLayout:oldFullLayout});for(i=0;is here\n// as other subplots may need them\n});// must remove overlaid subplot trace layers 'manually'\nfor(var k in fullLayout._plots){var subplotInfo=fullLayout._plots[k];var overlays=subplotInfo.overlays||[];for(var j=0;jdomain[1]-1/4096)containerOut.domain=dfltDomain;Lib.noneOrAll(containerIn.domain,containerOut.domain,dfltDomain);}coerce('layer');return containerOut;};},{\"../../lib\":735,\"fast-isnumeric\":237}],801:[function(_dereq_,module,exports){/**\n* Copyright 2012-2020, Plotly, Inc.\n* All rights reserved.\n*\n* This source code is licensed under the MIT license found in the\n* LICENSE file in the root directory of this source tree.\n*/'use strict';var FROM_BL=_dereq_('../../constants/alignment').FROM_BL;module.exports=function scaleZoom(ax,factor,centerFraction){if(centerFraction===undefined){centerFraction=FROM_BL[ax.constraintoward||'center'];}var rangeLinear=[ax.r2l(ax.range[0]),ax.r2l(ax.range[1])];var center=rangeLinear[0]+(rangeLinear[1]-rangeLinear[0])*centerFraction;ax.range=ax._input.range=[ax.l2r(center+(rangeLinear[0]-center)*factor),ax.l2r(center+(rangeLinear[1]-center)*factor)];};},{\"../../constants/alignment\":704}],802:[function(_dereq_,module,exports){/**\n* Copyright 2012-2020, Plotly, Inc.\n* All rights reserved.\n*\n* This source code is licensed under the MIT license found in the\n* LICENSE file in the root directory of this source tree.\n*/'use strict';var polybool=_dereq_('polybooljs');var Registry=_dereq_('../../registry');var dashStyle=_dereq_('../../components/drawing').dashStyle;var Color=_dereq_('../../components/color');var Fx=_dereq_('../../components/fx');var makeEventData=_dereq_('../../components/fx/helpers').makeEventData;var dragHelpers=_dereq_('../../components/dragelement/helpers');var freeMode=dragHelpers.freeMode;var rectMode=dragHelpers.rectMode;var drawMode=dragHelpers.drawMode;var openMode=dragHelpers.openMode;var selectMode=dragHelpers.selectMode;var displayOutlines=_dereq_('../../components/shapes/draw_newshape/display_outlines');var handleEllipse=_dereq_('../../components/shapes/draw_newshape/helpers').handleEllipse;var newShapes=_dereq_('../../components/shapes/draw_newshape/newshapes');var Lib=_dereq_('../../lib');var polygon=_dereq_('../../lib/polygon');var throttle=_dereq_('../../lib/throttle');var getFromId=_dereq_('./axis_ids').getFromId;var clearGlCanvases=_dereq_('../../lib/clear_gl_canvases');var redrawReglTraces=_dereq_('../../plot_api/subroutines').redrawReglTraces;var constants=_dereq_('./constants');var MINSELECT=constants.MINSELECT;var filteredPolygon=polygon.filter;var polygonTester=polygon.tester;var clearSelect=_dereq_('./handle_outline').clearSelect;var helpers=_dereq_('./helpers');var p2r=helpers.p2r;var axValue=helpers.axValue;var getTransform=helpers.getTransform;function prepSelect(e,startX,startY,dragOptions,mode){var isFreeMode=freeMode(mode);var isRectMode=rectMode(mode);var isOpenMode=openMode(mode);var isDrawMode=drawMode(mode);var isSelectMode=selectMode(mode);var isLine=mode==='drawline';var isEllipse=mode==='drawcircle';var isLineOrEllipse=isLine||isEllipse;// cases with two start & end positions\nvar gd=dragOptions.gd;var fullLayout=gd._fullLayout;var zoomLayer=fullLayout._zoomlayer;var dragBBox=dragOptions.element.getBoundingClientRect();var plotinfo=dragOptions.plotinfo;var transform=getTransform(plotinfo);var x0=startX-dragBBox.left;var y0=startY-dragBBox.top;var x1=x0;var y1=y0;var path0='M'+x0+','+y0;var pw=dragOptions.xaxes[0]._length;var ph=dragOptions.yaxes[0]._length;var allAxes=dragOptions.xaxes.concat(dragOptions.yaxes);var subtract=e.altKey&&!(drawMode(mode)&&isOpenMode);var filterPoly,selectionTester,mergedPolygons,currentPolygon;var i,searchInfo,eventData;coerceSelectionsCache(e,gd,dragOptions);if(isFreeMode){filterPoly=filteredPolygon([[x0,y0]],constants.BENDPX);}var outlines=zoomLayer.selectAll('path.select-outline-'+plotinfo.id).data(isDrawMode?[0]:[1,2]);var drwStyle=fullLayout.newshape;outlines.enter().append('path').attr('class',function(d){return'select-outline select-outline-'+d+' select-outline-'+plotinfo.id;}).style(isDrawMode?{opacity:drwStyle.opacity/2,fill:isOpenMode?undefined:drwStyle.fillcolor,stroke:drwStyle.line.color,'stroke-dasharray':dashStyle(drwStyle.line.dash,drwStyle.line.width),'stroke-width':drwStyle.line.width+'px'}:{}).attr('fill-rule',drwStyle.fillrule).classed('cursor-move',isDrawMode?true:false).attr('transform',transform).attr('d',path0+'Z');var corners=zoomLayer.append('path').attr('class','zoombox-corners').style({fill:Color.background,stroke:Color.defaultLine,'stroke-width':1}).attr('transform',transform).attr('d','M0,0Z');var throttleID=fullLayout._uid+constants.SELECTID;var selection=[];// find the traces to search for selection points\nvar searchTraces=determineSearchTraces(gd,dragOptions.xaxes,dragOptions.yaxes,dragOptions.subplot);function ascending(a,b){return a-b;}// allow subplots to override fillRangeItems routine\nvar fillRangeItems;if(plotinfo.fillRangeItems){fillRangeItems=plotinfo.fillRangeItems;}else{if(isRectMode){fillRangeItems=function(eventData,poly){var ranges=eventData.range={};for(i=0;i=0){gd._fullLayout._deactivateShape(gd);return;}if(isDrawMode)return;var clickmode=fullLayout.clickmode;throttle.done(throttleID).then(function(){throttle.clear(throttleID);if(numClicks===2){// clear selection on doubleclick\noutlines.remove();for(i=0;i-1){selectOnClick(evt,gd,dragOptions.xaxes,dragOptions.yaxes,dragOptions.subplot,dragOptions,outlines);}if(clickmode==='event'){// TODO: remove in v2 - this was probably never intended to work as it does,\n// but in case anyone depends on it we don't want to break it now.\n// Note that click-to-select introduced pre v2 also emitts proper\n// event data when clickmode is having 'select' in its flag list.\ngd.emit('plotly_selected',undefined);}}Fx.click(gd,evt);}).catch(Lib.error);};dragOptions.doneFn=function(){corners.remove();throttle.done(throttleID).then(function(){throttle.clear(throttleID);dragOptions.gd.emit('plotly_selected',eventData);if(currentPolygon&&dragOptions.selectionDefs){// save last polygons\ncurrentPolygon.subtract=subtract;dragOptions.selectionDefs.push(currentPolygon);// we have to keep reference to arrays container\ndragOptions.mergedPolygons.length=0;[].push.apply(dragOptions.mergedPolygons,mergedPolygons);}if(dragOptions.doneFnCompleted){dragOptions.doneFnCompleted(selection);}}).catch(Lib.error);if(isDrawMode){clearSelectionsCache(dragOptions);}};}function selectOnClick(evt,gd,xAxes,yAxes,subplot,dragOptions,polygonOutlines){var hoverData=gd._hoverdata;var fullLayout=gd._fullLayout;var clickmode=fullLayout.clickmode;var sendEvents=clickmode.indexOf('event')>-1;var selection=[];var searchTraces,searchInfo,currentSelectionDef,selectionTester,traceSelection;var thisTracesSelection,pointOrBinSelected,subtract,eventData,i;if(isHoverDataSet(hoverData)){coerceSelectionsCache(evt,gd,dragOptions);searchTraces=determineSearchTraces(gd,xAxes,yAxes,subplot);var clickedPtInfo=extractClickedPtInfo(hoverData,searchTraces);var isBinnedTrace=clickedPtInfo.pointNumbers.length>0;// Note: potentially costly operation isPointOrBinSelected is\n// called as late as possible through the use of an assignment\n// in an if condition.\nif(isBinnedTrace?isOnlyThisBinSelected(searchTraces,clickedPtInfo):isOnlyOnePointSelected(searchTraces)&&(pointOrBinSelected=isPointOrBinSelected(clickedPtInfo))){if(polygonOutlines)polygonOutlines.remove();for(i=0;i=0){gd._fullLayout._deactivateShape(gd);}if(drawMode(dragmode)){var fullLayout=gd._fullLayout;var zoomLayer=fullLayout._zoomlayer;var outlines=zoomLayer.selectAll('.select-outline-'+plotinfo.id);if(outlines&&gd._fullLayout._drawing){// add shape\nvar shapes=newShapes(outlines,dragOptions);if(shapes){Registry.call('_guiRelayout',gd,{shapes:shapes});}gd._fullLayout._drawing=false;}}plotinfo.selection={};plotinfo.selection.selectionDefs=dragOptions.selectionDefs=[];plotinfo.selection.mergedPolygons=dragOptions.mergedPolygons=[];}function determineSearchTraces(gd,xAxes,yAxes,subplot){var searchTraces=[];var xAxisIds=xAxes.map(function(ax){return ax._id;});var yAxisIds=yAxes.map(function(ax){return ax._id;});var cd,trace,i;for(i=0;i0;// When pointsNumbers is set (e.g. histogram's binning),\n// it is assumed that when the first point of\n// a bin is selected, all others are as well\nvar ptNumToTest=ptNumsSet?ptNums[0]:ptNum;// TODO potential performance improvement\n// Primarily we need this function to determine if a click adds\n// or subtracts from a selection.\n// In cases `trace.selectedpoints` is a huge array, indexOf\n// might be slow. One remedy would be to introduce a hash somewhere.\nreturn trace.selectedpoints?trace.selectedpoints.indexOf(ptNumToTest)>-1:false;}function isOnlyThisBinSelected(searchTraces,clickedPtInfo){var tracesWithSelectedPts=[];var searchInfo,trace,isSameTrace,i;for(i=0;i0){tracesWithSelectedPts.push(searchInfo);}}if(tracesWithSelectedPts.length===1){isSameTrace=tracesWithSelectedPts[0]===clickedPtInfo.searchInfo;if(isSameTrace){trace=clickedPtInfo.searchInfo.cd[0].trace;if(trace.selectedpoints.length===clickedPtInfo.pointNumbers.length){for(i=0;i1)return false;len+=trace.selectedpoints.length;if(len>1)return false;}}return len===1;}function updateSelectedState(gd,searchTraces,eventData){var i,searchInfo,cd,trace;// before anything else, update preGUI if necessary\nfor(i=0;i0)return Math.log(v)/Math.LN10;else if(v<=0&&clip&&ax.range&&ax.range.length===2){// clip NaN (ie past negative infinity) to LOG_CLIP axis\n// length past the negative edge\nvar r0=ax.range[0];var r1=ax.range[1];return 0.5*(r0+r1-2*LOG_CLIP*Math.abs(r0-r1));}else return BADNUM;}/*\n * wrapped dateTime2ms that:\n * - accepts ms numbers for backward compatibility\n * - inserts a dummy arg so calendar is the 3rd arg (see notes below).\n * - defaults to ax.calendar\n */function dt2ms(v,_,calendar){// NOTE: Changed this behavior: previously we took any numeric value\n// to be a ms, even if it was a string that could be a bare year.\n// Now we convert it as a date if at all possible, and only try\n// as (local) ms if that fails.\nvar ms=dateTime2ms(v,calendar||ax.calendar);if(ms===BADNUM){if(isNumeric(v)){v=+v;// keep track of tenths of ms, that `new Date` will drop\n// same logic as in Lib.ms2DateTime\nvar msecTenths=Math.floor(Lib.mod(v+0.05,1)*10);var msRounded=Math.round(v-msecTenths/10);ms=dateTime2ms(new Date(msRounded))+msecTenths/10;}else return BADNUM;}return ms;}// wrapped ms2DateTime to insert default ax.calendar\nfunction ms2dt(v,r,calendar){return ms2DateTime(v,r,calendar||ax.calendar);}function getCategoryName(v){return ax._categories[Math.round(v)];}/*\n * setCategoryIndex: return the index of category v,\n * inserting it in the list if it's not already there\n *\n * this will enter the categories in the order it\n * encounters them, ie all the categories from the\n * first data set, then all the ones from the second\n * that aren't in the first etc.\n *\n * it is assumed that this function is being invoked in the\n * already sorted category order; otherwise there would be\n * a disconnect between the array and the index returned\n */function setCategoryIndex(v){if(isValidCategory(v)){if(ax._categoriesMap===undefined){ax._categoriesMap={};}if(ax._categoriesMap[v]!==undefined){return ax._categoriesMap[v];}else{ax._categories.push(typeof v==='number'?String(v):v);var curLength=ax._categories.length-1;ax._categoriesMap[v]=curLength;return curLength;}}return BADNUM;}function setMultiCategoryIndex(arrayIn,len){var arrayOut=new Array(len);for(var i=0;iax.range[1])flip=!flip;var signAx=flip?-1:1;var pos=signAx*v;var q=0;for(var i=0;imax)q=i+1;else{// when falls into break, pick 'closest' offset\nq=pos<(min+max)/2?i:i+1;break;}}var b2=ax._B[q]||0;if(!isFinite(b2))return 0;// avoid NaN translate e.g. in positionLabels if one keep zooming exactly into a break\nreturn _l2p(v,ax._m2,b2);};p2l=function(px){var len=ax._rangebreaks.length;if(!len)return _p2l(px,ax._m,ax._b);var q=0;for(var i=0;iax._rangebreaks[i].pmax)q=i+1;}return _p2l(px,ax._m2,ax._B[q]);};}// conversions among c/l/p are fairly simple - do them together for all axis types\nax.c2l=ax.type==='log'?toLog:ensureNumber;ax.l2c=ax.type==='log'?fromLog:ensureNumber;ax.l2p=l2p;ax.p2l=p2l;ax.c2p=ax.type==='log'?function(v,clip){return l2p(toLog(v,clip));}:l2p;ax.p2c=ax.type==='log'?function(px){return fromLog(p2l(px));}:p2l;/*\n * now type-specific conversions for **ALL** other combinations\n * they're all written out, instead of being combinations of each other, for\n * both clarity and speed.\n */if(['linear','-'].indexOf(ax.type)!==-1){// all are data vals, but d and r need cleaning\nax.d2r=ax.r2d=ax.d2c=ax.r2c=ax.d2l=ax.r2l=cleanNumber;ax.c2d=ax.c2r=ax.l2d=ax.l2r=ensureNumber;ax.d2p=ax.r2p=function(v){return ax.l2p(cleanNumber(v));};ax.p2d=ax.p2r=p2l;ax.cleanPos=ensureNumber;}else if(ax.type==='log'){// d and c are data vals, r and l are logged (but d and r need cleaning)\nax.d2r=ax.d2l=function(v,clip){return toLog(cleanNumber(v),clip);};ax.r2d=ax.r2c=function(v){return fromLog(cleanNumber(v));};ax.d2c=ax.r2l=cleanNumber;ax.c2d=ax.l2r=ensureNumber;ax.c2r=toLog;ax.l2d=fromLog;ax.d2p=function(v,clip){return ax.l2p(ax.d2r(v,clip));};ax.p2d=function(px){return fromLog(p2l(px));};ax.r2p=function(v){return ax.l2p(cleanNumber(v));};ax.p2r=p2l;ax.cleanPos=ensureNumber;}else if(ax.type==='date'){// r and d are date strings, l and c are ms\n/*\n * Any of these functions with r and d on either side, calendar is the\n * **3rd** argument. log has reserved the second argument.\n *\n * Unless you need the special behavior of the second arg (ms2DateTime\n * uses this to limit precision, toLog uses true to clip negatives\n * to offscreen low rather than undefined), it's safe to pass 0.\n */ax.d2r=ax.r2d=Lib.identity;ax.d2c=ax.r2c=ax.d2l=ax.r2l=dt2ms;ax.c2d=ax.c2r=ax.l2d=ax.l2r=ms2dt;ax.d2p=ax.r2p=function(v,_,calendar){return ax.l2p(dt2ms(v,0,calendar));};ax.p2d=ax.p2r=function(px,r,calendar){return ms2dt(p2l(px),r,calendar);};ax.cleanPos=function(v){return Lib.cleanDate(v,BADNUM,ax.calendar);};}else if(ax.type==='category'){// d is categories (string)\n// c and l are indices (numbers)\n// r is categories or numbers\nax.d2c=ax.d2l=setCategoryIndex;ax.r2d=ax.c2d=ax.l2d=getCategoryName;ax.d2r=ax.d2l_noadd=getCategoryPosition;ax.r2c=function(v){var index=getCategoryPosition(v);return index!==undefined?index:ax.fraction2r(0.5);};ax.l2r=ax.c2r=ensureNumber;ax.r2l=getCategoryPosition;ax.d2p=function(v){return ax.l2p(ax.r2c(v));};ax.p2d=function(px){return getCategoryName(p2l(px));};ax.r2p=ax.d2p;ax.p2r=p2l;ax.cleanPos=function(v){if(typeof v==='string'&&v!=='')return v;return ensureNumber(v);};}else if(ax.type==='multicategory'){// N.B. multicategory axes don't define d2c and d2l,\n// as 'data-to-calcdata' conversion needs to take into\n// account all data array items as in ax.makeCalcdata.\nax.r2d=ax.c2d=ax.l2d=getCategoryName;ax.d2r=ax.d2l_noadd=getCategoryPosition;ax.r2c=function(v){var index=getCategoryPosition(v);return index!==undefined?index:ax.fraction2r(0.5);};ax.r2c_just_indices=getCategoryIndex;ax.l2r=ax.c2r=ensureNumber;ax.r2l=getCategoryPosition;ax.d2p=function(v){return ax.l2p(ax.r2c(v));};ax.p2d=function(px){return getCategoryName(p2l(px));};ax.r2p=ax.d2p;ax.p2r=p2l;ax.cleanPos=function(v){if(Array.isArray(v)||typeof v==='string'&&v!=='')return v;return ensureNumber(v);};ax.setupMultiCategory=function(fullData){var traceIndices=ax._traceIndices;var i,j;var matchGroups=fullLayout._axisMatchGroups;if(matchGroups&&matchGroups.length&&ax._categories.length===0){for(i=0;iFP_SAFE)range[i]=FP_SAFE;if(range[0]===range[1]){// somewhat arbitrary: split by 1 or 1ppm, whichever is bigger\nvar inc=Math.max(1,Math.abs(range[0]*1e-6));range[0]-=inc;range[1]+=inc;}}}};// set scaling to pixels\nax.setScale=function(usePrivateRange){var gs=fullLayout._size;// make sure we have a domain (pull it in from the axis\n// this one is overlaying if necessary)\nif(ax.overlaying){var ax2=axisIds.getFromId({_fullLayout:fullLayout},ax.overlaying);ax.domain=ax2.domain;}// While transitions are occurring, we get a double-transform\n// issue if we transform the drawn layer *and* use the new axis range to\n// draw the data. This allows us to construct setConvert using the pre-\n// interaction values of the range:\nvar rangeAttr=usePrivateRange&&ax._r?'_r':'range';var calendar=ax.calendar;ax.cleanRange(rangeAttr);var rl0=ax.r2l(ax[rangeAttr][0],calendar);var rl1=ax.r2l(ax[rangeAttr][1],calendar);var isY=axLetter==='y';if(isY){ax._offset=gs.t+(1-ax.domain[1])*gs.h;ax._length=gs.h*(ax.domain[1]-ax.domain[0]);ax._m=ax._length/(rl0-rl1);ax._b=-ax._m*rl1;}else{ax._offset=gs.l+ax.domain[0]*gs.w;ax._length=gs.w*(ax.domain[1]-ax.domain[0]);ax._m=ax._length/(rl1-rl0);ax._b=-ax._m*rl0;}// set of \"N\" disjoint rangebreaks inside the range\nax._rangebreaks=[];// length of these rangebreaks in value space - negative on reversed axes\nax._lBreaks=0;// l2p slope (same for all intervals)\nax._m2=0;// set of l2p offsets (one for each of the (N+1) piecewise intervals)\nax._B=[];if(ax.rangebreaks){var i,brk;ax._rangebreaks=ax.locateBreaks(Math.min(rl0,rl1),Math.max(rl0,rl1));if(ax._rangebreaks.length){for(i=0;irl1)flip=!flip;if(flip)ax._rangebreaks.reverse();var sign=flip?-1:1;ax._m2=sign*ax._length/(Math.abs(rl1-rl0)-ax._lBreaks);ax._B.push(-ax._m2*(isY?rl1:rl0));for(i=0;ib1){b1+=7;if(vbb1){b1+=24;if(vb=b0&&vb=b0&&vbrkj.max||maxbrkj.max){brkj.max=max;}isNewBreak=false;}}if(isNewBreak){rangebreaksOut.push({min:min,max:max});}};for(i=0;i rect').call(Drawing.setTranslate,0,0).call(Drawing.setScale,1,1);subplot.plot.call(Drawing.setTranslate,xa._offset,ya._offset).call(Drawing.setScale,1,1);var traceGroups=subplot.plot.selectAll('.scatterlayer .trace');// This is specifically directed at scatter traces, applying an inverse\n// scale to individual points to counteract the scale of the trace\n// as a whole:\ntraceGroups.selectAll('.point').call(Drawing.setPointGroupScale,1,1);traceGroups.selectAll('.textpoint').call(Drawing.setTextPointsScale,1,1);traceGroups.call(Drawing.hideOutsideRangePoints,subplot);}function updateSubplot(edit,progress){var plotinfo=edit.plotinfo;var xa=plotinfo.xaxis;var ya=plotinfo.yaxis;var xlen=xa._length;var ylen=ya._length;var editX=!!edit.xr1;var editY=!!edit.yr1;var viewBox=[];if(editX){var xr0=Lib.simpleMap(edit.xr0,xa.r2l);var xr1=Lib.simpleMap(edit.xr1,xa.r2l);var dx0=xr0[1]-xr0[0];var dx1=xr1[1]-xr1[0];viewBox[0]=(xr0[0]*(1-progress)+progress*xr1[0]-xr0[0])/(xr0[1]-xr0[0])*xlen;viewBox[2]=xlen*(1-progress+progress*dx1/dx0);xa.range[0]=xa.l2r(xr0[0]*(1-progress)+progress*xr1[0]);xa.range[1]=xa.l2r(xr0[1]*(1-progress)+progress*xr1[1]);}else{viewBox[0]=0;viewBox[2]=xlen;}if(editY){var yr0=Lib.simpleMap(edit.yr0,ya.r2l);var yr1=Lib.simpleMap(edit.yr1,ya.r2l);var dy0=yr0[1]-yr0[0];var dy1=yr1[1]-yr1[0];viewBox[1]=(yr0[1]*(1-progress)+progress*yr1[1]-yr0[1])/(yr0[0]-yr0[1])*ylen;viewBox[3]=ylen*(1-progress+progress*dy1/dy0);ya.range[0]=xa.l2r(yr0[0]*(1-progress)+progress*yr1[0]);ya.range[1]=ya.l2r(yr0[1]*(1-progress)+progress*yr1[1]);}else{viewBox[1]=0;viewBox[3]=ylen;}Axes.drawOne(gd,xa,{skipTitle:true});Axes.drawOne(gd,ya,{skipTitle:true});Axes.redrawComponents(gd,[xa._id,ya._id]);var xScaleFactor=editX?xlen/viewBox[2]:1;var yScaleFactor=editY?ylen/viewBox[3]:1;var clipDx=editX?viewBox[0]:0;var clipDy=editY?viewBox[1]:0;var fracDx=editX?viewBox[0]/viewBox[2]*xlen:0;var fracDy=editY?viewBox[1]/viewBox[3]*ylen:0;var plotDx=xa._offset-fracDx;var plotDy=ya._offset-fracDy;plotinfo.clipRect.call(Drawing.setTranslate,clipDx,clipDy).call(Drawing.setScale,1/xScaleFactor,1/yScaleFactor);plotinfo.plot.call(Drawing.setTranslate,plotDx,plotDy).call(Drawing.setScale,xScaleFactor,yScaleFactor);// apply an inverse scale to individual points to counteract\n// the scale of the trace group.\nDrawing.setPointGroupScale(plotinfo.zoomScalePts,1/xScaleFactor,1/yScaleFactor);Drawing.setTextPointsScale(plotinfo.zoomScaleTxt,1/xScaleFactor,1/yScaleFactor);}var onComplete;if(makeOnCompleteCallback){// This module makes the choice whether or not it notifies Plotly.transition\n// about completion:\nonComplete=makeOnCompleteCallback();}function transitionComplete(){var aobj={};for(var i=0;itransitionOpts.duration){transitionComplete();raf=window.cancelAnimationFrame(doFrame);}else{raf=window.requestAnimationFrame(doFrame);}}t1=Date.now();raf=window.requestAnimationFrame(doFrame);return Promise.resolve();};},{\"../../components/drawing\":624,\"../../lib\":735,\"../../registry\":866,\"./axes\":783,\"d3\":165}],808:[function(_dereq_,module,exports){/**\n* Copyright 2012-2020, Plotly, Inc.\n* All rights reserved.\n*\n* This source code is licensed under the MIT license found in the\n* LICENSE file in the root directory of this source tree.\n*/'use strict';var traceIs=_dereq_('../../registry').traceIs;var autoType=_dereq_('./axis_autotype');/*\n * data: the plot data to use in choosing auto type\n * name: axis object name (ie 'xaxis') if one should be stored\n */module.exports=function handleTypeDefaults(containerIn,containerOut,coerce,options){var axType=coerce('type',(options.splomStash||{}).type);if(axType==='-'){setAutoType(containerOut,options.data);if(containerOut.type==='-'){containerOut.type='linear';}else{// copy autoType back to input axis\n// note that if this object didn't exist\n// in the input layout, we have to put it in\n// this happens in the main supplyDefaults function\ncontainerIn.type=containerOut.type;}}};function setAutoType(ax,data){// new logic: let people specify any type they want,\n// only autotype if type is '-'\nif(ax.type!=='-')return;var id=ax._id;var axLetter=id.charAt(0);var i;// support 3d\nif(id.indexOf('scene')!==-1)id=axLetter;var d0=getFirstNonEmptyTrace(data,id,axLetter);if(!d0)return;// first check for histograms, as the count direction\n// should always default to a linear axis\nif(d0.type==='histogram'&&axLetter==={v:'y',h:'x'}[d0.orientation||'v']){ax.type='linear';return;}var calAttr=axLetter+'calendar';var calendar=d0[calAttr];var opts={noMultiCategory:!traceIs(d0,'cartesian')||traceIs(d0,'noMultiCategory')};// To not confuse 2D x/y used for per-box sample points for multicategory coordinates\nif(d0.type==='box'&&d0._hasPreCompStats&&axLetter==={h:'x',v:'y'}[d0.orientation||'v']){opts.noMultiCategory=true;}// check all boxes on this x axis to see\n// if they're dates, numbers, or categories\nif(isBoxWithoutPositionCoords(d0,axLetter)){var posLetter=getBoxPosLetter(d0);var boxPositions=[];for(i=0;i0&&(trace['_'+axLetter+'axes']||{})[id]){return trace;}if((trace[axLetter+'axis']||axLetter)===id){if(isBoxWithoutPositionCoords(trace,axLetter)){return trace;}else if((trace[axLetter]||[]).length||trace[axLetter+'0']){return trace;}}}}function getBoxPosLetter(trace){return{v:'x',h:'y'}[trace.orientation||'v'];}function isBoxWithoutPositionCoords(trace,axLetter){var posLetter=getBoxPosLetter(trace);var isBox=traceIs(trace,'box-violin');var isCandlestick=traceIs(trace._fullInput||{},'candlestick');return isBox&&!isCandlestick&&axLetter===posLetter&&trace[posLetter]===undefined&&trace[posLetter+'0']===undefined;}},{\"../../registry\":866,\"./axis_autotype\":784}],809:[function(_dereq_,module,exports){/**\n* Copyright 2012-2020, Plotly, Inc.\n* All rights reserved.\n*\n* This source code is licensed under the MIT license found in the\n* LICENSE file in the root directory of this source tree.\n*/'use strict';var Registry=_dereq_('../registry');var Lib=_dereq_('../lib');/*\n * Create or update an observer. This function is designed to be\n * idempotent so that it can be called over and over as the component\n * updates, and will attach and detach listeners as needed.\n *\n * @param {optional object} container\n * An object on which the observer is stored. This is the mechanism\n * by which it is idempotent. If it already exists, another won't be\n * added. Each time it's called, the value lookup table is updated.\n * @param {array} commandList\n * An array of commands, following either `buttons` of `updatemenus`\n * or `steps` of `sliders`.\n * @param {function} onchange\n * A listener called when the value is changed. Receives data object\n * with information about the new state.\n */exports.manageCommandObserver=function(gd,container,commandList,onchange){var ret={};var enabled=true;if(container&&container._commandObserver){ret=container._commandObserver;}if(!ret.cache){ret.cache={};}// Either create or just recompute this:\nret.lookupTable={};var binding=exports.hasSimpleAPICommandBindings(gd,commandList,ret.lookupTable);if(container&&container._commandObserver){if(!binding){// If container exists and there are no longer any bindings,\n// remove existing:\nif(container._commandObserver.remove){container._commandObserver.remove();container._commandObserver=null;return ret;}}else{// If container exists and there *are* bindings, then the lookup\n// table should have been updated and check is already attached,\n// so there's nothing to be done:\nreturn ret;}}// Determine whether there's anything to do for this binding:\nif(binding){// Build the cache:\nbindingValueHasChanged(gd,binding,ret.cache);ret.check=function check(){if(!enabled)return;var update=bindingValueHasChanged(gd,binding,ret.cache);if(update.changed&&onchange){// Disable checks for the duration of this command in order to avoid\n// infinite loops:\nif(ret.lookupTable[update.value]!==undefined){ret.disable();Promise.resolve(onchange({value:update.value,type:binding.type,prop:binding.prop,traces:binding.traces,index:ret.lookupTable[update.value]})).then(ret.enable,ret.enable);}}return update.changed;};var checkEvents=['plotly_relayout','plotly_redraw','plotly_restyle','plotly_update','plotly_animatingframe','plotly_afterplot'];for(var i=0;i0?'.':'')+attrName;if(Lib.isPlainObject(attr)){crawl(attr,callback,thisPath,depth+1);}else{// Only execute the callback on leaf nodes:\ncallback(thisPath,attrName,attr);}});}},{\"../lib\":735,\"../registry\":866}],810:[function(_dereq_,module,exports){/**\n* Copyright 2012-2020, Plotly, Inc.\n* All rights reserved.\n*\n* This source code is licensed under the MIT license found in the\n* LICENSE file in the root directory of this source tree.\n*/'use strict';var extendFlat=_dereq_('../lib/extend').extendFlat;/**\n * Make a xy domain attribute group\n *\n * @param {object} opts\n * @param {string}\n * opts.name: name to be inserted in the default description\n * @param {boolean}\n * opts.trace: set to true for trace containers\n * @param {string}\n * opts.editType: editType for all pieces\n * @param {boolean}\n * opts.noGridCell: set to true to omit `row` and `column`\n *\n * @param {object} extra\n * @param {string}\n * extra.description: extra description. N.B we use\n * a separate extra container to make it compatible with\n * the compress_attributes transform.\n *\n * @return {object} attributes object containing {x,y} as specified\n */exports.attributes=function(opts,extra){opts=opts||{};extra=extra||{};var base={valType:'info_array',editType:opts.editType,items:[{valType:'number',min:0,max:1,editType:opts.editType},{valType:'number',min:0,max:1,editType:opts.editType}],dflt:[0,1]};var namePart=opts.name?opts.name+' ':'';var contPart=opts.trace?'trace ':'subplot ';var descPart=extra.description?' '+extra.description:'';var out={x:extendFlat({},base,{}),y:extendFlat({},base,{}),editType:opts.editType};if(!opts.noGridCell){out.row={valType:'integer',min:0,dflt:0,editType:opts.editType};out.column={valType:'integer',min:0,dflt:0,editType:opts.editType};}return out;};exports.defaults=function(containerOut,layout,coerce,dfltDomains){var dfltX=dfltDomains&&dfltDomains.x||[0,1];var dfltY=dfltDomains&&dfltDomains.y||[0,1];var grid=layout.grid;if(grid){var column=coerce('domain.column');if(column!==undefined){if(column0){trace._module.calcGeoJSON(calcTrace,fullLayout);}}var hasInvalidBounds=this.updateProjection(geoCalcData,fullLayout);if(hasInvalidBounds)return;if(!this.viewInitial||this.scope!==geoLayout.scope){this.saveViewInitial(geoLayout);}this.scope=geoLayout.scope;this.updateBaseLayers(fullLayout,geoLayout);this.updateDims(fullLayout,geoLayout);this.updateFx(fullLayout,geoLayout);Plots.generalUpdatePerTraceModule(this.graphDiv,this,geoCalcData,geoLayout);var scatterLayer=this.layers.frontplot.select('.scatterlayer');this.dataPoints.point=scatterLayer.selectAll('.point');this.dataPoints.text=scatterLayer.selectAll('text');this.dataPaths.line=scatterLayer.selectAll('.js-line');var choroplethLayer=this.layers.backplot.select('.choroplethlayer');this.dataPaths.choropleth=choroplethLayer.selectAll('path');this.render();};proto.updateProjection=function(geoCalcData,fullLayout){var gd=this.graphDiv;var geoLayout=fullLayout[this.id];var gs=fullLayout._size;var domain=geoLayout.domain;var projLayout=geoLayout.projection;var lonaxis=geoLayout.lonaxis;var lataxis=geoLayout.lataxis;var axLon=lonaxis._ax;var axLat=lataxis._ax;var projection=this.projection=getProjection(geoLayout);// setup subplot extent [[x0,y0], [x1,y1]]\nvar extent=[[gs.l+gs.w*domain.x[0],gs.t+gs.h*(1-domain.y[1])],[gs.l+gs.w*domain.x[1],gs.t+gs.h*(1-domain.y[0])]];var center=geoLayout.center||{};var rotation=projLayout.rotation||{};var lonaxisRange=lonaxis.range||[];var lataxisRange=lataxis.range||[];if(geoLayout.fitbounds){axLon._length=extent[1][0]-extent[0][0];axLat._length=extent[1][1]-extent[0][1];axLon.range=getAutoRange(gd,axLon);axLat.range=getAutoRange(gd,axLat);var midLon=(axLon.range[0]+axLon.range[1])/2;var midLat=(axLat.range[0]+axLat.range[1])/2;if(geoLayout._isScoped){center={lon:midLon,lat:midLat};}else if(geoLayout._isClipped){center={lon:midLon,lat:midLat};rotation={lon:midLon,lat:midLat,roll:rotation.roll};var projType=projLayout.type;var lonHalfSpan=constants.lonaxisSpan[projType]/2||180;var latHalfSpan=constants.lataxisSpan[projType]/2||180;lonaxisRange=[midLon-lonHalfSpan,midLon+lonHalfSpan];lataxisRange=[midLat-latHalfSpan,midLat+latHalfSpan];}else{center={lon:midLon,lat:midLat};rotation={lon:midLon,lat:rotation.lat,roll:rotation.roll};}}// set 'pre-fit' projection\nprojection.center([center.lon-rotation.lon,center.lat-rotation.lat]).rotate([-rotation.lon,-rotation.lat,rotation.roll]).parallels(projLayout.parallels);// fit projection 'scale' and 'translate' to set lon/lat ranges\nvar rangeBox=makeRangeBox(lonaxisRange,lataxisRange);projection.fitExtent(extent,rangeBox);var b=this.bounds=projection.getBounds(rangeBox);var s=this.fitScale=projection.scale();var t=projection.translate();if(!isFinite(b[0][0])||!isFinite(b[0][1])||!isFinite(b[1][0])||!isFinite(b[1][1])||isNaN(t[0])||isNaN(t[0])){var attrToUnset=['fitbounds','projection.rotation','center','lonaxis.range','lataxis.range'];var msg='Invalid geo settings, relayout\\'ing to default view.';var updateObj={};// clear all attributes that could cause invalid bounds,\n// clear viewInitial to update reset-view behavior\nfor(var i=0;i-1){selectOnClick(d3.event,gd,[_this.xaxis],[_this.yaxis],_this.id,dragOptions);}if(clickMode.indexOf('event')>-1){// TODO: like pie and mapbox, this doesn't support right-click\n// actually this one is worse, as right-click starts a pan, or leaves\n// select in a weird state.\n// Also, only tangentially related, we should cancel hover during pan\nFx.click(gd,d3.event);}}});};proto.makeFramework=function(){var _this=this;var gd=_this.graphDiv;var fullLayout=gd._fullLayout;var clipId='clip'+fullLayout._uid+_this.id;_this.clipDef=fullLayout._clips.append('clipPath').attr('id',clipId);_this.clipRect=_this.clipDef.append('rect');_this.framework=d3.select(_this.container).append('g').attr('class','geo '+_this.id).call(Drawing.setClipUrl,clipId,gd);// sane lonlat to px\n_this.project=function(v){var px=_this.projection(v);return px?[px[0]-_this.xaxis._offset,px[1]-_this.yaxis._offset]:[null,null];};_this.xaxis={_id:'x',c2p:function(v){return _this.project(v)[0];}};_this.yaxis={_id:'y',c2p:function(v){return _this.project(v)[1];}};// mock axis for hover formatting\n_this.mockAxis={type:'linear',showexponent:'all',exponentformat:'B'};Axes.setConvert(_this.mockAxis,fullLayout);};proto.saveViewInitial=function(geoLayout){var center=geoLayout.center||{};var projLayout=geoLayout.projection;var rotation=projLayout.rotation||{};this.viewInitial={'fitbounds':geoLayout.fitbounds,'projection.scale':projLayout.scale};var extra;if(geoLayout._isScoped){extra={'center.lon':center.lon,'center.lat':center.lat};}else if(geoLayout._isClipped){extra={'projection.rotation.lon':rotation.lon,'projection.rotation.lat':rotation.lat};}else{extra={'center.lon':center.lon,'center.lat':center.lat,'projection.rotation.lon':rotation.lon};}Lib.extendFlat(this.viewInitial,extra);};// [hot code path] (re)draw all paths which depend on the projection\nproto.render=function(){var projection=this.projection;var pathFn=projection.getPath();var k;function translatePoints(d){var lonlatPx=projection(d.lonlat);return lonlatPx?'translate('+lonlatPx[0]+','+lonlatPx[1]+')':null;}function hideShowPoints(d){return projection.isLonLatOverEdges(d.lonlat)?'none':null;}for(k in this.basePaths){this.basePaths[k].attr('d',pathFn);}for(k in this.dataPaths){this.dataPaths[k].attr('d',function(d){return pathFn(d.geojson);});}for(k in this.dataPoints){this.dataPoints[k].attr('display',hideShowPoints).attr('transform',translatePoints);}};// Helper that wraps d3.geo[/* projection name /*]() which:\n//\n// - adds 'fitExtent' (available in d3 v4)\n// - adds 'getPath', 'getBounds' convenience methods\n// - scopes logic related to 'clipAngle'\n// - adds 'isLonLatOverEdges' method\n// - sets projection precision\n// - sets methods that aren't always defined depending\n// on the projection type to a dummy 'd3-esque' function,\n//\n// This wrapper alleviates subsequent code of (many) annoying if-statements.\nfunction getProjection(geoLayout){var projLayout=geoLayout.projection;var projType=projLayout.type;var projection=d3.geo[constants.projNames[projType]]();var clipAngle=geoLayout._isClipped?constants.lonaxisSpan[projType]/2:null;var methods=['center','rotate','parallels','clipExtent'];var dummyFn=function(_){return _?projection:[];};for(var i=0;imaxAngle;}else{return false;}};projection.getPath=function(){return d3.geo.path().projection(projection);};projection.getBounds=function(object){return projection.getPath().bounds(object);};// adapted from d3 v4:\n// https://github.com/d3/d3-geo/blob/master/src/projection/fit.js\nprojection.fitExtent=function(extent,object){var w=extent[1][0]-extent[0][0];var h=extent[1][1]-extent[0][1];var clip=projection.clipExtent&&projection.clipExtent();projection.scale(150).translate([0,0]);if(clip)projection.clipExtent(null);var b=projection.getBounds(object);var k=Math.min(w/(b[1][0]-b[0][0]),h/(b[1][1]-b[0][1]));var x=+extent[0][0]+(w-k*(b[1][0]+b[0][0]))/2;var y=+extent[0][1]+(h-k*(b[1][1]+b[0][1]))/2;if(clip)projection.clipExtent(clip);return projection.scale(k*150).translate([x,y]);};projection.precision(constants.precision);if(clipAngle){projection.clipAngle(clipAngle-constants.clipPad);}return projection;}function makeGraticule(axisName,geoLayout,fullLayout){// equivalent to the d3 \"ε\"\nvar epsilon=1e-6;// same as the geoGraticule default\nvar precision=2.5;var axLayout=geoLayout[axisName];var scopeDefaults=constants.scopeDefaults[geoLayout.scope];var rng;var oppRng;var coordFn;if(axisName==='lonaxis'){rng=scopeDefaults.lonaxisRange;oppRng=scopeDefaults.lataxisRange;coordFn=function(v,l){return[v,l];};}else if(axisName==='lataxis'){rng=scopeDefaults.lataxisRange;oppRng=scopeDefaults.lonaxisRange;coordFn=function(v,l){return[l,v];};}var dummyAx={type:'linear',range:[rng[0],rng[1]-epsilon],tick0:axLayout.tick0,dtick:axLayout.dtick};Axes.setConvert(dummyAx,fullLayout);var vals=Axes.calcTicks(dummyAx);// remove duplicate on antimeridian\nif(!geoLayout.isScoped&&axisName==='lonaxis'){vals.pop();}var len=vals.length;var coords=new Array(len);for(var i=0;i0&&lon1<0)lon1+=360;var dlon4=(lon1-lon0)/4;return{type:'Polygon',coordinates:[[[lon0,lat0],[lon0,lat1],[lon0+dlon4,lat1],[lon0+2*dlon4,lat1],[lon0+3*dlon4,lat1],[lon1,lat1],[lon1,lat0],[lon1-dlon4,lat0],[lon1-2*dlon4,lat0],[lon1-3*dlon4,lat0],[lon0,lat0]]]};}},{\"../../components/color\":602,\"../../components/dragelement\":621,\"../../components/drawing\":624,\"../../components/fx\":642,\"../../lib\":735,\"../../lib/geo_location_utils\":729,\"../../lib/topojson_utils\":762,\"../../registry\":866,\"../cartesian/autorange\":782,\"../cartesian/axes\":783,\"../cartesian/select\":802,\"../plots\":846,\"./constants\":813,\"./projections\":818,\"./zoom\":819,\"d3\":165,\"topojson-client\":538}],815:[function(_dereq_,module,exports){/**\n* Copyright 2012-2020, Plotly, Inc.\n* All rights reserved.\n*\n* This source code is licensed under the MIT license found in the\n* LICENSE file in the root directory of this source tree.\n*/'use strict';var getSubplotCalcData=_dereq_('../../plots/get_data').getSubplotCalcData;var counterRegex=_dereq_('../../lib').counterRegex;var createGeo=_dereq_('./geo');var GEO='geo';var counter=counterRegex(GEO);var attributes={};attributes[GEO]={valType:'subplotid',dflt:GEO,editType:'calc'};function plotGeo(gd){var fullLayout=gd._fullLayout;var calcData=gd.calcdata;var geoIds=fullLayout._subplots[GEO];for(var i=0;i0&&lon1<0)lon1+=360;var centerLon=(lon0+lon1)/2;var projLon;if(!isAlbersUsa){var dfltProjRotate=isScoped?scopeParams.projRotate:[centerLon,0,0];projLon=coerce('projection.rotation.lon',dfltProjRotate[0]);coerce('projection.rotation.lat',dfltProjRotate[1]);coerce('projection.rotation.roll',dfltProjRotate[2]);show=coerce('showcoastlines',!isScoped&&visible);if(show){coerce('coastlinecolor');coerce('coastlinewidth');}show=coerce('showocean',!visible?false:undefined);if(show)coerce('oceancolor');}var centerLonDflt;var centerLatDflt;if(isAlbersUsa){// 'albers usa' does not have a 'center',\n// these values were found using via:\n// projection.invert([geoLayout.center.lon, geoLayoutIn.center.lat])\ncenterLonDflt=-96.6;centerLatDflt=38.7;}else{centerLonDflt=isScoped?centerLon:projLon;centerLatDflt=(latRange[0]+latRange[1])/2;}coerce('center.lon',centerLonDflt);coerce('center.lat',centerLatDflt);if(isConic){var dfltProjParallels=scopeParams.projParallels||[0,60];coerce('projection.parallels',dfltProjParallels);}coerce('projection.scale');show=coerce('showland',!visible?false:undefined);if(show)coerce('landcolor');show=coerce('showlakes',!visible?false:undefined);if(show)coerce('lakecolor');show=coerce('showrivers',!visible?false:undefined);if(show){coerce('rivercolor');coerce('riverwidth');}show=coerce('showcountries',isScoped&&scope!=='usa'&&visible);if(show){coerce('countrycolor');coerce('countrywidth');}if(scope==='usa'||scope==='north america'&&resolution===50){// Only works for:\n// USA states at 110m\n// USA states + Canada provinces at 50m\ncoerce('showsubunits',visible);coerce('subunitcolor');coerce('subunitwidth');}if(!isScoped){// Does not work in non-world scopes\nshow=coerce('showframe',visible);if(show){coerce('framecolor');coerce('framewidth');}}coerce('bgcolor');var fitBounds=coerce('fitbounds');// clear attributes that will get auto-filled later\nif(fitBounds){delete geoLayoutOut.projection.scale;if(isScoped){delete geoLayoutOut.center.lon;delete geoLayoutOut.center.lat;}else if(isClipped){delete geoLayoutOut.center.lon;delete geoLayoutOut.center.lat;delete geoLayoutOut.projection.rotation.lon;delete geoLayoutOut.projection.rotation.lat;delete geoLayoutOut.lonaxis.range;delete geoLayoutOut.lataxis.range;}else{delete geoLayoutOut.center.lon;delete geoLayoutOut.center.lat;delete geoLayoutOut.projection.rotation.lon;}}}},{\"../../lib\":735,\"../get_data\":820,\"../subplot_defaults\":860,\"./constants\":813,\"./layout_attributes\":816}],818:[function(_dereq_,module,exports){/**\n* Copyright 2012-2020, Plotly, Inc.\n* All rights reserved.\n*\n* This source code is licensed under the MIT license found in the\n* LICENSE file in the root directory of this source tree.\n*/ /*\n * Generated by https://github.com/etpinard/d3-geo-projection-picker\n *\n * which is hand-picks projection from https://github.com/d3/d3-geo-projection\n *\n * into a CommonJS require-able module.\n */'use strict';/* eslint-disable */function addProjectionsToD3(d3){d3.geo.project=function(object,projection){var stream=projection.stream;if(!stream)throw new Error(\"not yet supported\");return(object&&d3_geo_projectObjectType.hasOwnProperty(object.type)?d3_geo_projectObjectType[object.type]:d3_geo_projectGeometry)(object,stream);};function d3_geo_projectFeature(object,stream){return{type:\"Feature\",id:object.id,properties:object.properties,geometry:d3_geo_projectGeometry(object.geometry,stream)};}function d3_geo_projectGeometry(geometry,stream){if(!geometry)return null;if(geometry.type===\"GeometryCollection\")return{type:\"GeometryCollection\",geometries:object.geometries.map(function(geometry){return d3_geo_projectGeometry(geometry,stream);})};if(!d3_geo_projectGeometryType.hasOwnProperty(geometry.type))return null;var sink=d3_geo_projectGeometryType[geometry.type];d3.geo.stream(geometry,stream(sink));return sink.result();}var d3_geo_projectObjectType={Feature:d3_geo_projectFeature,FeatureCollection:function(object,stream){return{type:\"FeatureCollection\",features:object.features.map(function(feature){return d3_geo_projectFeature(feature,stream);})};}};var d3_geo_projectPoints=[],d3_geo_projectLines=[];var d3_geo_projectPoint={point:function(x,y){d3_geo_projectPoints.push([x,y]);},result:function(){var result=!d3_geo_projectPoints.length?null:d3_geo_projectPoints.length<2?{type:\"Point\",coordinates:d3_geo_projectPoints[0]}:{type:\"MultiPoint\",coordinates:d3_geo_projectPoints};d3_geo_projectPoints=[];return result;}};var d3_geo_projectLine={lineStart:d3_geo_projectNoop,point:function(x,y){d3_geo_projectPoints.push([x,y]);},lineEnd:function(){if(d3_geo_projectPoints.length)d3_geo_projectLines.push(d3_geo_projectPoints),d3_geo_projectPoints=[];},result:function(){var result=!d3_geo_projectLines.length?null:d3_geo_projectLines.length<2?{type:\"LineString\",coordinates:d3_geo_projectLines[0]}:{type:\"MultiLineString\",coordinates:d3_geo_projectLines};d3_geo_projectLines=[];return result;}};var d3_geo_projectPolygon={polygonStart:d3_geo_projectNoop,lineStart:d3_geo_projectNoop,point:function(x,y){d3_geo_projectPoints.push([x,y]);},lineEnd:function(){var n=d3_geo_projectPoints.length;if(n){do d3_geo_projectPoints.push(d3_geo_projectPoints[0].slice());while(++n<4);d3_geo_projectLines.push(d3_geo_projectPoints),d3_geo_projectPoints=[];}},polygonEnd:d3_geo_projectNoop,result:function(){if(!d3_geo_projectLines.length)return null;var polygons=[],holes=[];d3_geo_projectLines.forEach(function(ring){if(d3_geo_projectClockwise(ring))polygons.push([ring]);else holes.push(ring);});holes.forEach(function(hole){var point=hole[0];polygons.some(function(polygon){if(d3_geo_projectContains(polygon[0],point)){polygon.push(hole);return true;}})||polygons.push([hole]);});d3_geo_projectLines=[];return!polygons.length?null:polygons.length>1?{type:\"MultiPolygon\",coordinates:polygons}:{type:\"Polygon\",coordinates:polygons[0]};}};var d3_geo_projectGeometryType={Point:d3_geo_projectPoint,MultiPoint:d3_geo_projectPoint,LineString:d3_geo_projectLine,MultiLineString:d3_geo_projectLine,Polygon:d3_geo_projectPolygon,MultiPolygon:d3_geo_projectPolygon,Sphere:d3_geo_projectPolygon};function d3_geo_projectNoop(){}function d3_geo_projectClockwise(ring){if((n=ring.length)<4)return false;var i=0,n,area=ring[n-1][1]*ring[0][0]-ring[n-1][0]*ring[0][1];while(++iy^yj>y&&x<(xj-xi)*(y-yi)/(yj-yi)+xi)contains=!contains;}return contains;}var ε=1e-6,ε2=ε*ε,π=Math.PI,halfπ=π/2,sqrtπ=Math.sqrt(π),radians=π/180,degrees=180/π;function sinci(x){return x?x/Math.sin(x):1;}function sgn(x){return x>0?1:x<0?-1:0;}function asin(x){return x>1?halfπ:x<-1?-halfπ:Math.asin(x);}function acos(x){return x>1?0:x<-1?π:Math.acos(x);}function asqrt(x){return x>0?Math.sqrt(x):0;}var projection=d3.geo.projection,projectionMutator=d3.geo.projectionMutator;d3.geo.interrupt=function(project){var lobes=[[[[-π,0],[0,halfπ],[π,0]]],[[[-π,0],[0,-halfπ],[π,0]]]];var bounds;function forward(λ,φ){var sign=φ<0?-1:+1,hemilobes=lobes[+(φ<0)];for(var i=0,n=hemilobes.length-1;ihemilobes[i][2][0];++i);var coordinates=project(λ-hemilobes[i][1][0],φ);coordinates[0]+=project(hemilobes[i][1][0],sign*φ>sign*hemilobes[i][0][1]?hemilobes[i][0][1]:φ)[0];return coordinates;}function reset(){bounds=lobes.map(function(hemilobes){return hemilobes.map(function(lobe){var x0=project(lobe[0][0],lobe[0][1])[0],x1=project(lobe[2][0],lobe[2][1])[0],y0=project(lobe[1][0],lobe[0][1])[1],y1=project(lobe[1][0],lobe[1][1])[1],t;if(y0>y1)t=y0,y0=y1,y1=t;return[[x0,y0],[x1,y1]];});});}if(project.invert)forward.invert=function(x,y){var hemibounds=bounds[+(y<0)],hemilobes=lobes[+(y<0)];for(var i=0,n=hemibounds.length;i=0;--i){var lobe=lobes[1][i],λ0=lobe[0][0]*180/π,φ0=lobe[0][1]*180/π,φ1=lobe[1][1]*180/π,λ2=lobe[2][0]*180/π,φ2=lobe[2][1]*180/π;coordinates.push(resample([[λ2-ε,φ2-ε],[λ2-ε,φ1+ε],[λ0+ε,φ1+ε],[λ0+ε,φ0-ε]],30));}return{type:\"Polygon\",coordinates:[d3.merge(coordinates)]};}function resample(coordinates,m){var i=-1,n=coordinates.length,p0=coordinates[0],p1,dx,dy,resampled=[];while(++iε;i++){var cosφ=Math.cos(φ);φ-=δ=(φ+Math.sin(φ)*(cosφ+2)-k)/(2*cosφ*(1+cosφ));}return[2/Math.sqrt(π*(4+π))*λ*(1+Math.cos(φ)),2*Math.sqrt(π/(4+π))*Math.sin(φ)];}eckert4.invert=function(x,y){var A=.5*y*Math.sqrt((4+π)/π),k=asin(A),c=Math.cos(k);return[x/(2/Math.sqrt(π*(4+π))*(1+c)),asin((k+A*(c+2))/(2+halfπ))];};(d3.geo.eckert4=function(){return projection(eckert4);}).raw=eckert4;var hammerAzimuthalEqualArea=d3.geo.azimuthalEqualArea.raw;function hammer(A,B){if(arguments.length<2)B=A;if(B===1)return hammerAzimuthalEqualArea;if(B===Infinity)return hammerQuarticAuthalic;function forward(λ,φ){var coordinates=hammerAzimuthalEqualArea(λ/B,φ);coordinates[0]*=A;return coordinates;}forward.invert=function(x,y){var coordinates=hammerAzimuthalEqualArea.invert(x/A,y);coordinates[0]*=B;return coordinates;};return forward;}function hammerProjection(){var B=2,m=projectionMutator(hammer),p=m(B);p.coefficient=function(_){if(!arguments.length)return B;return m(B=+_);};return p;}function hammerQuarticAuthalic(λ,φ){return[λ*Math.cos(φ)/Math.cos(φ/=2),2*Math.sin(φ)];}hammerQuarticAuthalic.invert=function(x,y){var φ=2*asin(y/2);return[x*Math.cos(φ/2)/Math.cos(φ),φ];};(d3.geo.hammer=hammerProjection).raw=hammer;function kavrayskiy7(λ,φ){return[3*λ/(2*π)*Math.sqrt(π*π/3-φ*φ),φ];}kavrayskiy7.invert=function(x,y){return[2/3*π*x/Math.sqrt(π*π/3-y*y),y];};(d3.geo.kavrayskiy7=function(){return projection(kavrayskiy7);}).raw=kavrayskiy7;function miller(λ,φ){return[λ,1.25*Math.log(Math.tan(π/4+.4*φ))];}miller.invert=function(x,y){return[x,2.5*Math.atan(Math.exp(.8*y))-.625*π];};(d3.geo.miller=function(){return projection(miller);}).raw=miller;function mollweideBromleyθ(Cp){return function(θ){var Cpsinθ=Cp*Math.sin(θ),i=30,δ;do θ-=δ=(θ+Math.sin(θ)-Cpsinθ)/(1+Math.cos(θ));while(Math.abs(δ)>ε&&--i>0);return θ/2;};}function mollweideBromley(Cx,Cy,Cp){var θ=mollweideBromleyθ(Cp);function forward(λ,φ){return[Cx*λ*Math.cos(φ=θ(φ)),Cy*Math.sin(φ)];}forward.invert=function(x,y){var θ=asin(y/Cy);return[x/(Cx*Math.cos(θ)),asin((2*θ+Math.sin(2*θ))/Cp)];};return forward;}var mollweideθ=mollweideBromleyθ(π),mollweide=mollweideBromley(Math.SQRT2/halfπ,Math.SQRT2,π);(d3.geo.mollweide=function(){return projection(mollweide);}).raw=mollweide;function naturalEarth(λ,φ){var φ2=φ*φ,φ4=φ2*φ2;return[λ*(.8707-.131979*φ2+φ4*(-.013791+φ4*(.003971*φ2-.001529*φ4))),φ*(1.007226+φ2*(.015085+φ4*(-.044475+.028874*φ2-.005916*φ4)))];}naturalEarth.invert=function(x,y){var φ=y,i=25,δ;do{var φ2=φ*φ,φ4=φ2*φ2;φ-=δ=(φ*(1.007226+φ2*(.015085+φ4*(-.044475+.028874*φ2-.005916*φ4)))-y)/(1.007226+φ2*(.015085*3+φ4*(-.044475*7+.028874*9*φ2-.005916*11*φ4)));}while(Math.abs(δ)>ε&&--i>0);return[x/(.8707+(φ2=φ*φ)*(-.131979+φ2*(-.013791+φ2*φ2*φ2*(.003971-.001529*φ2)))),φ];};(d3.geo.naturalEarth=function(){return projection(naturalEarth);}).raw=naturalEarth;var robinsonConstants=[[.9986,-.062],[1,0],[.9986,.062],[.9954,.124],[.99,.186],[.9822,.248],[.973,.31],[.96,.372],[.9427,.434],[.9216,.4958],[.8962,.5571],[.8679,.6176],[.835,.6769],[.7986,.7346],[.7597,.7903],[.7186,.8435],[.6732,.8936],[.6213,.9394],[.5722,.9761],[.5322,1]];robinsonConstants.forEach(function(d){d[1]*=1.0144;});function robinson(λ,φ){var i=Math.min(18,Math.abs(φ)*36/π),i0=Math.floor(i),di=i-i0,ax=(k=robinsonConstants[i0])[0],ay=k[1],bx=(k=robinsonConstants[++i0])[0],by=k[1],cx=(k=robinsonConstants[Math.min(19,++i0)])[0],cy=k[1],k;return[λ*(bx+di*(cx-ax)/2+di*di*(cx-2*bx+ax)/2),(φ>0?halfπ:-halfπ)*(by+di*(cy-ay)/2+di*di*(cy-2*by+ay)/2)];}robinson.invert=function(x,y){var yy=y/halfπ,φ=yy*90,i=Math.min(18,Math.abs(φ/5)),i0=Math.max(0,Math.floor(i));do{var ay=robinsonConstants[i0][1],by=robinsonConstants[i0+1][1],cy=robinsonConstants[Math.min(19,i0+2)][1],u=cy-ay,v=cy-2*by+ay,t=2*(Math.abs(yy)-by)/u,c=v/u,di=t*(1-c*t*(1-2*c*t));if(di>=0||i0===1){φ=(y>=0?5:-5)*(di+i);var j=50,δ;do{i=Math.min(18,Math.abs(φ)/5);i0=Math.floor(i);di=i-i0;ay=robinsonConstants[i0][1];by=robinsonConstants[i0+1][1];cy=robinsonConstants[Math.min(19,i0+2)][1];φ-=(δ=(y>=0?halfπ:-halfπ)*(by+di*(cy-ay)/2+di*di*(cy-2*by+ay)/2)-y)*degrees;}while(Math.abs(δ)>ε2&&--j>0);break;}}while(--i0>=0);var ax=robinsonConstants[i0][0],bx=robinsonConstants[i0+1][0],cx=robinsonConstants[Math.min(19,i0+2)][0];return[x/(bx+di*(cx-ax)/2+di*di*(cx-2*bx+ax)/2),φ*radians];};(d3.geo.robinson=function(){return projection(robinson);}).raw=robinson;function sinusoidal(λ,φ){return[λ*Math.cos(φ),φ];}sinusoidal.invert=function(x,y){return[x/Math.cos(y),y];};(d3.geo.sinusoidal=function(){return projection(sinusoidal);}).raw=sinusoidal;function aitoff(λ,φ){var cosφ=Math.cos(φ),sinciα=sinci(acos(cosφ*Math.cos(λ/=2)));return[2*cosφ*Math.sin(λ)*sinciα,Math.sin(φ)*sinciα];}aitoff.invert=function(x,y){if(x*x+4*y*y>π*π+ε)return;var λ=x,φ=y,i=25;do{var sinλ=Math.sin(λ),sinλ_2=Math.sin(λ/2),cosλ_2=Math.cos(λ/2),sinφ=Math.sin(φ),cosφ=Math.cos(φ),sin_2φ=Math.sin(2*φ),sin2φ=sinφ*sinφ,cos2φ=cosφ*cosφ,sin2λ_2=sinλ_2*sinλ_2,C=1-cos2φ*cosλ_2*cosλ_2,E=C?acos(cosφ*cosλ_2)*Math.sqrt(F=1/C):F=0,F,fx=2*E*cosφ*sinλ_2-x,fy=E*sinφ-y,δxδλ=F*(cos2φ*sin2λ_2+E*cosφ*cosλ_2*sin2φ),δxδφ=F*(.5*sinλ*sin_2φ-E*2*sinφ*sinλ_2),δyδλ=F*.25*(sin_2φ*sinλ_2-E*sinφ*cos2φ*sinλ),δyδφ=F*(sin2φ*cosλ_2+E*sin2λ_2*cosφ),denominator=δxδφ*δyδλ-δyδφ*δxδλ;if(!denominator)break;var δλ=(fy*δxδφ-fx*δyδφ)/denominator,δφ=(fx*δyδλ-fy*δxδλ)/denominator;λ-=δλ,φ-=δφ;}while((Math.abs(δλ)>ε||Math.abs(δφ)>ε)&&--i>0);return[λ,φ];};(d3.geo.aitoff=function(){return projection(aitoff);}).raw=aitoff;function winkel3(λ,φ){var coordinates=aitoff(λ,φ);return[(coordinates[0]+λ/halfπ)/2,(coordinates[1]+φ)/2];}winkel3.invert=function(x,y){var λ=x,φ=y,i=25;do{var cosφ=Math.cos(φ),sinφ=Math.sin(φ),sin_2φ=Math.sin(2*φ),sin2φ=sinφ*sinφ,cos2φ=cosφ*cosφ,sinλ=Math.sin(λ),cosλ_2=Math.cos(λ/2),sinλ_2=Math.sin(λ/2),sin2λ_2=sinλ_2*sinλ_2,C=1-cos2φ*cosλ_2*cosλ_2,E=C?acos(cosφ*cosλ_2)*Math.sqrt(F=1/C):F=0,F,fx=.5*(2*E*cosφ*sinλ_2+λ/halfπ)-x,fy=.5*(E*sinφ+φ)-y,δxδλ=.5*F*(cos2φ*sin2λ_2+E*cosφ*cosλ_2*sin2φ)+.5/halfπ,δxδφ=F*(sinλ*sin_2φ/4-E*sinφ*sinλ_2),δyδλ=.125*F*(sin_2φ*sinλ_2-E*sinφ*cos2φ*sinλ),δyδφ=.5*F*(sin2φ*cosλ_2+E*sin2λ_2*cosφ)+.5,denominator=δxδφ*δyδλ-δyδφ*δxδλ,δλ=(fy*δxδφ-fx*δyδφ)/denominator,δφ=(fx*δyδλ-fy*δxδλ)/denominator;λ-=δλ,φ-=δφ;}while((Math.abs(δλ)>ε||Math.abs(δφ)>ε)&&--i>0);return[λ,φ];};(d3.geo.winkel3=function(){return projection(winkel3);}).raw=winkel3;}module.exports=addProjectionsToD3;},{}],819:[function(_dereq_,module,exports){/**\n* Copyright 2012-2020, Plotly, Inc.\n* All rights reserved.\n*\n* This source code is licensed under the MIT license found in the\n* LICENSE file in the root directory of this source tree.\n*/'use strict';var d3=_dereq_('d3');var Lib=_dereq_('../../lib');var Registry=_dereq_('../../registry');var radians=Math.PI/180;var degrees=180/Math.PI;var zoomstartStyle={cursor:'pointer'};var zoomendStyle={cursor:'auto'};function createGeoZoom(geo,geoLayout){var projection=geo.projection;var zoomConstructor;if(geoLayout._isScoped){zoomConstructor=zoomScoped;}else if(geoLayout._isClipped){zoomConstructor=zoomClipped;}else{zoomConstructor=zoomNonClipped;}// TODO add a conic-specific zoom\nreturn zoomConstructor(geo,projection);}module.exports=createGeoZoom;// common to all zoom types\nfunction initZoom(geo,projection){return d3.behavior.zoom().translate(projection.translate()).scale(projection.scale());}// sync zoom updates with user & full layout\nfunction sync(geo,projection,cb){var id=geo.id;var gd=geo.graphDiv;var layout=gd.layout;var userOpts=layout[id];var fullLayout=gd._fullLayout;var fullOpts=fullLayout[id];var preGUI={};var eventData={};function set(propStr,val){preGUI[id+'.'+propStr]=Lib.nestedProperty(userOpts,propStr).get();Registry.call('_storeDirectGUIEdit',layout,fullLayout._preGUI,preGUI);var fullNp=Lib.nestedProperty(fullOpts,propStr);if(fullNp.get()!==val){fullNp.set(val);Lib.nestedProperty(userOpts,propStr).set(val);eventData[id+'.'+propStr]=val;}}cb(set);set('projection.scale',projection.scale()/geo.fitScale);set('fitbounds',false);gd.emit('plotly_relayout',eventData);}// zoom for scoped projections\nfunction zoomScoped(geo,projection){var zoom=initZoom(geo,projection);function handleZoomstart(){d3.select(this).style(zoomstartStyle);}function handleZoom(){projection.scale(d3.event.scale).translate(d3.event.translate);geo.render();var center=projection.invert(geo.midPt);geo.graphDiv.emit('plotly_relayouting',{'geo.projection.scale':projection.scale()/geo.fitScale,'geo.center.lon':center[0],'geo.center.lat':center[1]});}function syncCb(set){var center=projection.invert(geo.midPt);set('center.lon',center[0]);set('center.lat',center[1]);}function handleZoomend(){d3.select(this).style(zoomendStyle);sync(geo,projection,syncCb);}zoom.on('zoomstart',handleZoomstart).on('zoom',handleZoom).on('zoomend',handleZoomend);return zoom;}// zoom for non-clipped projections\nfunction zoomNonClipped(geo,projection){var zoom=initZoom(geo,projection);var INSIDETOLORANCEPXS=2;var mouse0,rotate0,translate0,lastRotate,zoomPoint,mouse1,rotate1,point1,didZoom;function position(x){return projection.invert(x);}function outside(x){var pos=position(x);if(!pos)return true;var pt=projection(pos);return Math.abs(pt[0]-x[0])>INSIDETOLORANCEPXS||Math.abs(pt[1]-x[1])>INSIDETOLORANCEPXS;}function handleZoomstart(){d3.select(this).style(zoomstartStyle);mouse0=d3.mouse(this);rotate0=projection.rotate();translate0=projection.translate();lastRotate=rotate0;zoomPoint=position(mouse0);}function handleZoom(){mouse1=d3.mouse(this);if(outside(mouse0)){zoom.scale(projection.scale());zoom.translate(projection.translate());return;}projection.scale(d3.event.scale);projection.translate([translate0[0],d3.event.translate[1]]);if(!zoomPoint){mouse0=mouse1;zoomPoint=position(mouse0);}else if(position(mouse1)){point1=position(mouse1);rotate1=[lastRotate[0]+(point1[0]-zoomPoint[0]),rotate0[1],rotate0[2]];projection.rotate(rotate1);lastRotate=rotate1;}didZoom=true;geo.render();var rotate=projection.rotate();var center=projection.invert(geo.midPt);geo.graphDiv.emit('plotly_relayouting',{'geo.projection.scale':projection.scale()/geo.fitScale,'geo.center.lon':center[0],'geo.center.lat':center[1],'geo.projection.rotation.lon':-rotate[0]});}function handleZoomend(){d3.select(this).style(zoomendStyle);if(didZoom)sync(geo,projection,syncCb);}function syncCb(set){var rotate=projection.rotate();var center=projection.invert(geo.midPt);set('projection.rotation.lon',-rotate[0]);set('center.lon',center[0]);set('center.lat',center[1]);}zoom.on('zoomstart',handleZoomstart).on('zoom',handleZoom).on('zoomend',handleZoomend);return zoom;}// zoom for clipped projections\n// inspired by https://www.jasondavies.com/maps/d3.geo.zoom.js\nfunction zoomClipped(geo,projection){var view={r:projection.rotate(),k:projection.scale()};var zoom=initZoom(geo,projection);var event=d3eventDispatch(zoom,'zoomstart','zoom','zoomend');var zooming=0;var zoomOn=zoom.on;var zoomPoint;zoom.on('zoomstart',function(){d3.select(this).style(zoomstartStyle);var mouse0=d3.mouse(this);var rotate0=projection.rotate();var lastRotate=rotate0;var translate0=projection.translate();var q=quaternionFromEuler(rotate0);zoomPoint=position(projection,mouse0);zoomOn.call(zoom,'zoom',function(){var mouse1=d3.mouse(this);projection.scale(view.k=d3.event.scale);if(!zoomPoint){// if no zoomPoint, the mouse wasn't over the actual geography yet\n// maybe this point is the start... we'll find out next time!\nmouse0=mouse1;zoomPoint=position(projection,mouse0);}else if(position(projection,mouse1)){// check if the point is on the map\n// if not, don't do anything new but scale\n// if it is, then we can assume between will exist below\n// so we don't need the 'bank' function, whatever that is.\n// go back to original projection temporarily\n// except for scale... that's kind of independent?\nprojection.rotate(rotate0).translate(translate0);// calculate the new params\nvar point1=position(projection,mouse1);var between=rotateBetween(zoomPoint,point1);var newEuler=eulerFromQuaternion(multiply(q,between));var rotateAngles=view.r=unRoll(newEuler,zoomPoint,lastRotate);if(!isFinite(rotateAngles[0])||!isFinite(rotateAngles[1])||!isFinite(rotateAngles[2])){rotateAngles=lastRotate;}// update the projection\nprojection.rotate(rotateAngles);lastRotate=rotateAngles;}zoomed(event.of(this,arguments));});zoomstarted(event.of(this,arguments));}).on('zoomend',function(){d3.select(this).style(zoomendStyle);zoomOn.call(zoom,'zoom',null);zoomended(event.of(this,arguments));sync(geo,projection,syncCb);}).on('zoom.redraw',function(){geo.render();var _rotate=projection.rotate();geo.graphDiv.emit('plotly_relayouting',{'geo.projection.scale':projection.scale()/geo.fitScale,'geo.projection.rotation.lon':-_rotate[0],'geo.projection.rotation.lat':-_rotate[1]});});function zoomstarted(dispatch){if(!zooming++)dispatch({type:'zoomstart'});}function zoomed(dispatch){dispatch({type:'zoom'});}function zoomended(dispatch){if(! --zooming)dispatch({type:'zoomend'});}function syncCb(set){var _rotate=projection.rotate();set('projection.rotation.lon',-_rotate[0]);set('projection.rotation.lat',-_rotate[1]);}return d3.rebind(zoom,event,'on');}// -- helper functions for zoomClipped\nfunction position(projection,point){var spherical=projection.invert(point);return spherical&&isFinite(spherical[0])&&isFinite(spherical[1])&&cartesian(spherical);}function quaternionFromEuler(euler){var lambda=0.5*euler[0]*radians;var phi=0.5*euler[1]*radians;var gamma=0.5*euler[2]*radians;var sinLambda=Math.sin(lambda);var cosLambda=Math.cos(lambda);var sinPhi=Math.sin(phi);var cosPhi=Math.cos(phi);var sinGamma=Math.sin(gamma);var cosGamma=Math.cos(gamma);return[cosLambda*cosPhi*cosGamma+sinLambda*sinPhi*sinGamma,sinLambda*cosPhi*cosGamma-cosLambda*sinPhi*sinGamma,cosLambda*sinPhi*cosGamma+sinLambda*cosPhi*sinGamma,cosLambda*cosPhi*sinGamma-sinLambda*sinPhi*cosGamma];}function multiply(a,b){var a0=a[0];var a1=a[1];var a2=a[2];var a3=a[3];var b0=b[0];var b1=b[1];var b2=b[2];var b3=b[3];return[a0*b0-a1*b1-a2*b2-a3*b3,a0*b1+a1*b0+a2*b3-a3*b2,a0*b2-a1*b3+a2*b0+a3*b1,a0*b3+a1*b2-a2*b1+a3*b0];}function rotateBetween(a,b){if(!a||!b)return;var axis=cross(a,b);var norm=Math.sqrt(dot(axis,axis));var halfgamma=0.5*Math.acos(Math.max(-1,Math.min(1,dot(a,b))));var k=Math.sin(halfgamma)/norm;return norm&&[Math.cos(halfgamma),axis[2]*k,-axis[1]*k,axis[0]*k];}// input:\n// rotateAngles: a calculated set of Euler angles\n// pt: a point (cartesian in 3-space) to keep fixed\n// roll0: an initial roll, to be preserved\n// output:\n// a set of Euler angles that preserve the projection of pt\n// but set roll (output[2]) equal to roll0\n// note that this doesn't depend on the particular projection,\n// just on the rotation angles\nfunction unRoll(rotateAngles,pt,lastRotate){// calculate the fixed point transformed by these Euler angles\n// but with the desired roll undone\nvar ptRotated=rotateCartesian(pt,2,rotateAngles[0]);ptRotated=rotateCartesian(ptRotated,1,rotateAngles[1]);ptRotated=rotateCartesian(ptRotated,0,rotateAngles[2]-lastRotate[2]);var x=pt[0];var y=pt[1];var z=pt[2];var f=ptRotated[0];var g=ptRotated[1];var h=ptRotated[2];// the following essentially solves:\n// ptRotated = rotateCartesian(rotateCartesian(pt, 2, newYaw), 1, newPitch)\n// for newYaw and newPitch, as best it can\nvar theta=Math.atan2(y,x)*degrees;var a=Math.sqrt(x*x+y*y);var b;var newYaw1;if(Math.abs(g)>a){newYaw1=(g>0?90:-90)-theta;b=0;}else{newYaw1=Math.asin(g/a)*degrees-theta;b=Math.sqrt(a*a-g*g);}var newYaw2=180-newYaw1-2*theta;var newPitch1=(Math.atan2(h,f)-Math.atan2(z,b))*degrees;var newPitch2=(Math.atan2(h,f)-Math.atan2(z,-b))*degrees;// which is closest to lastRotate[0,1]: newYaw/Pitch or newYaw2/Pitch2?\nvar dist1=angleDistance(lastRotate[0],lastRotate[1],newYaw1,newPitch1);var dist2=angleDistance(lastRotate[0],lastRotate[1],newYaw2,newPitch2);if(dist1<=dist2)return[newYaw1,newPitch1,lastRotate[2]];else return[newYaw2,newPitch2,lastRotate[2]];}function angleDistance(yaw0,pitch0,yaw1,pitch1){var dYaw=angleMod(yaw1-yaw0);var dPitch=angleMod(pitch1-pitch0);return Math.sqrt(dYaw*dYaw+dPitch*dPitch);}// reduce an angle in degrees to [-180,180]\nfunction angleMod(angle){return(angle%360+540)%360-180;}// rotate a cartesian vector\n// axis is 0 (x), 1 (y), or 2 (z)\n// angle is in degrees\nfunction rotateCartesian(vector,axis,angle){var angleRads=angle*radians;var vectorOut=vector.slice();var ax1=axis===0?1:0;var ax2=axis===2?1:2;var cosa=Math.cos(angleRads);var sina=Math.sin(angleRads);vectorOut[ax1]=vector[ax1]*cosa-vector[ax2]*sina;vectorOut[ax2]=vector[ax2]*cosa+vector[ax1]*sina;return vectorOut;}function eulerFromQuaternion(q){return[Math.atan2(2*(q[0]*q[1]+q[2]*q[3]),1-2*(q[1]*q[1]+q[2]*q[2]))*degrees,Math.asin(Math.max(-1,Math.min(1,2*(q[0]*q[2]-q[3]*q[1]))))*degrees,Math.atan2(2*(q[0]*q[3]+q[1]*q[2]),1-2*(q[2]*q[2]+q[3]*q[3]))*degrees];}function cartesian(spherical){var lambda=spherical[0]*radians;var phi=spherical[1]*radians;var cosPhi=Math.cos(phi);return[cosPhi*Math.cos(lambda),cosPhi*Math.sin(lambda),Math.sin(phi)];}function dot(a,b){var s=0;for(var i=0,n=a.length;iMath.abs(dy)){result.boxEnd[1]=result.boxStart[1]+Math.abs(dx)*dydx*(dy>=0?1:-1);// gl-select-box clips to the plot area bounds,\n// which breaks the axis constraint, so don't allow\n// this box to go out of bounds\nif(result.boxEnd[1]dataBox[3]){result.boxEnd[1]=dataBox[3];result.boxEnd[0]=result.boxStart[0]+(dataBox[3]-result.boxStart[1])/Math.abs(dydx);}}else{result.boxEnd[0]=result.boxStart[0]+Math.abs(dy)/dydx*(dx>=0?1:-1);if(result.boxEnd[0]dataBox[2]){result.boxEnd[0]=dataBox[2];result.boxEnd[1]=result.boxStart[1]+(dataBox[2]-result.boxStart[0])*Math.abs(dydx);}}}else{// otherwise clamp small changes to the origin so we get 1D zoom\nif(smallDx)result.boxEnd[0]=result.boxStart[0];if(smallDy)result.boxEnd[1]=result.boxStart[1];}}else if(result.boxEnabled){dx=result.boxStart[0]!==result.boxEnd[0];dy=result.boxStart[1]!==result.boxEnd[1];if(dx||dy){if(dx){updateRange(0,result.boxStart[0],result.boxEnd[0]);scene.xaxis.autorange=false;}if(dy){updateRange(1,result.boxStart[1],result.boxEnd[1]);scene.yaxis.autorange=false;}scene.relayoutCallback();}else{scene.glplot.setDirty();}result.boxEnabled=false;result.boxInited=false;}else if(result.boxInited){// if box was inited but button released then - reset the box\nresult.boxInited=false;}break;case'pan':result.boxEnabled=false;result.boxInited=false;if(buttons){if(!result.panning){result.dragStart[0]=x;result.dragStart[1]=y;}if(Math.abs(result.dragStart[0]-x) 'scene'\nexports.cleanId=function cleanId(id){if(!id.match(/^scene[0-9]*$/))return;var sceneNum=id.substr(5);if(sceneNum==='1')sceneNum='';return SCENE+sceneNum;};exports.updateFx=function(gd){var fullLayout=gd._fullLayout;var subplotIds=fullLayout._subplots[GL3D];for(var i=0;i1;// some layout-wide attribute are used in all scenes\n// if 3D is the only visible plot type\nfunction getDfltFromLayout(attr){if(hasNon3D)return;var isValid=Lib.validate(layoutIn[attr],layoutAttributes[attr]);if(isValid)return layoutIn[attr];}handleSubplotDefaults(layoutIn,layoutOut,fullData,{type:GL3D,attributes:layoutAttributes,handleDefaults:handleGl3dDefaults,fullLayout:layoutOut,font:layoutOut.font,fullData:fullData,getDfltFromLayout:getDfltFromLayout,paper_bgcolor:layoutOut.paper_bgcolor,calendar:layoutOut.calendar});};function handleGl3dDefaults(sceneLayoutIn,sceneLayoutOut,coerce,opts){/*\n * Scene numbering proceeds as follows\n * scene\n * scene2\n * scene3\n *\n * and d.scene will be undefined or some number or number string\n *\n * Also write back a blank scene object to user layout so that some\n * attributes like aspectratio can be written back dynamically.\n */var bgcolor=coerce('bgcolor');var bgColorCombined=Color.combine(bgcolor,opts.paper_bgcolor);var cameraKeys=['up','center','eye'];for(var j=0;j0 not >=0)\n * note that 0's go false with the !! call\n */var hasAspect=!!coerce('aspectratio.x')&&!!coerce('aspectratio.y')&&!!coerce('aspectratio.z');var defaultAspectMode=hasAspect?'manual':'auto';var aspectMode=coerce('aspectmode',defaultAspectMode);/*\n * We need aspectratio object in all the Layouts as it is dynamically set\n * in the calculation steps, ie, we cant set the correct data now, it happens later.\n * We must also account for the case the user sends bad ratio data with 'manual' set\n * for the mode. In this case we must force change it here as the default coerce\n * misses it above.\n */if(!hasAspect){sceneLayoutIn.aspectratio=sceneLayoutOut.aspectratio={x:1,y:1,z:1};if(aspectMode==='manual')sceneLayoutOut.aspectmode='auto';/*\n * kind of like autorange - we need the calculated aspectmode back in\n * the input layout or relayout can cause problems later\n */sceneLayoutIn.aspectmode=sceneLayoutOut.aspectmode;}var fullGl3dData=getSubplotData(opts.fullData,GL3D,opts.id);supplyGl3dAxisLayoutDefaults(sceneLayoutIn,sceneLayoutOut,{font:opts.font,scene:opts.id,data:fullGl3dData,bgColor:bgColorCombined,calendar:opts.calendar,fullLayout:opts.fullLayout});Registry.getComponentMethod('annotations3d','handleDefaults')(sceneLayoutIn,sceneLayoutOut,opts);var dragmode=opts.getDfltFromLayout('dragmode');if(dragmode!==false){if(!dragmode){dragmode='orbit';if(sceneLayoutIn.camera&&sceneLayoutIn.camera.up){var x=sceneLayoutIn.camera.up.x;var y=sceneLayoutIn.camera.up.y;var z=sceneLayoutIn.camera.up.z;if(z!==0){if(!x||!y||!z){dragmode='turntable';}else if(z/Math.sqrt(x*x+y*y+z*z)>0.999){dragmode='turntable';}}}else{dragmode='turntable';}}}coerce('dragmode',dragmode);coerce('hovermode',opts.getDfltFromLayout('hovermode'));}},{\"../../../components/color\":602,\"../../../lib\":735,\"../../../registry\":866,\"../../get_data\":820,\"../../subplot_defaults\":860,\"./axis_defaults\":828,\"./layout_attributes\":831}],831:[function(_dereq_,module,exports){/**\n* Copyright 2012-2020, Plotly, Inc.\n* All rights reserved.\n*\n* This source code is licensed under the MIT license found in the\n* LICENSE file in the root directory of this source tree.\n*/'use strict';var gl3dAxisAttrs=_dereq_('./axis_attributes');var domainAttrs=_dereq_('../../domain').attributes;var extendFlat=_dereq_('../../../lib/extend').extendFlat;var counterRegex=_dereq_('../../../lib').counterRegex;function makeCameraVector(x,y,z){return{x:{valType:'number',dflt:x,editType:'camera'},y:{valType:'number',dflt:y,editType:'camera'},z:{valType:'number',dflt:z,editType:'camera'},editType:'camera'};}module.exports={_arrayAttrRegexps:[counterRegex('scene','.annotations',true)],bgcolor:{valType:'color',dflt:'rgba(0,0,0,0)',editType:'plot'},camera:{up:extendFlat(makeCameraVector(0,0,1),{}),center:extendFlat(makeCameraVector(0,0,0),{}),eye:extendFlat(makeCameraVector(1.25,1.25,1.25),{}),projection:{type:{valType:'enumerated',values:['perspective','orthographic'],dflt:'perspective',editType:'calc'},editType:'calc'},editType:'camera'},domain:domainAttrs({name:'scene',editType:'plot'}),aspectmode:{valType:'enumerated',values:['auto','cube','data','manual'],dflt:'auto',editType:'plot',impliedEdits:{'aspectratio.x':undefined,'aspectratio.y':undefined,'aspectratio.z':undefined}},aspectratio:{// must be positive (0's are coerced to 1)\nx:{valType:'number',min:0,editType:'plot',impliedEdits:{'^aspectmode':'manual'}},y:{valType:'number',min:0,editType:'plot',impliedEdits:{'^aspectmode':'manual'}},z:{valType:'number',min:0,editType:'plot',impliedEdits:{'^aspectmode':'manual'}},editType:'plot',impliedEdits:{aspectmode:'manual'}},xaxis:gl3dAxisAttrs,yaxis:gl3dAxisAttrs,zaxis:gl3dAxisAttrs,dragmode:{valType:'enumerated',values:['orbit','turntable','zoom','pan',false],editType:'plot'},hovermode:{valType:'enumerated',values:['closest',false],dflt:'closest',editType:'modebar'},uirevision:{valType:'any',editType:'none'},editType:'plot',_deprecated:{cameraposition:{valType:'info_array',editType:'camera'}}};},{\"../../../lib\":735,\"../../../lib/extend\":726,\"../../domain\":810,\"./axis_attributes\":827}],832:[function(_dereq_,module,exports){/**\n* Copyright 2012-2020, Plotly, Inc.\n* All rights reserved.\n*\n* This source code is licensed under the MIT license found in the\n* LICENSE file in the root directory of this source tree.\n*/'use strict';var str2RGBArray=_dereq_('../../../lib/str2rgbarray');var AXES_NAMES=['xaxis','yaxis','zaxis'];function SpikeOptions(){this.enabled=[true,true,true];this.colors=[[0,0,0,1],[0,0,0,1],[0,0,0,1]];this.drawSides=[true,true,true];this.lineWidth=[1,1,1];}var proto=SpikeOptions.prototype;proto.merge=function(sceneLayout){for(var i=0;i<3;++i){var axes=sceneLayout[AXES_NAMES[i]];if(!axes.visible){this.enabled[i]=false;this.drawSides[i]=false;continue;}this.enabled[i]=axes.showspikes;this.colors[i]=str2RGBArray(axes.spikecolor);this.drawSides[i]=axes.spikesides;this.lineWidth[i]=axes.spikethickness;}};function createSpikeOptions(layout){var result=new SpikeOptions();result.merge(layout);return result;}module.exports=createSpikeOptions;},{\"../../../lib/str2rgbarray\":758}],833:[function(_dereq_,module,exports){/**\n* Copyright 2012-2020, Plotly, Inc.\n* All rights reserved.\n*\n* This source code is licensed under the MIT license found in the\n* LICENSE file in the root directory of this source tree.\n*/ /* eslint block-scoped-var: 0*/ /* eslint no-redeclare: 0*/'use strict';module.exports=computeTickMarks;var Axes=_dereq_('../../cartesian/axes');var Lib=_dereq_('../../../lib');var AXES_NAMES=['xaxis','yaxis','zaxis'];var centerPoint=[0,0,0];function contourLevelsFromTicks(ticks){var result=new Array(3);for(var i=0;i<3;++i){var tlevel=ticks[i];var clevel=new Array(tlevel.length);for(var j=0;j/g,' ');}}ticks[i]=dataTicks;axes.tickmode=tickModeCached;}}axesOptions.ticks=ticks;// Calculate tick lengths dynamically\nfor(var i=0;i<3;++i){centerPoint[i]=0.5*(scene.glplot.bounds[0][i]+scene.glplot.bounds[1][i]);for(var j=0;j<2;++j){axesOptions.bounds[j][i]=scene.glplot.bounds[j][i];}}scene.contourLevels=contourLevelsFromTicks(ticks);}},{\"../../../lib\":735,\"../../cartesian/axes\":783}],834:[function(_dereq_,module,exports){/**\n* Copyright 2012-2020, Plotly, Inc.\n* All rights reserved.\n*\n* This source code is licensed under the MIT license found in the\n* LICENSE file in the root directory of this source tree.\n*/'use strict';function xformMatrix(m,v){var out=[0,0,0,0];var i,j;for(i=0;i<4;++i){for(j=0;j<4;++j){out[j]+=m[4*i+j]*v[i];}}return out;}function project(camera,v){var p=xformMatrix(camera.projection,xformMatrix(camera.view,xformMatrix(camera.model,[v[0],v[1],v[2],1])));return p;}module.exports=project;},{}],835:[function(_dereq_,module,exports){/**\n* Copyright 2012-2020, Plotly, Inc.\n* All rights reserved.\n*\n* This source code is licensed under the MIT license found in the\n* LICENSE file in the root directory of this source tree.\n*/'use strict';var glPlot3d=_dereq_('gl-plot3d');var createCamera=glPlot3d.createCamera;var createPlot=glPlot3d.createScene;var getContext=_dereq_('webgl-context');var passiveSupported=_dereq_('has-passive-events');var Registry=_dereq_('../../registry');var Lib=_dereq_('../../lib');var Axes=_dereq_('../../plots/cartesian/axes');var Fx=_dereq_('../../components/fx');var str2RGBAarray=_dereq_('../../lib/str2rgbarray');var showNoWebGlMsg=_dereq_('../../lib/show_no_webgl_msg');var project=_dereq_('./project');var createAxesOptions=_dereq_('./layout/convert');var createSpikeOptions=_dereq_('./layout/spikes');var computeTickMarks=_dereq_('./layout/tick_marks');var isMobile=_dereq_('is-mobile')({tablet:true,featureDetect:true});var STATIC_CANVAS,STATIC_CONTEXT;function Scene(options,fullLayout){// create sub container for plot\nvar sceneContainer=document.createElement('div');var plotContainer=options.container;// keep a ref to the graph div to fire hover+click events\nthis.graphDiv=options.graphDiv;// create SVG container for hover text\nvar svgContainer=document.createElementNS('http://www.w3.org/2000/svg','svg');svgContainer.style.position='absolute';svgContainer.style.top=svgContainer.style.left='0px';svgContainer.style.width=svgContainer.style.height='100%';svgContainer.style['z-index']=20;svgContainer.style['pointer-events']='none';sceneContainer.appendChild(svgContainer);this.svgContainer=svgContainer;// Tag the container with the sceneID\nsceneContainer.id=options.id;sceneContainer.style.position='absolute';sceneContainer.style.top=sceneContainer.style.left='0px';sceneContainer.style.width=sceneContainer.style.height='100%';plotContainer.appendChild(sceneContainer);this.fullLayout=fullLayout;this.id=options.id||'scene';this.fullSceneLayout=fullLayout[this.id];// Saved from last call to plot()\nthis.plotArgs=[[],{},{}];/*\n * Move this to calc step? Why does it work here?\n */this.axesOptions=createAxesOptions(fullLayout,fullLayout[this.id]);this.spikeOptions=createSpikeOptions(fullLayout[this.id]);this.container=sceneContainer;this.staticMode=!!options.staticPlot;this.pixelRatio=this.pixelRatio||options.plotGlPixelRatio||2;// Coordinate rescaling\nthis.dataScale=[1,1,1];this.contourLevels=[[],[],[]];this.convertAnnotations=Registry.getComponentMethod('annotations3d','convert');this.drawAnnotations=Registry.getComponentMethod('annotations3d','draw');this.initializeGLPlot();}var proto=Scene.prototype;proto.prepareOptions=function(){var scene=this;var opts={canvas:scene.canvas,gl:scene.gl,glOptions:{preserveDrawingBuffer:isMobile,premultipliedAlpha:true,antialias:true},container:scene.container,axes:scene.axesOptions,spikes:scene.spikeOptions,pickRadius:10,snapToData:true,autoScale:true,autoBounds:false,cameraObject:scene.camera,pixelRatio:scene.pixelRatio};// for static plots, we reuse the WebGL context\n// as WebKit doesn't collect them reliably\nif(scene.staticMode){if(!STATIC_CONTEXT){STATIC_CANVAS=document.createElement('canvas');STATIC_CONTEXT=getContext({canvas:STATIC_CANVAS,preserveDrawingBuffer:true,premultipliedAlpha:true,antialias:true});if(!STATIC_CONTEXT){throw new Error('error creating static canvas/context for image server');}}opts.gl=STATIC_CONTEXT;opts.canvas=STATIC_CANVAS;}return opts;};proto.tryCreatePlot=function(){var scene=this;var opts=scene.prepareOptions();var success=true;try{scene.glplot=createPlot(opts);}catch(e){if(scene.staticMode){success=false;}else{// try second time\ntry{// invert preserveDrawingBuffer setup which could be resulted from is-mobile not detecting the right device\nLib.warn(['webgl setup failed possibly due to',isMobile?'disabling':'enabling','preserveDrawingBuffer config.','The device may not be supported by is-mobile module!','Inverting preserveDrawingBuffer option in second attempt to create webgl scene.'].join(' '));isMobile=opts.glOptions.preserveDrawingBuffer=!opts.glOptions.preserveDrawingBuffer;scene.glplot=createPlot(opts);}catch(e){success=false;}}}return success;};proto.initializeGLCamera=function(){var scene=this;var cameraData=scene.fullSceneLayout.camera;var isOrtho=cameraData.projection.type==='orthographic';scene.camera=createCamera(scene.container,{center:[cameraData.center.x,cameraData.center.y,cameraData.center.z],eye:[cameraData.eye.x,cameraData.eye.y,cameraData.eye.z],up:[cameraData.up.x,cameraData.up.y,cameraData.up.z],_ortho:isOrtho,zoomMin:0.01,zoomMax:100,mode:'orbit'});};proto.initializeGLPlot=function(){var scene=this;scene.initializeGLCamera();var success=scene.tryCreatePlot();/*\n * createPlot will throw when webgl is not enabled in the client.\n * Lets return an instance of the module with all functions noop'd.\n * The destroy method - which will remove the container from the DOM\n * is overridden with a function that removes the container only.\n */if(!success)return showNoWebGlMsg(scene);// List of scene objects\nscene.traces={};scene.make4thDimension();var gd=scene.graphDiv;var layout=gd.layout;var makeUpdate=function(){var update={};if(scene.isCameraChanged(layout)){// camera updates\nupdate[scene.id+'.camera']=scene.getCamera();}if(scene.isAspectChanged(layout)){// scene updates\nupdate[scene.id+'.aspectratio']=scene.glplot.getAspectratio();if(layout[scene.id].aspectmode!=='manual'){scene.fullSceneLayout.aspectmode=layout[scene.id].aspectmode=update[scene.id+'.aspectmode']='manual';}}return update;};var relayoutCallback=function(scene){if(scene.fullSceneLayout.dragmode===false)return;var update=makeUpdate();scene.saveLayout(layout);scene.graphDiv.emit('plotly_relayout',update);};scene.glplot.canvas.addEventListener('mouseup',function(){relayoutCallback(scene);});scene.glplot.canvas.addEventListener('wheel',function(e){if(gd._context._scrollZoom.gl3d){if(scene.camera._ortho){var s=e.deltaX>e.deltaY?1.1:1.0/1.1;var o=scene.glplot.getAspectratio();scene.glplot.setAspectratio({x:s*o.x,y:s*o.y,z:s*o.z});}relayoutCallback(scene);}},passiveSupported?{passive:false}:false);scene.glplot.canvas.addEventListener('mousemove',function(){if(scene.fullSceneLayout.dragmode===false)return;if(scene.camera.mouseListener.buttons===0)return;var update=makeUpdate();scene.graphDiv.emit('plotly_relayouting',update);});if(!scene.staticMode){scene.glplot.canvas.addEventListener('webglcontextlost',function(event){if(gd&&gd.emit){gd.emit('plotly_webglcontextlost',{event:event,layer:scene.id});}},false);}scene.glplot.oncontextloss=function(){scene.recoverContext();};scene.glplot.onrender=function(){scene.render();};return true;};proto.render=function(){var scene=this;var gd=scene.graphDiv;var trace;// update size of svg container\nvar svgContainer=scene.svgContainer;var clientRect=scene.container.getBoundingClientRect();var width=clientRect.width;var height=clientRect.height;svgContainer.setAttributeNS(null,'viewBox','0 0 '+width+' '+height);svgContainer.setAttributeNS(null,'width',width);svgContainer.setAttributeNS(null,'height',height);computeTickMarks(scene);scene.glplot.axes.update(scene.axesOptions);// check if pick has changed\nvar keys=Object.keys(scene.traces);var lastPicked=null;var selection=scene.glplot.selection;for(var i=0;i');}else if(trace.type==='isosurface'||trace.type==='volume'){labels.valueLabel=Axes.tickText(scene._mockAxis,scene._mockAxis.d2l(selection.traceCoordinate[3]),'hover').text;vectorTx.push('value: '+labels.valueLabel);if(selection.textLabel){vectorTx.push(selection.textLabel);}tx=vectorTx.join('
');}else{tx=selection.textLabel;}var pointData={x:selection.traceCoordinate[0],y:selection.traceCoordinate[1],z:selection.traceCoordinate[2],data:traceNow._input,fullData:traceNow,curveNumber:traceNow.index,pointNumber:ptNumber};Fx.appendArrayPointValue(pointData,traceNow,ptNumber);if(trace._module.eventData){pointData=traceNow._module.eventData(pointData,selection,traceNow,{},ptNumber);}var eventData={points:[pointData]};if(scene.fullSceneLayout.hovermode){Fx.loneHover({trace:traceNow,x:(0.5+0.5*pdata[0]/pdata[3])*width,y:(0.5-0.5*pdata[1]/pdata[3])*height,xLabel:labels.xLabel,yLabel:labels.yLabel,zLabel:labels.zLabel,text:tx,name:lastPicked.name,color:Fx.castHoverOption(traceNow,ptNumber,'bgcolor')||lastPicked.color,borderColor:Fx.castHoverOption(traceNow,ptNumber,'bordercolor'),fontFamily:Fx.castHoverOption(traceNow,ptNumber,'font.family'),fontSize:Fx.castHoverOption(traceNow,ptNumber,'font.size'),fontColor:Fx.castHoverOption(traceNow,ptNumber,'font.color'),nameLength:Fx.castHoverOption(traceNow,ptNumber,'namelength'),textAlign:Fx.castHoverOption(traceNow,ptNumber,'align'),hovertemplate:Lib.castOption(traceNow,ptNumber,'hovertemplate'),hovertemplateLabels:Lib.extendFlat({},pointData,labels),eventData:[pointData]},{container:svgContainer,gd:gd});}if(selection.buttons&&selection.distance<5){gd.emit('plotly_click',eventData);}else{gd.emit('plotly_hover',eventData);}oldEventData=eventData;}else{Fx.loneUnhover(svgContainer);gd.emit('plotly_unhover',oldEventData);}scene.drawAnnotations(scene);};proto.recoverContext=function(){var scene=this;scene.glplot.dispose();var tryRecover=function(){if(scene.glplot.gl.isContextLost()){requestAnimationFrame(tryRecover);return;}if(!scene.initializeGLPlot()){Lib.error('Catastrophic and unrecoverable WebGL error. Context lost.');return;}scene.plot.apply(scene,scene.plotArgs);};requestAnimationFrame(tryRecover);};var axisProperties=['xaxis','yaxis','zaxis'];function computeTraceBounds(scene,trace,bounds){var fullSceneLayout=scene.fullSceneLayout;for(var d=0;d<3;d++){var axisName=axisProperties[d];var axLetter=axisName.charAt(0);var ax=fullSceneLayout[axisName];var coords=trace[axLetter];var calendar=trace[axLetter+'calendar'];var len=trace['_'+axLetter+'length'];if(!Lib.isArrayOrTypedArray(coords)){bounds[0][d]=Math.min(bounds[0][d],0);bounds[1][d]=Math.max(bounds[1][d],len-1);}else{var v;for(var i=0;i<(len||coords.length);i++){if(Lib.isArrayOrTypedArray(coords[i])){for(var j=0;jsceneBounds[1][i]){sceneBounds[0][i]=-1;sceneBounds[1][i]=1;}else{var d=sceneBounds[1][i]-sceneBounds[0][i];sceneBounds[0][i]-=d/32.0;sceneBounds[1][i]+=d/32.0;}if(axis.autorange==='reversed'){// swap bounds:\nvar tmp=sceneBounds[0][i];sceneBounds[0][i]=sceneBounds[1][i];sceneBounds[1][i]=tmp;}}else{var range=axis.range;sceneBounds[0][i]=axis.r2l(range[0]);sceneBounds[1][i]=axis.r2l(range[1]);}if(sceneBounds[0][i]===sceneBounds[1][i]){sceneBounds[0][i]-=1;sceneBounds[1][i]+=1;}axisDataRange[i]=sceneBounds[1][i]-sceneBounds[0][i];// Update plot bounds\nscene.glplot.setBounds(i,{min:sceneBounds[0][i]*dataScale[i],max:sceneBounds[1][i]*dataScale[i]});}/*\n * Dynamically set the aspect ratio depending on the users aspect settings\n */var aspectRatio;var aspectmode=fullSceneLayout.aspectmode;if(aspectmode==='cube'){aspectRatio=[1,1,1];}else if(aspectmode==='manual'){var userRatio=fullSceneLayout.aspectratio;aspectRatio=[userRatio.x,userRatio.y,userRatio.z];}else if(aspectmode==='auto'||aspectmode==='data'){var axesScaleRatio=[1,1,1];// Compute axis scale per category\nfor(i=0;i<3;++i){axis=fullSceneLayout[axisProperties[i]];axisType=axis.type;var axisRatio=axisTypeRatios[axisType];axesScaleRatio[i]=Math.pow(axisRatio.acc,1.0/axisRatio.count)/dataScale[i];}if(aspectmode==='data'){aspectRatio=axesScaleRatio;}else{// i.e. 'auto' option\nif(Math.max.apply(null,axesScaleRatio)/Math.min.apply(null,axesScaleRatio)<=4){// USE DATA MODE WHEN AXIS RANGE DIMENSIONS ARE RELATIVELY EQUAL\naspectRatio=axesScaleRatio;}else{// USE EQUAL MODE WHEN AXIS RANGE DIMENSIONS ARE HIGHLY UNEQUAL\naspectRatio=[1,1,1];}}}else{throw new Error('scene.js aspectRatio was not one of the enumerated types');}/*\n * Write aspect Ratio back to user data and fullLayout so that it is modifies as user\n * manipulates the aspectmode settings and the fullLayout is up-to-date.\n */fullSceneLayout.aspectratio.x=sceneLayout.aspectratio.x=aspectRatio[0];fullSceneLayout.aspectratio.y=sceneLayout.aspectratio.y=aspectRatio[1];fullSceneLayout.aspectratio.z=sceneLayout.aspectratio.z=aspectRatio[2];/*\n * Finally assign the computed aspecratio to the glplot module. This will have an effect\n * on the next render cycle.\n */scene.glplot.setAspectratio(fullSceneLayout.aspectratio);// save 'initial' aspectratio & aspectmode view settings for modebar buttons\nif(!scene.viewInitial.aspectratio){scene.viewInitial.aspectratio={x:fullSceneLayout.aspectratio.x,y:fullSceneLayout.aspectratio.y,z:fullSceneLayout.aspectratio.z};}if(!scene.viewInitial.aspectmode){scene.viewInitial.aspectmode=fullSceneLayout.aspectmode;}// Update frame position for multi plots\nvar domain=fullSceneLayout.domain||null;var size=fullLayout._size||null;if(domain&&size){var containerStyle=scene.container.style;containerStyle.position='absolute';containerStyle.left=size.l+domain.x[0]*size.w+'px';containerStyle.top=size.t+(1-domain.y[1])*size.h+'px';containerStyle.width=size.w*(domain.x[1]-domain.x[0])+'px';containerStyle.height=size.h*(domain.y[1]-domain.y[0])+'px';}// force redraw so that promise is returned when rendering is completed\nscene.glplot.redraw();};proto.destroy=function(){var scene=this;if(!scene.glplot)return;scene.camera.mouseListener.enabled=false;scene.container.removeEventListener('wheel',scene.camera.wheelListener);scene.camera=null;scene.glplot.dispose();scene.container.parentNode.removeChild(scene.container);scene.glplot=null;};// getCameraArrays :: plotly_coords -> gl-plot3d_coords\n// inverse of getLayoutCamera\nfunction getCameraArrays(camera){return[[camera.eye.x,camera.eye.y,camera.eye.z],[camera.center.x,camera.center.y,camera.center.z],[camera.up.x,camera.up.y,camera.up.z]];}// getLayoutCamera :: gl-plot3d_coords -> plotly_coords\n// inverse of getCameraArrays\nfunction getLayoutCamera(camera){return{up:{x:camera.up[0],y:camera.up[1],z:camera.up[2]},center:{x:camera.center[0],y:camera.center[1],z:camera.center[2]},eye:{x:camera.eye[0],y:camera.eye[1],z:camera.eye[2]},projection:{type:camera._ortho===true?'orthographic':'perspective'}};}// get camera position in plotly coords from 'gl-plot3d' coords\nproto.getCamera=function(){var scene=this;scene.camera.view.recalcMatrix(scene.camera.view.lastT());return getLayoutCamera(scene.camera);};// set gl-plot3d camera position and scene aspects with a set of plotly coords\nproto.setViewport=function(sceneLayout){var scene=this;var cameraData=sceneLayout.camera;scene.camera.lookAt.apply(this,getCameraArrays(cameraData));scene.glplot.setAspectratio(sceneLayout.aspectratio);var newOrtho=cameraData.projection.type==='orthographic';var oldOrtho=scene.camera._ortho;if(newOrtho!==oldOrtho){scene.glplot.redraw();// TODO: figure out why we need to redraw here?\nscene.glplot.clearRGBA();scene.glplot.dispose();scene.initializeGLPlot();}};proto.isCameraChanged=function(layout){var scene=this;var cameraData=scene.getCamera();var cameraNestedProp=Lib.nestedProperty(layout,scene.id+'.camera');var cameraDataLastSave=cameraNestedProp.get();function same(x,y,i,j){var vectors=['up','center','eye'];var components=['x','y','z'];return y[vectors[i]]&&x[vectors[i]][components[j]]===y[vectors[i]][components[j]];}var changed=false;if(cameraDataLastSave===undefined){changed=true;}else{for(var i=0;i<3;i++){for(var j=0;j<3;j++){if(!same(cameraData,cameraDataLastSave,i,j)){changed=true;break;}}}if(!cameraDataLastSave.projection||cameraData.projection&&cameraData.projection.type!==cameraDataLastSave.projection.type){changed=true;}}return changed;};proto.isAspectChanged=function(layout){var scene=this;var aspectData=scene.glplot.getAspectratio();var aspectNestedProp=Lib.nestedProperty(layout,scene.id+'.aspectratio');var aspectDataLastSave=aspectNestedProp.get();return aspectDataLastSave===undefined||aspectDataLastSave.x!==aspectData.x||aspectDataLastSave.y!==aspectData.y||aspectDataLastSave.z!==aspectData.z;};// save camera to user layout (i.e. gd.layout)\nproto.saveLayout=function(layout){var scene=this;var fullLayout=scene.fullLayout;var cameraData;var cameraNestedProp;var cameraDataLastSave;var aspectData;var aspectNestedProp;var aspectDataLastSave;var cameraChanged=scene.isCameraChanged(layout);var aspectChanged=scene.isAspectChanged(layout);var hasChanged=cameraChanged||aspectChanged;if(hasChanged){var preGUI={};if(cameraChanged){cameraData=scene.getCamera();cameraNestedProp=Lib.nestedProperty(layout,scene.id+'.camera');cameraDataLastSave=cameraNestedProp.get();preGUI[scene.id+'.camera']=cameraDataLastSave;}if(aspectChanged){aspectData=scene.glplot.getAspectratio();aspectNestedProp=Lib.nestedProperty(layout,scene.id+'.aspectratio');aspectDataLastSave=aspectNestedProp.get();preGUI[scene.id+'.aspectratio']=aspectDataLastSave;}Registry.call('_storeDirectGUIEdit',layout,fullLayout._preGUI,preGUI);if(cameraChanged){cameraNestedProp.set(cameraData);var cameraFullNP=Lib.nestedProperty(fullLayout,scene.id+'.camera');cameraFullNP.set(cameraData);}if(aspectChanged){aspectNestedProp.set(aspectData);var aspectFullNP=Lib.nestedProperty(fullLayout,scene.id+'.aspectratio');aspectFullNP.set(aspectData);scene.glplot.redraw();}}return hasChanged;};proto.updateFx=function(dragmode,hovermode){var scene=this;var camera=scene.camera;if(camera){// rotate and orbital are synonymous\nif(dragmode==='orbit'){camera.mode='orbit';camera.keyBindingMode='rotate';}else if(dragmode==='turntable'){camera.up=[0,0,1];camera.mode='turntable';camera.keyBindingMode='rotate';// The setter for camera.mode animates the transition to z-up,\n// but only if we *don't* explicitly set z-up earlier via the\n// relayout. So push `up` back to layout & fullLayout manually now.\nvar gd=scene.graphDiv;var fullLayout=gd._fullLayout;var fullCamera=scene.fullSceneLayout.camera;var x=fullCamera.up.x;var y=fullCamera.up.y;var z=fullCamera.up.z;// only push `up` back to (full)layout if it's going to change\nif(z/Math.sqrt(x*x+y*y+z*z)<0.999){var attr=scene.id+'.camera.up';var zUp={x:0,y:0,z:1};var edits={};edits[attr]=zUp;var layout=gd.layout;Registry.call('_storeDirectGUIEdit',layout,fullLayout._preGUI,edits);fullCamera.up=zUp;Lib.nestedProperty(layout,attr).set(zUp);}}else{// none rotation modes [pan or zoom]\ncamera.keyBindingMode=dragmode;}}// to put dragmode and hovermode on the same grounds from relayout\nscene.fullSceneLayout.hovermode=hovermode;};function flipPixels(pixels,w,h){for(var i=0,q=h-1;i0){var q=255/a;for(var l=0;l<3;++l){// RGB\npixels[k+l]=Math.min(q*pixels[k+l],255);}}}}}proto.toImage=function(format){var scene=this;if(!format)format='png';if(scene.staticMode)scene.container.appendChild(STATIC_CANVAS);// Force redraw\nscene.glplot.redraw();// Grab context and yank out pixels\nvar gl=scene.glplot.gl;var w=gl.drawingBufferWidth;var h=gl.drawingBufferHeight;gl.bindFramebuffer(gl.FRAMEBUFFER,null);var pixels=new Uint8Array(w*h*4);gl.readPixels(0,0,w,h,gl.RGBA,gl.UNSIGNED_BYTE,pixels);flipPixels(pixels,w,h);correctRGB(pixels,w,h);var canvas=document.createElement('canvas');canvas.width=w;canvas.height=h;var context=canvas.getContext('2d');var imageData=context.createImageData(w,h);imageData.data.set(pixels);context.putImageData(imageData,0,0);var dataURL;switch(format){case'jpeg':dataURL=canvas.toDataURL('image/jpeg');break;case'webp':dataURL=canvas.toDataURL('image/webp');break;default:dataURL=canvas.toDataURL('image/png');}if(scene.staticMode)scene.container.removeChild(STATIC_CANVAS);return dataURL;};proto.setConvert=function(){var scene=this;for(var i=0;i<3;i++){var ax=scene.fullSceneLayout[axisProperties[i]];Axes.setConvert(ax,scene.fullLayout);ax.setScale=Lib.noop;}};proto.make4thDimension=function(){var scene=this;var gd=scene.graphDiv;var fullLayout=gd._fullLayout;// mock axis for hover formatting\nscene._mockAxis={type:'linear',showexponent:'all',exponentformat:'B'};Axes.setConvert(scene._mockAxis,fullLayout);};module.exports=Scene;},{\"../../components/fx\":642,\"../../lib\":735,\"../../lib/show_no_webgl_msg\":756,\"../../lib/str2rgbarray\":758,\"../../plots/cartesian/axes\":783,\"../../registry\":866,\"./layout/convert\":829,\"./layout/spikes\":832,\"./layout/tick_marks\":833,\"./project\":834,\"gl-plot3d\":300,\"has-passive-events\":414,\"is-mobile\":424,\"webgl-context\":565}],836:[function(_dereq_,module,exports){/**\n* Copyright 2012-2020, Plotly, Inc.\n* All rights reserved.\n*\n* This source code is licensed under the MIT license found in the\n* LICENSE file in the root directory of this source tree.\n*/'use strict';module.exports=function zip3(x,y,z,len){len=len||x.length;var result=new Array(len);for(var i=0;i© OpenStreetMap',tiles:['https://a.tile.openstreetmap.org/{z}/{x}/{y}.png','https://b.tile.openstreetmap.org/{z}/{x}/{y}.png'],tileSize:256}},layers:[{id:'plotly-osm-tiles',type:'raster',source:'plotly-osm-tiles',minzoom:0,maxzoom:22}]},'white-bg':{id:'white-bg',version:8,sources:{},layers:[{id:'white-bg',type:'background',paint:{'background-color':'#FFFFFF'},minzoom:0,maxzoom:22}]},'carto-positron':{id:'carto-positron',version:8,sources:{'plotly-carto-positron':{type:'raster',attribution:'© CARTO',tiles:['https://cartodb-basemaps-c.global.ssl.fastly.net/light_all/{z}/{x}/{y}.png'],tileSize:256}},layers:[{id:'plotly-carto-positron',type:'raster',source:'plotly-carto-positron',minzoom:0,maxzoom:22}]},'carto-darkmatter':{id:'carto-darkmatter',version:8,sources:{'plotly-carto-darkmatter':{type:'raster',attribution:'© CARTO',tiles:['https://cartodb-basemaps-c.global.ssl.fastly.net/dark_all/{z}/{x}/{y}.png'],tileSize:256}},layers:[{id:'plotly-carto-darkmatter',type:'raster',source:'plotly-carto-darkmatter',minzoom:0,maxzoom:22}]},'stamen-terrain':{id:'stamen-terrain',version:8,sources:{'plotly-stamen-terrain':{type:'raster',attribution:'Map tiles by Stamen Design, under CC BY 3.0 | Data by OpenStreetMap, under ODbL.',tiles:['https://stamen-tiles.a.ssl.fastly.net/terrain/{z}/{x}/{y}.png'],tileSize:256}},layers:[{id:'plotly-stamen-terrain',type:'raster',source:'plotly-stamen-terrain',minzoom:0,maxzoom:22}]},'stamen-toner':{id:'stamen-toner',version:8,sources:{'plotly-stamen-toner':{type:'raster',attribution:'Map tiles by Stamen Design, under CC BY 3.0 | Data by OpenStreetMap, under ODbL.',tiles:['https://stamen-tiles.a.ssl.fastly.net/toner/{z}/{x}/{y}.png'],tileSize:256}},layers:[{id:'plotly-stamen-toner',type:'raster',source:'plotly-stamen-toner',minzoom:0,maxzoom:22}]},'stamen-watercolor':{id:'stamen-watercolor',version:8,sources:{'plotly-stamen-watercolor':{type:'raster',attribution:'Map tiles by Stamen Design, under CC BY 3.0 | Data by OpenStreetMap, under CC BY SA.',tiles:['https://stamen-tiles.a.ssl.fastly.net/watercolor/{z}/{x}/{y}.png'],tileSize:256}},layers:[{id:'plotly-stamen-watercolor',type:'raster',source:'plotly-stamen-watercolor',minzoom:0,maxzoom:22}]}};var styleValuesNonMapbox=Object.keys(stylesNonMapbox);module.exports={requiredVersion:requiredVersion,styleUrlPrefix:'mapbox://styles/mapbox/',styleUrlSuffix:'v9',styleValuesMapbox:['basic','streets','outdoors','light','dark','satellite','satellite-streets'],styleValueDflt:'basic',stylesNonMapbox:stylesNonMapbox,styleValuesNonMapbox:styleValuesNonMapbox,traceLayerPrefix:'plotly-trace-layer-',layoutLayerPrefix:'plotly-layout-layer-',wrongVersionErrorMsg:['Your custom plotly.js bundle is not using the correct mapbox-gl version','Please install mapbox-gl@'+requiredVersion+'.'].join('\\n'),noAccessTokenErrorMsg:['Missing Mapbox access token.','Mapbox trace type require a Mapbox access token to be registered.','For example:',' Plotly.plot(gd, data, layout, { mapboxAccessToken: \\'my-access-token\\' });','More info here: https://www.mapbox.com/help/define-access-token/'].join('\\n'),missingStyleErrorMsg:['No valid mapbox style found, please set `mapbox.style` to one of:',styleValuesNonMapbox.join(', '),'or register a Mapbox access token to use a Mapbox-served style.'].join('\\n'),multipleTokensErrorMsg:['Set multiple mapbox access token across different mapbox subplot,','using first token found as mapbox-gl does not allow multiple'+'access tokens on the same page.'].join('\\n'),mapOnErrorMsg:'Mapbox error.',// Mapbox logo for static export\nmapboxLogo:{path0:'m 10.5,1.24 c -5.11,0 -9.25,4.15 -9.25,9.25 0,5.1 4.15,9.25 9.25,9.25 5.1,0 9.25,-4.15 9.25,-9.25 0,-5.11 -4.14,-9.25 -9.25,-9.25 z m 4.39,11.53 c -1.93,1.93 -4.78,2.31 -6.7,2.31 -0.7,0 -1.41,-0.05 -2.1,-0.16 0,0 -1.02,-5.64 2.14,-8.81 0.83,-0.83 1.95,-1.28 3.13,-1.28 1.27,0 2.49,0.51 3.39,1.42 1.84,1.84 1.89,4.75 0.14,6.52 z',path1:'M 10.5,-0.01 C 4.7,-0.01 0,4.7 0,10.49 c 0,5.79 4.7,10.5 10.5,10.5 5.8,0 10.5,-4.7 10.5,-10.5 C 20.99,4.7 16.3,-0.01 10.5,-0.01 Z m 0,19.75 c -5.11,0 -9.25,-4.15 -9.25,-9.25 0,-5.1 4.14,-9.26 9.25,-9.26 5.11,0 9.25,4.15 9.25,9.25 0,5.13 -4.14,9.26 -9.25,9.26 z',path2:'M 14.74,6.25 C 12.9,4.41 9.98,4.35 8.23,6.1 5.07,9.27 6.09,14.91 6.09,14.91 c 0,0 5.64,1.02 8.81,-2.14 C 16.64,11 16.59,8.09 14.74,6.25 Z m -2.27,4.09 -0.91,1.87 -0.9,-1.87 -1.86,-0.91 1.86,-0.9 0.9,-1.87 0.91,1.87 1.86,0.9 z',polygon:'11.56,12.21 10.66,10.34 8.8,9.43 10.66,8.53 11.56,6.66 12.47,8.53 14.33,9.43 12.47,10.34'},// a subset of node_modules/mapbox-gl/dist/mapbox-gl.css\nstyleRules:{map:'overflow:hidden;position:relative;','missing-css':'display:none;','canary':'background-color:salmon;',// Reusing CSS directives from: https://api.tiles.mapbox.com/mapbox-gl-js/v1.1.1/mapbox-gl.css\n'ctrl-bottom-left':'position: absolute; pointer-events: none; z-index: 2; bottom: 0; left: 0;','ctrl-bottom-right':'position: absolute; pointer-events: none; z-index: 2; right: 0; bottom: 0;','ctrl':'clear: both; pointer-events: auto; transform: translate(0, 0);',// Compact ctrl\n'ctrl-attrib.mapboxgl-compact .mapboxgl-ctrl-attrib-inner':'display: none;','ctrl-attrib.mapboxgl-compact:hover .mapboxgl-ctrl-attrib-inner':'display: block; margin-top:2px','ctrl-attrib.mapboxgl-compact:hover':'padding: 2px 24px 2px 4px; visibility: visible; margin-top: 6px;','ctrl-attrib.mapboxgl-compact::after':'content: \"\"; cursor: pointer; position: absolute; background-image: url(\\'data:image/svg+xml;charset=utf-8,%3Csvg viewBox=\"0 0 20 20\" xmlns=\"http://www.w3.org/2000/svg\"%3E %3Cpath fill=\"%23333333\" fill-rule=\"evenodd\" d=\"M4,10a6,6 0 1,0 12,0a6,6 0 1,0 -12,0 M9,7a1,1 0 1,0 2,0a1,1 0 1,0 -2,0 M9,10a1,1 0 1,1 2,0l0,3a1,1 0 1,1 -2,0\"/%3E %3C/svg%3E\\'); background-color: rgba(255, 255, 255, 0.5); width: 24px; height: 24px; box-sizing: border-box; border-radius: 12px;','ctrl-attrib.mapboxgl-compact':'min-height: 20px; padding: 0; margin: 10px; position: relative; background-color: #fff; border-radius: 3px 12px 12px 3px;','ctrl-bottom-right > .mapboxgl-ctrl-attrib.mapboxgl-compact::after':'bottom: 0; right: 0','ctrl-bottom-left > .mapboxgl-ctrl-attrib.mapboxgl-compact::after':'bottom: 0; left: 0','ctrl-bottom-left .mapboxgl-ctrl':'margin: 0 0 10px 10px; float: left;','ctrl-bottom-right .mapboxgl-ctrl':'margin: 0 10px 10px 0; float: right;','ctrl-attrib':'color: rgba(0, 0, 0, 0.75); text-decoration: none; font-size: 12px','ctrl-attrib a':'color: rgba(0, 0, 0, 0.75); text-decoration: none; font-size: 12px','ctrl-attrib a:hover':'color: inherit; text-decoration: underline;','ctrl-attrib .mapbox-improve-map':'font-weight: bold; margin-left: 2px;','attrib-empty':'display: none;',// Compact Mapbox logo without text\n'ctrl-logo':'display:block; width: 21px; height: 21px; background-image: url(\\'data:image/svg+xml;charset=utf-8,%3C?xml version=\"1.0\" encoding=\"utf-8\"?%3E %3Csvg version=\"1.1\" id=\"Layer_1\" xmlns=\"http://www.w3.org/2000/svg\" xmlns:xlink=\"http://www.w3.org/1999/xlink\" x=\"0px\" y=\"0px\" viewBox=\"0 0 21 21\" style=\"enable-background:new 0 0 21 21;\" xml:space=\"preserve\"%3E%3Cg transform=\"translate(0,0.01)\"%3E%3Cpath d=\"m 10.5,1.24 c -5.11,0 -9.25,4.15 -9.25,9.25 0,5.1 4.15,9.25 9.25,9.25 5.1,0 9.25,-4.15 9.25,-9.25 0,-5.11 -4.14,-9.25 -9.25,-9.25 z m 4.39,11.53 c -1.93,1.93 -4.78,2.31 -6.7,2.31 -0.7,0 -1.41,-0.05 -2.1,-0.16 0,0 -1.02,-5.64 2.14,-8.81 0.83,-0.83 1.95,-1.28 3.13,-1.28 1.27,0 2.49,0.51 3.39,1.42 1.84,1.84 1.89,4.75 0.14,6.52 z\" style=\"opacity:0.9;fill:%23ffffff;enable-background:new\" class=\"st0\"/%3E%3Cpath d=\"M 10.5,-0.01 C 4.7,-0.01 0,4.7 0,10.49 c 0,5.79 4.7,10.5 10.5,10.5 5.8,0 10.5,-4.7 10.5,-10.5 C 20.99,4.7 16.3,-0.01 10.5,-0.01 Z m 0,19.75 c -5.11,0 -9.25,-4.15 -9.25,-9.25 0,-5.1 4.14,-9.26 9.25,-9.26 5.11,0 9.25,4.15 9.25,9.25 0,5.13 -4.14,9.26 -9.25,9.26 z\" style=\"opacity:0.35;enable-background:new\" class=\"st1\"/%3E%3Cpath d=\"M 14.74,6.25 C 12.9,4.41 9.98,4.35 8.23,6.1 5.07,9.27 6.09,14.91 6.09,14.91 c 0,0 5.64,1.02 8.81,-2.14 C 16.64,11 16.59,8.09 14.74,6.25 Z m -2.27,4.09 -0.91,1.87 -0.9,-1.87 -1.86,-0.91 1.86,-0.9 0.9,-1.87 0.91,1.87 1.86,0.9 z\" style=\"opacity:0.35;enable-background:new\" class=\"st1\"/%3E%3Cpolygon points=\"11.56,12.21 10.66,10.34 8.8,9.43 10.66,8.53 11.56,6.66 12.47,8.53 14.33,9.43 12.47,10.34 \" style=\"opacity:0.9;fill:%23ffffff;enable-background:new\" class=\"st0\"/%3E%3C/g%3E%3C/svg%3E\\')'// Mapbox logo WITH text below (commented out for now)\n// 'ctrl-logo': 'width: 85px; height: 21px; margin: 0 0 -3px -3px; display: block; background-repeat: no-repeat; cursor: pointer; background-image: url(\\'data:image/svg+xml;charset=utf-8,%3C?xml version=\"1.0\" encoding=\"utf-8\"?%3E%3Csvg version=\"1.1\" id=\"Layer_1\" xmlns=\"http://www.w3.org/2000/svg\" xmlns:xlink=\"http://www.w3.org/1999/xlink\" x=\"0px\" y=\"0px\" viewBox=\"0 0 84.49 21\" style=\"enable-background:new 0 0 84.49 21;\" xml:space=\"preserve\"%3E%3Cg%3E %3Cpath class=\"st0\" style=\"opacity:0.9; fill: %23FFFFFF; enable-background: new;\" d=\"M83.25,14.26c0,0.12-0.09,0.21-0.21,0.21h-1.61c-0.13,0-0.24-0.06-0.3-0.17l-1.44-2.39l-1.44,2.39 c-0.06,0.11-0.18,0.17-0.3,0.17h-1.61c-0.04,0-0.08-0.01-0.12-0.03c-0.09-0.06-0.13-0.19-0.06-0.28l0,0l2.43-3.68L76.2,6.84 c-0.02-0.03-0.03-0.07-0.03-0.12c0-0.12,0.09-0.21,0.21-0.21h1.61c0.13,0,0.24,0.06,0.3,0.17l1.41,2.36l1.4-2.35 c0.06-0.11,0.18-0.17,0.3-0.17H83c0.04,0,0.08,0.01,0.12,0.03c0.09,0.06,0.13,0.19,0.06,0.28l0,0l-2.37,3.63l2.43,3.67 C83.24,14.18,83.25,14.22,83.25,14.26z\"/%3E %3Cpath class=\"st0\" style=\"opacity:0.9; fill: %23FFFFFF; enable-background: new;\" d=\"M66.24,9.59c-0.39-1.88-1.96-3.28-3.84-3.28c-1.03,0-2.03,0.42-2.73,1.18V3.51c0-0.13-0.1-0.23-0.23-0.23h-1.4 c-0.13,0-0.23,0.11-0.23,0.23v10.72c0,0.13,0.1,0.23,0.23,0.23h1.4c0.13,0,0.23-0.11,0.23-0.23V13.5c0.71,0.75,1.7,1.18,2.73,1.18 c1.88,0,3.45-1.41,3.84-3.29C66.37,10.79,66.37,10.18,66.24,9.59L66.24,9.59z M62.08,13c-1.32,0-2.39-1.11-2.41-2.48v-0.06 c0.02-1.38,1.09-2.48,2.41-2.48s2.42,1.12,2.42,2.51S63.41,13,62.08,13z\"/%3E %3Cpath class=\"st0\" style=\"opacity:0.9; fill: %23FFFFFF; enable-background: new;\" d=\"M71.67,6.32c-1.98-0.01-3.72,1.35-4.16,3.29c-0.13,0.59-0.13,1.19,0,1.77c0.44,1.94,2.17,3.32,4.17,3.3 c2.35,0,4.26-1.87,4.26-4.19S74.04,6.32,71.67,6.32z M71.65,13.01c-1.33,0-2.42-1.12-2.42-2.51s1.08-2.52,2.42-2.52 c1.33,0,2.42,1.12,2.42,2.51S72.99,13,71.65,13.01L71.65,13.01z\"/%3E %3Cpath class=\"st1\" style=\"opacity:0.35; enable-background:new;\" d=\"M62.08,7.98c-1.32,0-2.39,1.11-2.41,2.48v0.06C59.68,11.9,60.75,13,62.08,13s2.42-1.12,2.42-2.51 S63.41,7.98,62.08,7.98z M62.08,11.76c-0.63,0-1.14-0.56-1.17-1.25v-0.04c0.01-0.69,0.54-1.25,1.17-1.25 c0.63,0,1.17,0.57,1.17,1.27C63.24,11.2,62.73,11.76,62.08,11.76z\"/%3E %3Cpath class=\"st1\" style=\"opacity:0.35; enable-background:new;\" d=\"M71.65,7.98c-1.33,0-2.42,1.12-2.42,2.51S70.32,13,71.65,13s2.42-1.12,2.42-2.51S72.99,7.98,71.65,7.98z M71.65,11.76c-0.64,0-1.17-0.57-1.17-1.27c0-0.7,0.53-1.26,1.17-1.26s1.17,0.57,1.17,1.27C72.82,11.21,72.29,11.76,71.65,11.76z\"/%3E %3Cpath class=\"st0\" style=\"opacity:0.9; fill: %23FFFFFF; enable-background: new;\" d=\"M45.74,6.53h-1.4c-0.13,0-0.23,0.11-0.23,0.23v0.73c-0.71-0.75-1.7-1.18-2.73-1.18 c-2.17,0-3.94,1.87-3.94,4.19s1.77,4.19,3.94,4.19c1.04,0,2.03-0.43,2.73-1.19v0.73c0,0.13,0.1,0.23,0.23,0.23h1.4 c0.13,0,0.23-0.11,0.23-0.23V6.74c0-0.12-0.09-0.22-0.22-0.22C45.75,6.53,45.75,6.53,45.74,6.53z M44.12,10.53 C44.11,11.9,43.03,13,41.71,13s-2.42-1.12-2.42-2.51s1.08-2.52,2.4-2.52c1.33,0,2.39,1.11,2.41,2.48L44.12,10.53z\"/%3E %3Cpath class=\"st1\" style=\"opacity:0.35; enable-background:new;\" d=\"M41.71,7.98c-1.33,0-2.42,1.12-2.42,2.51S40.37,13,41.71,13s2.39-1.11,2.41-2.48v-0.06 C44.1,9.09,43.03,7.98,41.71,7.98z M40.55,10.49c0-0.7,0.52-1.27,1.17-1.27c0.64,0,1.14,0.56,1.17,1.25v0.04 c-0.01,0.68-0.53,1.24-1.17,1.24C41.08,11.75,40.55,11.19,40.55,10.49z\"/%3E %3Cpath class=\"st0\" style=\"opacity:0.9; fill: %23FFFFFF; enable-background: new;\" d=\"M52.41,6.32c-1.03,0-2.03,0.42-2.73,1.18V6.75c0-0.13-0.1-0.23-0.23-0.23h-1.4c-0.13,0-0.23,0.11-0.23,0.23 v10.72c0,0.13,0.1,0.23,0.23,0.23h1.4c0.13,0,0.23-0.1,0.23-0.23V13.5c0.71,0.75,1.7,1.18,2.74,1.18c2.17,0,3.94-1.87,3.94-4.19 S54.58,6.32,52.41,6.32z M52.08,13.01c-1.32,0-2.39-1.11-2.42-2.48v-0.07c0.02-1.38,1.09-2.49,2.4-2.49c1.32,0,2.41,1.12,2.41,2.51 S53.4,13,52.08,13.01L52.08,13.01z\"/%3E %3Cpath class=\"st1\" style=\"opacity:0.35; enable-background:new;\" d=\"M52.08,7.98c-1.32,0-2.39,1.11-2.42,2.48v0.06c0.03,1.38,1.1,2.48,2.42,2.48s2.41-1.12,2.41-2.51 S53.4,7.98,52.08,7.98z M52.08,11.76c-0.63,0-1.14-0.56-1.17-1.25v-0.04c0.01-0.69,0.54-1.25,1.17-1.25c0.63,0,1.17,0.58,1.17,1.27 S52.72,11.76,52.08,11.76z\"/%3E %3Cpath class=\"st0\" style=\"opacity:0.9; fill: %23FFFFFF; enable-background: new;\" d=\"M36.08,14.24c0,0.13-0.1,0.23-0.23,0.23h-1.41c-0.13,0-0.23-0.11-0.23-0.23V9.68c0-0.98-0.74-1.71-1.62-1.71 c-0.8,0-1.46,0.7-1.59,1.62l0.01,4.66c0,0.13-0.11,0.23-0.23,0.23h-1.41c-0.13,0-0.23-0.11-0.23-0.23V9.68 c0-0.98-0.74-1.71-1.62-1.71c-0.85,0-1.54,0.79-1.6,1.8v4.48c0,0.13-0.1,0.23-0.23,0.23h-1.4c-0.13,0-0.23-0.11-0.23-0.23V6.74 c0.01-0.13,0.1-0.22,0.23-0.22h1.4c0.13,0,0.22,0.11,0.23,0.22V7.4c0.5-0.68,1.3-1.09,2.16-1.1h0.03c1.09,0,2.09,0.6,2.6,1.55 c0.45-0.95,1.4-1.55,2.44-1.56c1.62,0,2.93,1.25,2.9,2.78L36.08,14.24z\"/%3E %3Cpath class=\"st1\" style=\"opacity:0.35; enable-background:new;\" d=\"M84.34,13.59l-0.07-0.13l-1.96-2.99l1.94-2.95c0.44-0.67,0.26-1.56-0.41-2.02c-0.02,0-0.03,0-0.04-0.01 c-0.23-0.15-0.5-0.22-0.78-0.22h-1.61c-0.56,0-1.08,0.29-1.37,0.78L79.72,6.6l-0.34-0.56C79.09,5.56,78.57,5.27,78,5.27h-1.6 c-0.6,0-1.13,0.37-1.35,0.92c-2.19-1.66-5.28-1.47-7.26,0.45c-0.35,0.34-0.65,0.72-0.89,1.14c-0.9-1.62-2.58-2.72-4.5-2.72 c-0.5,0-1.01,0.07-1.48,0.23V3.51c0-0.82-0.66-1.48-1.47-1.48h-1.4c-0.81,0-1.47,0.66-1.47,1.47v3.75 c-0.95-1.36-2.5-2.18-4.17-2.19c-0.74,0-1.46,0.16-2.12,0.47c-0.24-0.17-0.54-0.26-0.84-0.26h-1.4c-0.45,0-0.87,0.21-1.15,0.56 c-0.02-0.03-0.04-0.05-0.07-0.08c-0.28-0.3-0.68-0.47-1.09-0.47h-1.39c-0.3,0-0.6,0.09-0.84,0.26c-0.67-0.3-1.39-0.46-2.12-0.46 c-1.83,0-3.43,1-4.37,2.5c-0.2-0.46-0.48-0.89-0.83-1.25c-0.8-0.81-1.89-1.25-3.02-1.25h-0.01c-0.89,0.01-1.75,0.33-2.46,0.88 c-0.74-0.57-1.64-0.88-2.57-0.88H28.1c-0.29,0-0.58,0.03-0.86,0.11c-0.28,0.06-0.56,0.16-0.82,0.28c-0.21-0.12-0.45-0.18-0.7-0.18 h-1.4c-0.82,0-1.47,0.66-1.47,1.47v7.5c0,0.82,0.66,1.47,1.47,1.47h1.4c0.82,0,1.48-0.66,1.48-1.48l0,0V9.79 c0.03-0.36,0.23-0.59,0.36-0.59c0.18,0,0.38,0.18,0.38,0.47v4.57c0,0.82,0.66,1.47,1.47,1.47h1.41c0.82,0,1.47-0.66,1.47-1.47 l-0.01-4.57c0.06-0.32,0.25-0.47,0.35-0.47c0.18,0,0.38,0.18,0.38,0.47v4.57c0,0.82,0.66,1.47,1.47,1.47h1.41 c0.82,0,1.47-0.66,1.47-1.47v-0.38c0.96,1.29,2.46,2.06,4.06,2.06c0.74,0,1.46-0.16,2.12-0.47c0.24,0.17,0.54,0.26,0.84,0.26h1.39 c0.3,0,0.6-0.09,0.84-0.26v2.01c0,0.82,0.66,1.47,1.47,1.47h1.4c0.82,0,1.47-0.66,1.47-1.47v-1.77c0.48,0.15,0.99,0.23,1.49,0.22 c1.7,0,3.22-0.87,4.17-2.2v0.52c0,0.82,0.66,1.47,1.47,1.47h1.4c0.3,0,0.6-0.09,0.84-0.26c0.66,0.31,1.39,0.47,2.12,0.47 c1.92,0,3.6-1.1,4.49-2.73c1.54,2.65,4.95,3.53,7.58,1.98c0.18-0.11,0.36-0.22,0.53-0.36c0.22,0.55,0.76,0.91,1.35,0.9H78 c0.56,0,1.08-0.29,1.37-0.78l0.37-0.61l0.37,0.61c0.29,0.48,0.81,0.78,1.38,0.78h1.6c0.81,0,1.46-0.66,1.45-1.46 C84.49,14.02,84.44,13.8,84.34,13.59L84.34,13.59z M35.86,14.47h-1.41c-0.13,0-0.23-0.11-0.23-0.23V9.68 c0-0.98-0.74-1.71-1.62-1.71c-0.8,0-1.46,0.7-1.59,1.62l0.01,4.66c0,0.13-0.1,0.23-0.23,0.23h-1.41c-0.13,0-0.23-0.11-0.23-0.23 V9.68c0-0.98-0.74-1.71-1.62-1.71c-0.85,0-1.54,0.79-1.6,1.8v4.48c0,0.13-0.1,0.23-0.23,0.23h-1.4c-0.13,0-0.23-0.11-0.23-0.23 V6.74c0.01-0.13,0.11-0.22,0.23-0.22h1.4c0.13,0,0.22,0.11,0.23,0.22V7.4c0.5-0.68,1.3-1.09,2.16-1.1h0.03 c1.09,0,2.09,0.6,2.6,1.55c0.45-0.95,1.4-1.55,2.44-1.56c1.62,0,2.93,1.25,2.9,2.78l0.01,5.16C36.09,14.36,35.98,14.46,35.86,14.47 L35.86,14.47z M45.97,14.24c0,0.13-0.1,0.23-0.23,0.23h-1.4c-0.13,0-0.23-0.11-0.23-0.23V13.5c-0.7,0.76-1.69,1.18-2.72,1.18 c-2.17,0-3.94-1.87-3.94-4.19s1.77-4.19,3.94-4.19c1.03,0,2.02,0.43,2.73,1.18V6.74c0-0.13,0.1-0.23,0.23-0.23h1.4 c0.12-0.01,0.22,0.08,0.23,0.21c0,0.01,0,0.01,0,0.02v7.51h-0.01V14.24z M52.41,14.67c-1.03,0-2.02-0.43-2.73-1.18v3.97 c0,0.13-0.1,0.23-0.23,0.23h-1.4c-0.13,0-0.23-0.1-0.23-0.23V6.75c0-0.13,0.1-0.22,0.23-0.22h1.4c0.13,0,0.23,0.11,0.23,0.23v0.73 c0.71-0.76,1.7-1.18,2.73-1.18c2.17,0,3.94,1.86,3.94,4.18S54.58,14.67,52.41,14.67z M66.24,11.39c-0.39,1.87-1.96,3.29-3.84,3.29 c-1.03,0-2.02-0.43-2.73-1.18v0.73c0,0.13-0.1,0.23-0.23,0.23h-1.4c-0.13,0-0.23-0.11-0.23-0.23V3.51c0-0.13,0.1-0.23,0.23-0.23 h1.4c0.13,0,0.23,0.11,0.23,0.23v3.97c0.71-0.75,1.7-1.18,2.73-1.17c1.88,0,3.45,1.4,3.84,3.28C66.37,10.19,66.37,10.8,66.24,11.39 L66.24,11.39L66.24,11.39z M71.67,14.68c-2,0.01-3.73-1.35-4.17-3.3c-0.13-0.59-0.13-1.19,0-1.77c0.44-1.94,2.17-3.31,4.17-3.3 c2.36,0,4.26,1.87,4.26,4.19S74.03,14.68,71.67,14.68L71.67,14.68z M83.04,14.47h-1.61c-0.13,0-0.24-0.06-0.3-0.17l-1.44-2.39 l-1.44,2.39c-0.06,0.11-0.18,0.17-0.3,0.17h-1.61c-0.04,0-0.08-0.01-0.12-0.03c-0.09-0.06-0.13-0.19-0.06-0.28l0,0l2.43-3.68 L76.2,6.84c-0.02-0.03-0.03-0.07-0.03-0.12c0-0.12,0.09-0.21,0.21-0.21h1.61c0.13,0,0.24,0.06,0.3,0.17l1.41,2.36l1.41-2.36 c0.06-0.11,0.18-0.17,0.3-0.17h1.61c0.04,0,0.08,0.01,0.12,0.03c0.09,0.06,0.13,0.19,0.06,0.28l0,0l-2.38,3.64l2.43,3.67 c0.02,0.03,0.03,0.07,0.03,0.12C83.25,14.38,83.16,14.47,83.04,14.47L83.04,14.47L83.04,14.47z\"/%3E %3Cpath class=\"st0\" style=\"opacity:0.9; fill: %23FFFFFF; enable-background: new;\" d=\"M10.5,1.24c-5.11,0-9.25,4.15-9.25,9.25s4.15,9.25,9.25,9.25s9.25-4.15,9.25-9.25 C19.75,5.38,15.61,1.24,10.5,1.24z M14.89,12.77c-1.93,1.93-4.78,2.31-6.7,2.31c-0.7,0-1.41-0.05-2.1-0.16c0,0-1.02-5.64,2.14-8.81 c0.83-0.83,1.95-1.28,3.13-1.28c1.27,0,2.49,0.51,3.39,1.42C16.59,8.09,16.64,11,14.89,12.77z\"/%3E %3Cpath class=\"st1\" style=\"opacity:0.35; enable-background:new;\" d=\"M10.5-0.01C4.7-0.01,0,4.7,0,10.49s4.7,10.5,10.5,10.5S21,16.29,21,10.49C20.99,4.7,16.3-0.01,10.5-0.01z M10.5,19.74c-5.11,0-9.25-4.15-9.25-9.25s4.14-9.26,9.25-9.26s9.25,4.15,9.25,9.25C19.75,15.61,15.61,19.74,10.5,19.74z\"/%3E %3Cpath class=\"st1\" style=\"opacity:0.35; enable-background:new;\" d=\"M14.74,6.25C12.9,4.41,9.98,4.35,8.23,6.1c-3.16,3.17-2.14,8.81-2.14,8.81s5.64,1.02,8.81-2.14 C16.64,11,16.59,8.09,14.74,6.25z M12.47,10.34l-0.91,1.87l-0.9-1.87L8.8,9.43l1.86-0.9l0.9-1.87l0.91,1.87l1.86,0.9L12.47,10.34z\"/%3E %3Cpolygon class=\"st0\" style=\"opacity:0.9; fill: %23FFFFFF; enable-background: new;\" points=\"14.33,9.43 12.47,10.34 11.56,12.21 10.66,10.34 8.8,9.43 10.66,8.53 11.56,6.66 12.47,8.53 \"/%3E%3C/g%3E%3C/svg%3E\\');'\n}};},{}],839:[function(_dereq_,module,exports){/**\n* Copyright 2012-2020, Plotly, Inc.\n* All rights reserved.\n*\n* This source code is licensed under the MIT license found in the\n* LICENSE file in the root directory of this source tree.\n*/'use strict';var Lib=_dereq_('../../lib');/**\n * Convert plotly.js 'textposition' to mapbox-gl 'anchor' and 'offset'\n * (with the help of the icon size).\n *\n * @param {string} textpostion : plotly.js textposition value\n * @param {number} iconSize : plotly.js icon size (e.g. marker.size for traces)\n *\n * @return {object}\n * - anchor\n * - offset\n */module.exports=function convertTextOpts(textposition,iconSize){var parts=textposition.split(' ');var vPos=parts[0];var hPos=parts[1];// ballpack values\nvar factor=Lib.isArrayOrTypedArray(iconSize)?Lib.mean(iconSize):iconSize;var xInc=0.5+factor/100;var yInc=1.5+factor/100;var anchorVals=['',''];var offset=[0,0];switch(vPos){case'top':anchorVals[0]='top';offset[1]=-yInc;break;case'bottom':anchorVals[0]='bottom';offset[1]=yInc;break;}switch(hPos){case'left':anchorVals[1]='right';offset[0]=-xInc;break;case'right':anchorVals[1]='left';offset[0]=xInc;break;}// Mapbox text-anchor must be one of:\n// center, left, right, top, bottom,\n// top-left, top-right, bottom-left, bottom-right\nvar anchor;if(anchorVals[0]&&anchorVals[1])anchor=anchorVals.join('-');else if(anchorVals[0])anchor=anchorVals[0];else if(anchorVals[1])anchor=anchorVals[1];else anchor='center';return{anchor:anchor,offset:offset};};},{\"../../lib\":735}],840:[function(_dereq_,module,exports){/**\n* Copyright 2012-2020, Plotly, Inc.\n* All rights reserved.\n*\n* This source code is licensed under the MIT license found in the\n* LICENSE file in the root directory of this source tree.\n*/'use strict';var mapboxgl=_dereq_('mapbox-gl');var Lib=_dereq_('../../lib');var getSubplotCalcData=_dereq_('../../plots/get_data').getSubplotCalcData;var xmlnsNamespaces=_dereq_('../../constants/xmlns_namespaces');var d3=_dereq_('d3');var Drawing=_dereq_('../../components/drawing');var svgTextUtils=_dereq_('../../lib/svg_text_utils');var Mapbox=_dereq_('./mapbox');var MAPBOX='mapbox';var constants=exports.constants=_dereq_('./constants');exports.name=MAPBOX;exports.attr='subplot';exports.idRoot=MAPBOX;exports.idRegex=exports.attrRegex=Lib.counterRegex(MAPBOX);exports.attributes={subplot:{valType:'subplotid',dflt:'mapbox',editType:'calc'}};exports.layoutAttributes=_dereq_('./layout_attributes');exports.supplyLayoutDefaults=_dereq_('./layout_defaults');exports.plot=function plot(gd){var fullLayout=gd._fullLayout;var calcData=gd.calcdata;var mapboxIds=fullLayout._subplots[MAPBOX];if(mapboxgl.version!==constants.requiredVersion){throw new Error(constants.wrongVersionErrorMsg);}var accessToken=findAccessToken(gd,mapboxIds);mapboxgl.accessToken=accessToken;for(var i=0;i