From bde802af992426fdb2e132d38c77be91a94e9a0c Mon Sep 17 00:00:00 2001 From: WHMCSModule Networks Date: Thu, 7 Nov 2024 10:52:53 +0200 Subject: [PATCH] feat: new implementation of the module --- modules/servers/upCloudVps/cron/bandwidth.php | 52 + modules/servers/upCloudVps/lang/english.php | 102 + modules/servers/upCloudVps/lib/Helper.php | 90 + .../servers/upCloudVps/lib/adminManager.php | 153 + modules/servers/upCloudVps/lib/ajaxAction.php | 163 + .../servers/upCloudVps/lib/clientManager.php | 100 + .../servers/upCloudVps/lib/configOptions.php | 262 + modules/servers/upCloudVps/lib/upCloudVps.php | 439 + .../servers/upCloudVps/lib/usageUpdate.php | 58 + modules/servers/upCloudVps/lib/vmManager.php | 129 + .../templates/assets/css/layout.css | 1041 ++ .../upCloudVps/templates/assets/css/theme.css | 402 + .../assets/img/ajax-loader-small.gif | Bin 0 -> 673 bytes .../templates/assets/img/ajax-loader.gif | Bin 0 -> 10819 bytes .../upCloudVps/templates/assets/img/boot.png | Bin 0 -> 226388 bytes .../upCloudVps/templates/assets/img/disks.png | Bin 0 -> 241739 bytes .../templates/assets/img/editvm.png | Bin 0 -> 238816 bytes .../templates/assets/img/graphs.png | Bin 0 -> 226601 bytes .../templates/assets/img/network.png | Bin 0 -> 231673 bytes .../templates/assets/img/reboot.png | Bin 0 -> 226336 bytes .../templates/assets/img/sort_asc.png | Bin 0 -> 1118 bytes .../assets/img/sort_asc_disabled.png | Bin 0 -> 2916 bytes .../templates/assets/img/sort_both.png | Bin 0 -> 1136 bytes .../templates/assets/img/sort_desc.png | Bin 0 -> 1127 bytes .../assets/img/sort_desc_disabled.png | Bin 0 -> 1045 bytes .../upCloudVps/templates/assets/img/stop.png | Bin 0 -> 226446 bytes .../upCloudVps/templates/assets/img/vnc.png | Bin 0 -> 225941 bytes .../templates/assets/img/vps-details.png | Bin 0 -> 2430 bytes .../upCloudVps/templates/assets/js/Chart.js | 14456 ++++++++++++++++ .../servers/upCloudVps/templates/error.tpl | 9 + .../servers/upCloudVps/templates/overview.tpl | 591 + modules/servers/upCloudVps/upCloudVps.php | 238 + 32 files changed, 18285 insertions(+) create mode 100644 modules/servers/upCloudVps/cron/bandwidth.php create mode 100644 modules/servers/upCloudVps/lang/english.php create mode 100644 modules/servers/upCloudVps/lib/Helper.php create mode 100644 modules/servers/upCloudVps/lib/adminManager.php create mode 100644 modules/servers/upCloudVps/lib/ajaxAction.php create mode 100644 modules/servers/upCloudVps/lib/clientManager.php create mode 100644 modules/servers/upCloudVps/lib/configOptions.php create mode 100644 modules/servers/upCloudVps/lib/upCloudVps.php create mode 100644 modules/servers/upCloudVps/lib/usageUpdate.php create mode 100644 modules/servers/upCloudVps/lib/vmManager.php create mode 100644 modules/servers/upCloudVps/templates/assets/css/layout.css create mode 100644 modules/servers/upCloudVps/templates/assets/css/theme.css create mode 100644 modules/servers/upCloudVps/templates/assets/img/ajax-loader-small.gif create mode 100644 modules/servers/upCloudVps/templates/assets/img/ajax-loader.gif create mode 100644 modules/servers/upCloudVps/templates/assets/img/boot.png create mode 100644 modules/servers/upCloudVps/templates/assets/img/disks.png create mode 100644 modules/servers/upCloudVps/templates/assets/img/editvm.png create mode 100644 modules/servers/upCloudVps/templates/assets/img/graphs.png create mode 100644 modules/servers/upCloudVps/templates/assets/img/network.png create mode 100644 modules/servers/upCloudVps/templates/assets/img/reboot.png create mode 100644 modules/servers/upCloudVps/templates/assets/img/sort_asc.png create mode 100644 modules/servers/upCloudVps/templates/assets/img/sort_asc_disabled.png create mode 100644 modules/servers/upCloudVps/templates/assets/img/sort_both.png create mode 100644 modules/servers/upCloudVps/templates/assets/img/sort_desc.png create mode 100644 modules/servers/upCloudVps/templates/assets/img/sort_desc_disabled.png create mode 100644 modules/servers/upCloudVps/templates/assets/img/stop.png create mode 100644 modules/servers/upCloudVps/templates/assets/img/vnc.png create mode 100644 modules/servers/upCloudVps/templates/assets/img/vps-details.png create mode 100644 modules/servers/upCloudVps/templates/assets/js/Chart.js create mode 100644 modules/servers/upCloudVps/templates/error.tpl create mode 100644 modules/servers/upCloudVps/templates/overview.tpl create mode 100644 modules/servers/upCloudVps/upCloudVps.php diff --git a/modules/servers/upCloudVps/cron/bandwidth.php b/modules/servers/upCloudVps/cron/bandwidth.php new file mode 100644 index 0000000..0455ac7 --- /dev/null +++ b/modules/servers/upCloudVps/cron/bandwidth.php @@ -0,0 +1,52 @@ +join('tblproducts', 'tblproducts.id', '=', 'tblhosting.packageid') + ->where('tblhosting.domainstatus', 'Active') + ->where('tblproducts.servertype', 'upCloudVps')->get(['tblhosting.id','tblhosting.packageid']); + + + foreach ($services as $service) { + $serviceId = $service->id; + $packageId = $service->packageid; + + $tblcustomfields = Capsule::table('tblcustomfields') + ->where('tblcustomfields.relid', $packageId) + ->where('tblcustomfields.fieldname', 'instanceId|instance Id')->value('id'); + + $instanceId = Capsule::table('tblcustomfieldsvalues') + ->where('tblcustomfieldsvalues.relid', $serviceId) + ->where('tblcustomfieldsvalues.fieldid', $tblcustomfields)->value('value'); + + $product = Capsule::table('tblproducts')->where('id', $packageId)->first(); + $server = Capsule::table('tblservers') + ->join('tblservergroupsrel', 'tblservergroupsrel.serverid', '=', 'tblservers.id') + ->where('tblservergroupsrel.groupid', $product->servergroup) + ->first(); + + $params = [ + 'serverusername' => $server->username, + 'serverpassword' => decrypt($server->password), + ]; + + try { + $manager = new upCloudVps($params); + $details = $manager->GetServer($instanceId)['response']['server']; + $Ipv4 = $manager->formatSizeBytestoMB($details['plan_ipv4_bytes']); + $Ipv6 = $manager->formatSizeBytestoMB($details['plan_ipv6_bytes']); + + if ($Ipv4 != '' && $Ipv6 != '') { + Capsule::table('mod_upCloudVps_bandwidth')->insert([ 'serviceId' => $serviceId,'IPv4' => $Ipv4, + 'IPv6' => $Ipv6]); + } + } catch (\Exception $e) { + echo $e->getMessage() . ' ['.$service->id.']' . PHP_EOL; + } + } diff --git a/modules/servers/upCloudVps/lang/english.php b/modules/servers/upCloudVps/lang/english.php new file mode 100644 index 0000000..e3f47c1 --- /dev/null +++ b/modules/servers/upCloudVps/lang/english.php @@ -0,0 +1,102 @@ +$action(); + if ($details['response']['error']['error_message']) { + $results['result'] = 'failure'; + $results['message'] = $details['response']['error']['error_message']; + } else { + $results['message'] = (!empty($_LANG['ajax'][$action])) ? $_LANG['ajax'][$action] : $_LANG['ajax']['action']['success']; + + switch ($action) { + case "refreshServer": + $results['data']['details']['status'] = $details['response']['server']['state']; + $results['data']['details']['statusLang'] = $_LANG['status'][$details['response']['server']['state']]; + break; + case "vncDetails": + $results['vnchost'] = $details['vnchost']; + $results['vncport'] = $details['vncport']; + break; + case "getIpAddresses": + $results = $details; + break; + case "getBandwidth": + $results = $details; + break; + } + } + + } else { + $results['result'] = 'failure'; + $results['message'] = $_LANG['ajax']['action']['not_valid']; + } + + echo json_encode($results); + die; + } catch (\Exception $e) { + echo json_encode(['result' => 'failure', 'message' => $e->getMessage()]); + die; + } + } + + public static function clientAreaPrimarySidebarHook(array $params) + { + add_hook('ClientAreaPrimarySidebar', 1, function (MenuItem $primarySidebar) use ($params) { + $_LANG = Helper::getLang(); + $panel = $primarySidebar->getChild('Service Details Overview'); + if (is_a($panel, 'WHMCS\View\Menu\Item')) { + $panel = $panel->getChild('Information'); + if (is_a($panel, 'WHMCS\View\Menu\Item')) { + $panel->setUri("clientarea.php?action=productdetails&id={$params['serviceid']}"); + $panel->setAttributes([]); + } + } + }); + } + +} diff --git a/modules/servers/upCloudVps/lib/adminManager.php b/modules/servers/upCloudVps/lib/adminManager.php new file mode 100644 index 0000000..1af1e48 --- /dev/null +++ b/modules/servers/upCloudVps/lib/adminManager.php @@ -0,0 +1,153 @@ +manager = new upCloudVps($params); + $this->params = $params; + $this->_LANG = Helper::getLang(); + } + + +public function adminarea(){ + global $aInt; + $instanceId = $this->params['model']->serviceProperties->get('instanceId|instance Id'); + $details = $this->manager->GetServer($instanceId)['response']['server']; + $totalStorage = 0; + $memoryGb = $details['memory_amount'] / 1024; + + $templ = $details['storage_devices']['storage_device']; + $zones = $this->manager->GetZones()['response']['zones']['zone']; + + foreach ($zones as $zone) { + if ($zone['id'] == $details['zone']) { + $details['zoneDescription'] = $zone['description']; + break; + } + } + + + foreach ($templ as $temp) { + if ($temp['part_of_plan'] == "yes" || $details['plan'] == "custom") { + $details['osname'] = $temp['storage_title']; + $details['base_storage_size'] = $temp['storage_size']; + break; + } + } + if (!empty($details["ip_addresses"])) { + foreach ($details['ip_addresses']['ip_address'] as $ip) { + $ReverseDNSValue = $this->manager->GetIPaddress($ip['address'])['response']; + if (strpos($ReverseDNSValue['ip_address']['ptr_record'], "upcloud") !== false) { + $this->manager->ModifyIPaddress($instanceId, $ip["address"], "client.".$_SERVER['SERVER_NAME'].".host"); + } + $tableData[] = array($ip["address"], $ReverseDNSValue['ip_address']['ptr_record'], $ip["access"], $ip["family"]); + } + $aInt->sortableTableInit("nopagination"); + $interfaceInfo = $aInt->sortableTable(array($this->_LANG["IPAddress"], $this->_LANG["reversePTR"], $this->_LANG["Access"], $this->_LANG["Family"],), $tableData); + } + + + foreach ($this->manager->Getplans()['response']['plans']['plan'] as $Plan){ + if($Plan['name'] == $details['plan'] and $Plan['memory_amount'] == $details['memory_amount']){ + $TotalTraffic = $Plan['public_traffic_out'] ; + $Outgoing = $this->manager->formatSizeBytestoGB($details['plan_ipv4_bytes'] + $details['plan_ipv6_bytes']); + $Percentage = round((($Outgoing / $TotalTraffic) * 100), 2) ; + $progressClass = 'progress-bar-success'; + if ($Percentage >= 49 && $Percentage < 70) { + $progressClass = 'progress-bar-info'; + } elseif ($Percentage >= 70 && $Percentage < 86) { + $progressClass = 'progress-bar-warning'; + } elseif ($Percentage >= 86) { + $progressClass = 'progress-bar-danger'; + } + + $Bandwidth = '
+
+ ' . $Percentage . '% +
+
+ ' . $this->_LANG["TotalTraffic"] . ': ' . $TotalTraffic . ' ' . $this->_LANG["GB"] . ' – ' . $this->_LANG["used"] . ' ' . $Outgoing . ' ' . $this->_LANG["GB"] . ' (' . $Percentage . '%)'; + } + } + + + $remoteAccessHost = $details['remote_access_host']; + + if (!empty($remoteAccessHost)) { + $resolvedHost = gethostbyname($remoteAccessHost); + $resolvedHost = ($resolvedHost !== $remoteAccessHost) ? $resolvedHost : $remoteAccessHost; + } else { + $resolvedHost = null; + } + + $vncIp = $resolvedHost; + + $output = ' +
+ + + + + + + + + +
'.$this->_LANG["Hostname"].' '.$details["hostname"].'
'.$this->_LANG["VMId"].' '.$details["uuid"].'
'.$this->_LANG["Template"].' '.$details['osname'].'
'.$this->_LANG["Plan"].' '.$details["plan"].'
'.$this->_LANG["Status"].' '.$details['state'].'
'.$this->_LANG["Location"].' '.$details['zoneDescription'].'
'.$this->_LANG["Backup"].' '.ucfirst($details["simple_backup"]).'
+ + + + + + + + +
'.$this->_LANG["cpu"].' '.$details["core_number"].''.$this->_LANG["core"].'
'.$this->_LANG["Disk"].' '.$details["base_storage_size"].''.$this->_LANG["GB"].'
'.$this->_LANG["memory"].' '.$memoryGb.''.$this->_LANG["GB"].'
'.$this->_LANG["vncEnabled"].' '.ucfirst($details["remote_access_enabled"]).'
'.$this->_LANG["vncHost"].' '. $vncIp.'
'.$this->_LANG["vncPort"].' '.$details["remote_access_port"].'
'.$this->_LANG["vncPassword"].' '.$details["remote_access_password"].'
+
+ '; + + $ReverseDNS = ' +
+ + + +
'; + + $menu[$this->_LANG["VmInfo"]] = $output; + $menu[$this->_LANG["Interface"]] = $interfaceInfo; + + foreach ($this->manager->Getplans()['response']['plans']['plan'] as $Plan){ + if($Plan['name'] == $details['plan'] and $Plan['memory_amount'] == $details['memory_amount']){ + $menu[$this->_LANG["Bandwidth"]] = $Bandwidth; +} +} + $menu[$this->_LANG["reversePTR"]] = $ReverseDNS; + + return $menu; +} + + +} diff --git a/modules/servers/upCloudVps/lib/ajaxAction.php b/modules/servers/upCloudVps/lib/ajaxAction.php new file mode 100644 index 0000000..9ee5625 --- /dev/null +++ b/modules/servers/upCloudVps/lib/ajaxAction.php @@ -0,0 +1,163 @@ +manager = new upCloudVps($params); + $this->instanceId = $params['model']->serviceProperties->get('instanceId|instance Id'); + $this->serviceid = $params['serviceid']; + } + + public function refreshServer() + { + return $this->manager->GetServer($this->instanceId); + } + + public function StartServer() + { + return $this->manager->StartServer($this->instanceId); + } + + public function RestartServer() + { + return $this->manager->RestartServer($this->instanceId); + } + + public function StopServer() + { + return $this->manager->StopServer($this->instanceId); + } + + public function saveVNCConfiguration() + { + return $this->manager->vncPasswordUpdate($this->instanceId, filter_input(INPUT_POST, 'vnc_password', FILTER_SANITIZE_STRING)); + } + + public function changeVNCStatus() + { + $type = filter_input(INPUT_POST, 'vnc', FILTER_SANITIZE_STRING); + $type = ($type == 'on') ? 'yes' : 'no'; + return $this->manager->vncEnableDisable($this->instanceId, $type); + } + + public function vncDetails() + { + $details = $this->manager->GetServer($this->instanceId); + if($details['response']['error']['error_message']){ + return $details['response']['error']['error_message']; + } else { + $remoteAccessHost = $details['response']['server']['remote_access_host']; + +if (!empty($remoteAccessHost)) { + $resolvedHost = gethostbyname($remoteAccessHost); + $resolvedHost = ($resolvedHost !== $remoteAccessHost) ? $resolvedHost : $remoteAccessHost; +} else { + $resolvedHost = null; +} + $results['vnchost'] = $resolvedHost; + $results['vncport'] = $details['response']['server']['remote_access_port']; + return $results; + } + } + + public function getIpAddresses() + { + $details = $this->manager->GetServer($this->instanceId); + $ips = $details['response']['server']['ip_addresses']['ip_address']; + foreach ($ips as $ip) { + $ReverseDNSValue = $this->manager->GetIPaddress($ip['address'])['response']['ip_address']['ptr_record']; + if (strpos($ReverseDNSValue, "upcloud") !== false) { + $this->manager->ModifyIPaddress($this->instanceId, $ip["address"], "client.".$_SERVER['SERVER_NAME'].".host"); + } + $btn = ($ip['access'] == "utility") ? "" : ''; + + $output[] = [ + ucfirst($ip['access']).' '.$ip['family'], + $ip['address'], + $ReverseDNSValue, + $btn, + ]; + } + + $ips['data'] = (!empty($output)) ? $output : []; + + return $ips; + } + + public function editIp() + { + return $this->manager->ModifyIPaddress($this->instanceId, filter_input(INPUT_POST, 'ip', FILTER_SANITIZE_STRING), filter_input(INPUT_POST, 'ptr', FILTER_SANITIZE_STRING)); + } + + public function saveServerConfiguration() + { + $serverConfig = [ + 'server' => [ + 'hostname' => filter_input(INPUT_POST, 'hostname', FILTER_SANITIZE_STRING), + 'boot_order' => filter_input(INPUT_POST, 'bootOrder', FILTER_SANITIZE_STRING), + 'video_model' => filter_input(INPUT_POST, 'displayAdapter', FILTER_SANITIZE_STRING), + 'nic_model' => filter_input(INPUT_POST, 'networkAdapter', FILTER_SANITIZE_STRING), + 'timezone' => filter_input(INPUT_POST, 'timezone', FILTER_SANITIZE_STRING), + ], + ]; + + return $this->manager->modifyVPS($this->instanceId, $serverConfig); + } + + public function getBandwidth() + { + $data = Capsule::table('mod_upCloudVps_bandwidth'); + if (!empty(filter_input(INPUT_POST, 'time', FILTER_SANITIZE_STRING))) { + switch (filter_input(INPUT_POST, 'time', FILTER_SANITIZE_STRING)) { + case '24 Hours': + $data->whereRaw('created_at >= NOW() - INTERVAL 1 DAY') + ->where('serviceId', $this->serviceid) + ->groupBy(Capsule::raw('day(created_at),hour(created_at),from_unixtime(FLOOR(UNIX_TIMESTAMP(created_at)/(15*60))*(15*60)) ')); + break; + case 'Week': + $data->whereRaw('created_at >= NOW() - INTERVAL 1 WEEK') + ->where('serviceId', $this->serviceid) + ->groupBy(Capsule::raw('week(created_at),day(created_at), hour(created_at), from_unixtime(FLOOR(UNIX_TIMESTAMP(created_at)/(60*60))*(60*60))')); + break; + case 'Month': + $data->whereRaw('created_at >= NOW() - INTERVAL 1 MONTH') + ->where('serviceId', $this->serviceid) + ->groupBy(Capsule::raw('month(created_at),day(created_at)')); + break; + case 'Year': + $data->whereRaw('created_at >= NOW() - INTERVAL 1 YEAR') + ->where('serviceId', $this->serviceid) + ->groupBy(Capsule::raw('year(created_at),month(created_at),week(created_at)')); + break; + default: + $data->whereRaw('created_at >= NOW() - INTERVAL 1 DAY') + ->where('serviceId', $this->serviceid) + ->groupBy(Capsule::raw('day(created_at),hour(created_at),from_unixtime(FLOOR(UNIX_TIMESTAMP(created_at)/(15*60))*(15*60))')); + } + } + + $data = $data->get(); + $output = []; + foreach ($data as $index => $dat) { + if ($index >= 0) { + $output['IPv4'][] = ($data[$index]->IPv4 - $data[($index - 1)]->IPv4); + $output['IPv6'][] = ($data[$index]->IPv6 - $data[($index - 1)]->IPv6); + $output['labels'][] = $dat->created_at; + } + } + + return [ 'data' => $output]; + } + +} diff --git a/modules/servers/upCloudVps/lib/clientManager.php b/modules/servers/upCloudVps/lib/clientManager.php new file mode 100644 index 0000000..0fb250a --- /dev/null +++ b/modules/servers/upCloudVps/lib/clientManager.php @@ -0,0 +1,100 @@ +manager = new upCloudVps($params); + $this->params = $params; + } + + public function getData($page) + { + try { + $instanceId = $this->params['model']->serviceProperties->get('instanceId|instance Id'); + switch ($page) { + case 'details': + $details = $this->manager->GetServer($instanceId); + + if ($details['response_code'] == '200') { + $details = $details['response']['server']; + $totalStorage = 0; + $memoryGb = $details['memory_amount'] / 1024; + + $templ = $details['storage_devices']['storage_device']; + $zones = $this->manager->GetZones()['response']['zones']['zone']; + + foreach ($zones as $zone) { + if ($zone['id'] == $details['zone']) { + $details['zoneDescription'] = $zone['description']; + break; + } + } + + foreach ($templ as $temp) { + if ($temp['part_of_plan'] == "yes" || $details['plan'] == "custom") { + $details['osname'] = $temp['storage_title']; + $details['base_storage_size'] = $temp['storage_size']; + break; + } + } + + $vnc = ($details['remote_access_enabled'] == 'yes') ? 'on' : 'off'; + + $remoteAccessHost = $details['remote_access_host']; + + if (!empty($remoteAccessHost)) { + $resolvedHost = gethostbyname($remoteAccessHost); + $resolvedHost = ($resolvedHost !== $remoteAccessHost) ? $resolvedHost : $remoteAccessHost; + } else { + $resolvedHost = null; + } + + $vncIp = $resolvedHost; + $data['timezones'] = $this->manager->getTimezones()['response']['timezones']['timezone']; + + $data['details'] = [ + 'hostname' => $details['hostname'], + 'ip' => $details->ip, + 'uuid' => $details['uuid'], + 'plan' => $details['plan'], + 'template' => $details['osname'], + 'diskSize' => $details['base_storage_size'], + 'status' => $details['state'], + 'location' => $details['zoneDescription'], + 'vnc' => $vnc, + 'vnc_host' => $vncIp, + 'vnc_port' => $details['remote_access_port'], + 'vnc_password' => $details['remote_access_password'], + 'video_model' => $details['video_model'], + 'nic_model' => $details['nic_model'], + 'timezone' => $details['timezone'], + 'boot_order' => $details['boot_order'], + ]; + + if (!empty($details['ip_addresses'])) { + foreach ($details['ip_addresses']['ip_address'] as $ip) { + if ($ip['access'] == 'public' && $ip['family'] == 'IPv4') { + $data['details']['ip'] = $ip['address']; + } + + } + } + } + break; + } + } catch (\Exception $e) { + return []; + } + + return $data; + } + +} diff --git a/modules/servers/upCloudVps/lib/configOptions.php b/modules/servers/upCloudVps/lib/configOptions.php new file mode 100644 index 0000000..5a4cda5 --- /dev/null +++ b/modules/servers/upCloudVps/lib/configOptions.php @@ -0,0 +1,262 @@ +manager = new upCloudVps($params); + $this->params = $params; + $this->_LANG = Helper::getLang(); + } + + public function configs(){ + $product = Product::find(App::getFromRequest('id')); + if (App::getFromRequest('action') == 'save') { + $this->ensureCustomFields($product); + $this->createGlobalConfigurableOptions($product); + $this->createCustomConfigurableOptions($product); + } // Save End + + return [ + 'Default Location' => ['Type' => 'dropdown', 'Options' => $this->getZoneLocation()], + 'Plan' => ['Type' => 'dropdown', 'Options' => $this->getVmplans()], + 'Template' => ['Type' => 'dropdown', 'Options' => $this->getTemplateIds()], + //'Backup' => ['Type' => 'dropdown', 'Options' => $this->getBackups()], + //'Networking' => ['Type' => 'dropdown', 'Options' => $this->getNetworking()], + ]; + } + + private function getNetworking() + { + $nw['ipv4only'] = "Public IPv4 Only"; + $nw['default'] = "Public Default (IPv4, IPv6 & Utility)"; + return $nw; + } + + private function createCustomConfigurableOptions($product) + { + $currencyId = Capsule::table('tblcurrencies')->where('default', '1')->first()->id; + $currencyCode = Capsule::table('tblcurrencies')->where('default', '1')->first()->code; + $groupId = Capsule::table('tblproductconfiggroups')->where('name', 'Configurable options for UpCloud - Custom Plans')->first()->id; + + if(!$groupId){ + $groupId = Capsule::table('tblproductconfiggroups')->insertGetId(['name' => 'Configurable options for UpCloud - Custom Plans', 'description' => 'Auto generated by upCloudVps module']); + $groupIdLinks = Capsule::table('tblproductconfiglinks')->where('gid', $groupId)->where('pid', $product->id)->first()->gid; + if(!$groupIdLinks){ + Capsule::table('tblproductconfiglinks')->insert(['gid' => $groupId, 'pid' => $product->id]); + } + $this->createRAMFields($groupId, $currencyId, $currencyCode); + $this->createCPUFields($groupId, $currencyId, $currencyCode); + $this->createStorageFields($groupId, $currencyId, $currencyCode); + } + } + + private function createGlobalConfigurableOptions($product) + { + $groupId = Capsule::table('tblproductconfiggroups')->where('name', 'Configurable options for UpCloud Global')->first()->id; + $currencyId = Capsule::table('tblcurrencies')->where('default', '1')->first()->id; + $currencyCode = Capsule::table('tblcurrencies')->where('default', '1')->first()->code; + if(!$groupId){ + $groupId = Capsule::table('tblproductconfiggroups')->insertGetId(['name' => 'Configurable options for UpCloud Global', 'description' => 'Auto generated by upCloudVps module']); + $groupIdLinks = Capsule::table('tblproductconfiglinks')->where('gid', $groupId)->where('pid', $product->id)->first()->gid; + if(!$groupIdLinks){ + Capsule::table('tblproductconfiglinks')->insert(['gid' => $groupId, 'pid' => $product->id]); + } + $pomTemplates = $this->getTemplateIds(); + $zones = $this->getZoneLocation(); + $this->createLocationFields($groupId, $zones, $currencyId); + $this->createTemplateFields($groupId, $pomTemplates, $currencyId, $currencyCode); + $this->createBackupFields($groupId, $this->getBackups()); + $this->createIPFields($groupId, $this->getNetworking()); + } + } + + private function createBackupFields($groupId, $backup){ + $optionId = Capsule::table('tblproductconfigoptions')->where('gid', $groupId)->where('optionname', 'backup|Backup')->first()->id; + if(!$optionId){ + $optionId = Capsule::table('tblproductconfigoptions')->insertGetId(['gid' => $groupId, 'optionname' => 'backup|Backup', 'optiontype' => 1]); + foreach ($backup as $id => $val) { + Capsule::table('tblproductconfigoptionssub')->updateOrInsert(['optionname' => $id.'|'.$val], ['configid' => $optionId]); + } + } + } + + private function createIPFields($groupId, $ips){ + $optionId = Capsule::table('tblproductconfigoptions')->where('gid', $groupId)->where('optionname', 'ipaddress|IP Address')->first()->id; + if(!$optionId){ + $optionId = Capsule::table('tblproductconfigoptions')->insertGetId(['gid' => $groupId, 'optionname' => 'ipaddress|IP Address', 'optiontype' => 1]); + foreach ($ips as $id => $val) { + Capsule::table('tblproductconfigoptionssub')->updateOrInsert(['optionname' => $id.'|'.$val], ['configid' => $optionId]); + } + } + } + + private function createRAMFields($groupId, $currencyId, $currencyCode){ + $mems = array(); + for ($i = 4; $i <= 64; $i += 1) { + $mems["$i"] = "$i Gigabyte (GB)"; + } + $optionId = Capsule::table('tblproductconfigoptions')->where('gid', $groupId)->where('optionname', 'ram|Memory (RAM)')->first()->id; + if(!$optionId){ + $optionId = Capsule::table('tblproductconfigoptions')->insertGetId(['gid' => $groupId, 'optionname' => 'ram|Memory (RAM)', 'optiontype' => 1]); + foreach ($mems as $mem => $mvals) { + Capsule::table('tblproductconfigoptionssub')->updateOrInsert(['optionname' => $mem.'|'.$mvals], ['configid' => $optionId]); + } + foreach (Capsule::table('tblproductconfigoptionssub')->where('configid', $optionId)->get() as $id) { + $parts = explode("|", $id->optionname); + $fprice = $parts[0] * "2"; + $monthlys = $this->manager->CurrencyConvert('EUR', $currencyCode, $fprice); + Capsule::table('tblpricing')->updateOrInsert(['type' => 'configoptions', 'relid' => $id->id, 'monthly' => $monthlys['convertedAmount'] ],['currency' => $currencyId]); + } + } + } + + private function createCPUFields($groupId, $currencyId, $currencyCode){ + $cpus = array(); + for ($i = 2; $i <= 24; $i += 1) { + $cpus["$i"] = "$i Core vCPU"; + } + $optionId = Capsule::table('tblproductconfigoptions')->where('gid', $groupId)->where('optionname', 'vcpu|vCPU')->first()->id; + if(!$optionId){ + $optionId = Capsule::table('tblproductconfigoptions')->insertGetId(['gid' => $groupId, 'optionname' => 'vcpu|vCPU', 'optiontype' => 1]); + foreach ($cpus as $cpu => $cvals) { + Capsule::table('tblproductconfigoptionssub')->updateOrInsert(['optionname' => $cpu.'|'.$cvals], ['configid' => $optionId]); + } + foreach (Capsule::table('tblproductconfigoptionssub')->where('configid', $optionId)->get() as $id) { + $parts = explode("|", $id->optionname); + $fprice = $parts[0] * "6"; + $monthlys = $this->manager->CurrencyConvert('EUR', $currencyCode, $fprice); + Capsule::table('tblpricing')->updateOrInsert(['type' => 'configoptions', 'relid' => $id->id, 'monthly' => $monthlys['convertedAmount'] ],['currency' => $currencyId]); + } + } + } + + private function createStorageFields($groupId, $currencyId, $currencyCode){ + $storages = array(); + for ($i = 50; $i <= 460; $i += 10) { + $storages["$i"] = "$i Gigabyte (GB)"; + } + $optionId = Capsule::table('tblproductconfigoptions')->where('gid', $groupId)->where('optionname', 'storage|Storage')->first()->id; + if(!$optionId){ + $optionId = Capsule::table('tblproductconfigoptions')->insertGetId(['gid' => $groupId, 'optionname' => 'storage|Storage', 'optiontype' => 1]); + foreach ($storages as $storage => $vals) { + Capsule::table('tblproductconfigoptionssub')->updateOrInsert(['optionname' => $storage.'|'.$vals], ['configid' => $optionId]); + } + foreach (Capsule::table('tblproductconfigoptionssub')->where('configid', $optionId)->get() as $id) { + $parts = explode("|", $id->optionname); + $fprice = $parts[0] * "0.10"; + $monthlys = $this->manager->CurrencyConvert('EUR', $currencyCode, $fprice); + Capsule::table('tblpricing')->updateOrInsert(['type' => 'configoptions', 'relid' => $id->id, 'monthly' => $monthlys['convertedAmount'] ], ['currency' => $currencyId] ); + } + } + } + + private function createTemplateFields($groupId, $pomTemplates, $currencyId, $currencyCode){ + $optionId = Capsule::table('tblproductconfigoptions')->where('gid', $groupId)->where('optionname', 'template|Template')->first()->id; + if(!$optionId){ + $optionId = Capsule::table('tblproductconfigoptions')->insertGetId(['gid' => $groupId, 'optionname' => 'template|Template', 'optiontype' => 1]); + foreach ($pomTemplates as $id => $val) { + Capsule::table('tblproductconfigoptionssub')->updateOrInsert(['optionname' => $id.'|'.$val], ['configid' => $optionId]); + } + foreach (Capsule::table('tblproductconfigoptionssub')->where('configid', $optionId)->get() as $id) { + if (strpos($id->optionname, "Datacenter") !== false) { + $monthlys = $this->manager->CurrencyConvert('EUR', $currencyCode, "25"); + } elseif (strpos($id->optionname, "Standard") !== false) { + $monthlys = $this->manager->CurrencyConvert('EUR', $currencyCode, "10"); + } else { + $monthlys['convertedAmount'] = "0.00"; + } + Capsule::table('tblpricing')->updateOrInsert(['type' => 'configoptions', 'relid' => $id->id, 'monthly' => $monthlys['convertedAmount']], ['currency' => $currencyId]); + } + } + } + +private function createLocationFields($groupId, $zones, $currencyId){ + $optionId = Capsule::table('tblproductconfigoptions')->where('gid', $groupId)->where('optionname', 'location|Location')->first()->id; + if(!$optionId){ + $optionId = Capsule::table('tblproductconfigoptions')->insertGetId(['gid' => $groupId, 'optionname' => 'location|Location', 'optiontype' => 1]); + foreach ($zones as $zon => $val) { + Capsule::table('tblproductconfigoptionssub')->updateOrInsert(['optionname' => $zon.'|'.$val], ['configid' => $optionId]); + } + foreach (Capsule::table('tblproductconfigoptionssub')->where('configid', $optionId)->get() as $id) { + Capsule::table('tblpricing')->updateOrInsert(['type' => 'configoptions', 'relid' => $id->id],['currency' => $currencyId]); + } + } +} + + private function ensureCustomFields($product) +{ + $customFields = [ + ['fieldname' => 'ssh_key|SSH Public Key', 'fieldtype' => 'textarea', 'description' => $this->_LANG["sshRsa"], 'regexpr' => "#ssh-rsa AAAA[0-9A-Za-z+/]+[=]{0,3}( [^@]+@[^@]+)?#", "showorder" => "on"], + ['fieldname' => 'instanceId|instance Id', 'fieldtype' => 'text', 'adminonly' => 'on'], + ['fieldname' => 'userData|User Data', 'fieldtype' => 'textarea', 'description' => $this->_LANG["userData"], "showorder" => "on"] + ]; + + foreach ($customFields as $field) { + if (empty(Capsule::table('tblcustomfields')->where('fieldname', $field['fieldname'])->where('relid', $product->id)->value('relid'))) { + Capsule::table('tblcustomfields')->updateOrInsert(['type' => 'product', 'relid' => $product->id] + $field); + } + } +} + + private function getBackups() + { + $backups['no'] = "Disable"; + $backups['daily'] = "Daily (Day Plan)"; + $backups['dailies'] = "Dailies (Week Plan)"; + $backups['weeklies'] = "Weeklies (Month Plan)"; + $backups['monthlies'] = "Monthlies (Year Plan)"; + return $backups; + } + + + private function getZoneLocation() + { + $zones = $this->manager->GetZones()['response']['zones']['zone']; + $zoneLocation = []; + + foreach ($zones as $zone) { + $zoneLocation[$zone['id']] = $zone['description']; + } + + return $zoneLocation; + } + + private function getTemplateIds() + { + $templates = $this->manager->GetTemplate()['response']['storages']['storage']; + $templateIds = []; + + foreach ($templates as $template) { + $templateIds[$template['uuid']] = $template['title']; + } + + return $templateIds; + } + + private function getVmplans() + { + $plans = $this->manager->Getplans()['response']['plans']['plan']; + $Vmplans = ['custom' => $this->_LANG['custom'] . ' [ ' . $this->_LANG['custDesc'] . ' ]']; + + foreach ($plans as $plan) { + $PlanDesc = $this->_LANG['cpu'] . ': ' . $plan['core_number'] . ' ' . $this->_LANG['core'] . ' / ' . $this->_LANG['memory'] . ': ' + . $plan['memory_amount'] . $this->_LANG['MB'] . ' / ' . $this->_LANG['Disk'] . ': ' . $plan['storage_size'] . $this->_LANG['GB']; + $Vmplans[$plan['name']] = $plan['name'] . ' [ ' . $PlanDesc . ' ]'; + } + + return $Vmplans; + } + +} diff --git a/modules/servers/upCloudVps/lib/upCloudVps.php b/modules/servers/upCloudVps/lib/upCloudVps.php new file mode 100644 index 0000000..8216991 --- /dev/null +++ b/modules/servers/upCloudVps/lib/upCloudVps.php @@ -0,0 +1,439 @@ +baseUrl = 'https://api.upcloud.com/1.3/'; + $this->curl = curl_init(); + curl_setopt_array($this->curl, [ + CURLOPT_RETURNTRANSFER => true, + CURLOPT_USERPWD => $user . ':' . $password, + ]); + } + + public function __destruct() + { + curl_close($this->curl); + } + + protected function setHttpHeader($name, $value) + { + $this->httpHeader[$name] = $value; + } + + protected function executeRequest($method, $url, $data = null) + { + curl_setopt($this->curl, CURLOPT_URL, $this->baseUrl . $url); + curl_setopt($this->curl, CURLOPT_CUSTOMREQUEST, $method); + + if ($data !== null) { + curl_setopt($this->curl, CURLOPT_POSTFIELDS, json_encode($data)); + $this->setHttpHeader('Content-Type', 'application/json'); + } + + // Set HTTP headers + curl_setopt($this->curl, CURLOPT_HTTPHEADER, array_map( + function ($key, $value) { + return "$key: $value"; + }, + array_keys($this->httpHeader), + $this->httpHeader + )); + + $response = curl_exec($this->curl); + $statusCode = curl_getinfo($this->curl, CURLINFO_HTTP_CODE); + + if ($response === false) { + throw new \Exception('Curl error: ' . curl_error($this->curl)); + } + + logModuleCall( + 'upCloudVps', + strtoupper($method), + $url.PHP_EOL.((!empty($data)) ? json_encode($data, JSON_PRETTY_PRINT) : ''), + json_decode($response, true), + json_decode($response, true), + [] + ); + + return ['response_code' => $statusCode, 'response' => json_decode($response, true)]; + } + + public function get($url) + { + return $this->executeRequest('GET', $url); + } + + public function post($url, $data = null) + { + return $this->executeRequest('POST', $url, $data); + } + + public function put($url, $data = null) + { + return $this->executeRequest('PUT', $url, $data); + } + + public function delete($url, $data = null) + { + return $this->executeRequest('DELETE', $url, $data); + } + + + + //Get Account Info + public function GetAccountInfo() + { + return $this->get('account'); + } + + //List prices + public function GetPrices() + { + return $this->get('price'); + } + + //List available zones + public function GetZones() + { + return $this->get('zone'); + } + + //List timezones + public function GetTimezones() + { + return $this->get('timezone'); + } + + //List available plans + public function GetPlans() + { + return $this->get('plan'); + } + + //List server configurations + public function GetServerConfigurations() + { + return $this->get('server_size'); + } + + //List servers + public function GetAllServers() + { + return $this->get('server'); + } + + //Get server details + public function GetServer($ServerUUID) + { + return $this->get('server/' . $ServerUUID); + } + + //Create server - Getting templates + public function GetTemplate() + { + return $this->get('storage/template'); + } + + //Create server - Creating from a template + public function CreateServer($ZoneID, $Hostname, $Plan, $OsUUID, $sshKey, $user_data, $backup, $networking, $ram = null, $vcpu = null, $storage = null) +{ +$Templates = $this->GetTemplate()['response']['storages']['storage']; +foreach ($Templates as $Template){ +if ($Template['uuid'] == $OsUUID){ +$TemplateTitle = $Template['title']; +$TemplateUUID = $Template['uuid']; +break; +} +} + +if(($Plan == "custom") && isset($ram) && isset($vcpu) && isset($storage)){ + + $postData = [ + 'server' => [ + 'metadata' => 'yes', + 'zone' => $ZoneID, // GetZones() + 'title' => $Hostname, // hostname + 'hostname' => $Hostname, // hostname + 'remote_access_enabled' => 'yes', + //"simple_backup" => "0430,weeklies", + "core_number" => (int)$vcpu, + "memory_amount" => (int)"1024" * (int)$ram, + 'storage_devices' => [ + 'storage_device' => [ + [ + 'action' => 'clone', + 'storage' => $TemplateUUID, // GetTemplates() + 'size' => $storage, + 'tier' => "maxiops", + 'title' => $TemplateTitle, // OS Name + ] + ] + ] + ] + ]; + +} else { + + $AllPlans = $this->Getplans()['response']['plans']['plan']; + foreach ($AllPlans as $Plans){ + if ($Plans['name'] == $Plan){ + $PlanName = $Plans['name']; + $PlanSize = $Plans['storage_size']; + $PlanTier = $Plans['storage_tier']; + break; + } + } + + $postData = [ + 'server' => [ + 'metadata' => 'yes', + 'zone' => $ZoneID, // GetZones() + 'title' => $Hostname, // hostname + 'hostname' => $Hostname, // hostname + 'plan' => $PlanName, // Getplans() + // "simple_backup" => "0430,weeklies", + 'remote_access_enabled' => 'yes', + 'storage_devices' => [ + 'storage_device' => [ + [ + 'action' => 'clone', + 'storage' => $TemplateUUID, // GetTemplates() + 'size' => $PlanSize, // storage_size from Getplans() + 'tier' => $PlanTier, // storage_tier from Getplans() + 'title' => $TemplateTitle, // OS Name + ] + ] + ] + ] + ]; + +} + +if($sshKey != "na"){ + $postData['server']['login_user'] = [ + 'username' => 'root', + 'ssh_keys' => [ + 'ssh_key' => [ + $sshKey, + ], + ], + ]; +} + +if($networking == "ipv4only"){ + $postData['server']['networking'] = [ + "interfaces" => [ + "interface" => [ + [ + "ip_addresses" => [ + "ip_address" => [ + [ + "family" => "IPv4" + ] + ] + ], + "type" => "public" + ] + ] + ] +]; +} + +if ($user_data != "na") { + $postData['server']['user_data'] = $user_data; +} + +if ($backup != "no") { + $postData['server']['simple_backup'] = "0100,".$backup; +} + + return $this->post('server', $postData); +} + +public function serverOperation($action, $ServerUUID, $stop_type = null) { + $data = array(); + + if ($stop_type !== null) { + $data[$action.'_server']['stop_type'] = $stop_type; + $data[$action.'_server']['timeout'] = "60"; + } + return $this->post('server/'.$ServerUUID.'/'.$action, $data); +} + +public function StartServer($ServerUUID) { + return $this->serverOperation('start', $ServerUUID); +} + +public function StopServer($ServerUUID) { + return $this->serverOperation('stop', $ServerUUID, 'hard'); +} + +public function RestartServer($ServerUUID) { + return $this->serverOperation('restart', $ServerUUID, 'hard'); +} + +public function CancelServer($ServerUUID) { + return $this->serverOperation('cancel', $ServerUUID); +} + +public function DeleteServer($ServerUUID) { + return $this->delete('server/'.$ServerUUID); +} + +public function ModifyServer($uuid, $Plan) +{ + $this->stopServerAndWait($uuid); + + $allPlans = $this->Getplans()['response']['plans']['plan']; + foreach ($allPlans as $plan) { + if ($plan['name'] == $Plan) { + $planSize = $plan['storage_size']; + break; + } +} + + $upgradePlan = $this->put('server/' . $uuid, ['server' => ['plan' => $Plan]]); + + if($upgradePlan['response']['error']['error_message']){ + return $upgradePlan; + } else { + $storages = $this->GetServer($uuid)['response']['server']['storage_devices']['storage_device']; + foreach ($storages as $storage) { + if ($storage['part_of_plan'] == "yes") { + $storageId = $storage['storage']; + $existingStorageSize = $storage['storage_size']; + break; + } + } + if ($storageId && $planSize > $existingStorageSize) { + $modeyStorage = $this->modifyStorage($storageId, $planSize); + $this->StartServer($uuid); + return $modeyStorage; + } + } + +} + + +public function modifyStorage($storageId, $planSize) +{ + $body = [ + 'storage' => [ + 'size' => $planSize + ] + ]; +return $this->put('storage/'.$storageId, $body); +} + +//Delete server and storage + public function DeleteServernStorage($ServerUUID) +{ + $this->stopServerAndWait($ServerUUID); +return $this->delete('server/'.$ServerUUID.'?storages=1'); +} + +public function DeleteServernStorageBackup($ServerUUID) +{ + $this->stopServerAndWait($ServerUUID); +return $this->delete('server/'.$ServerUUID.'?storages=1&backups=delete'); +} + +public function stopServerAndWait($ServerUUID) +{ + $result = $this->GetServer($ServerUUID); + $state = $result['response']['server']['state']; + + if ($state == 'started') { + $this->StopServer($ServerUUID); + } + + $times = 0; + // If VM delete takes time please increase it but when tested working within 45 second + while ($state != 'stopped' && $times < 45) { + sleep(2); + $result = $this->GetServer($ServerUUID); + $state = $result['response']['server']['state']; + ++$times; + } + + if ($state != 'stopped') { + throw new \Exception('Can not stop server, taking time to response'); + } +} + +public function GetIPaddress( $IPAddress ) +{ +return $this->get('ip_address/'.$IPAddress); +} + +public function ModifyIPaddress($instanceId, $IP, $ptr_record) +{ + if (!($this->GetIPaddress($IP)['response']['ip_address']['server'] == $instanceId)) { + throw new \Exception('IP does not belong to your server'); + } +return $this->put('ip_address/'.$IP, array('ip_address' => array('ptr_record' => $ptr_record))); +} + + +public function vncPasswordUpdate($instanceId, $vncPass) +{ +return $this->put('server/'.$instanceId, array('server' => array('remote_access_password' => $vncPass))); +} + +public function vncEnableDisable($instanceId, $vncType) +{ +return $this->put('server/'.$instanceId, array('server' => array('remote_access_enabled' => $vncType))); +} + +public function modifyVPS($instanceId, $serverConfig) +{ +return $this->put('server/'.$instanceId, $serverConfig); +} + +public function formatSizeBytestoTB($bytes) +{ + return round($bytes / 1024 / 1024 / 1024 / 1024,2); +} + +public function formatSizeBytestoMB($bytes) +{ + return round($bytes / 1024 / 1024, 2); +} + +public function formatSizeBytestoGB($bytes) +{ + return round($bytes / 1024 / 1024 / 1024, 2); +} + +public function formatSizeMBtoGB($MB) +{ + return round($MB / 1024); +} + +public function formatBytes($bytes, $precision = 2) { +$unit = ["B", "KB", "MB", "GB", "TB"]; +$exp = floor(log($bytes, 1024)) | 0; +return round($bytes / (pow(1024, $exp)), $precision).' '.$unit[$exp]; +} + +public function CurrencyConvert($fromCurrency, $toCurrency, $amount) +{ +$amount_from_db = Capsule::table('tblcurrencies')->where('code', $toCurrency)->get(); +$exhangeRate_whmcs = json_decode($amount_from_db, true)[0]['rate']; +$convertedAmount = $amount * $exhangeRate_whmcs; +return array( 'convertedAmount' => round($convertedAmount, 2) ); +} + + +} diff --git a/modules/servers/upCloudVps/lib/usageUpdate.php b/modules/servers/upCloudVps/lib/usageUpdate.php new file mode 100644 index 0000000..471dfc8 --- /dev/null +++ b/modules/servers/upCloudVps/lib/usageUpdate.php @@ -0,0 +1,58 @@ +manager = new upCloudVps($params); + $this->params = $params; + } + + public function usage(){ + $services = Capsule::table('tblhosting') + ->join('tblproducts', 'tblproducts.id', '=', 'tblhosting.packageid') + ->where('tblhosting.domainstatus', 'Active') + ->where('tblhosting.server', $this->params['serverid']) + ->where('tblproducts.servertype', 'upCloudVps')->get(['tblhosting.id','tblhosting.packageid']); + + foreach ($services as $whmcsData) { + $serviceId = $whmcsData->id; + $packageId = $whmcsData->packageid; + + $tblcustomfields = Capsule::table('tblcustomfields') + ->where('tblcustomfields.relid', $packageId) + ->where('tblcustomfields.fieldname', 'instanceId|instance Id')->value('id'); + + $instanceId = Capsule::table('tblcustomfieldsvalues') + ->where('tblcustomfieldsvalues.relid', $serviceId) + ->where('tblcustomfieldsvalues.fieldid', $tblcustomfields)->value('value'); + + $details = $this->manager->GetServer($instanceId)['response']['server']; + $Outgoing = $this->manager->formatSizeBytestoGB($details['plan_ipv4_bytes'] + $details['plan_ipv6_bytes']); + + foreach ($this->manager->Getplans()['response']['plans']['plan'] as $Plan){ + if($Plan['name'] == $details['plan'] and $Plan['memory_amount'] == $details['memory_amount']){ + $TotalTraffic = $Plan['public_traffic_out'] * 1000 ; + } + } + $Outgoing = $this->manager->formatSizeBytestoMB($details['plan_ipv4_bytes'] + $details['plan_ipv6_bytes']); + Capsule::table('tblhosting') + ->where('server', $this->params['serverid']) + ->where('id', $serviceId) + ->where('packageid', $packageId) + ->update([ + 'bwusage' => $Outgoing, + 'bwlimit' => $TotalTraffic, + 'lastupdate' => Capsule::raw('now()'), + ]); + } + } + +} diff --git a/modules/servers/upCloudVps/lib/vmManager.php b/modules/servers/upCloudVps/lib/vmManager.php new file mode 100644 index 0000000..76cc6da --- /dev/null +++ b/modules/servers/upCloudVps/lib/vmManager.php @@ -0,0 +1,129 @@ +manager = new upCloudVps($params); + $this->params = $params; + $this->_LANG = Helper::getLang(); + } + + public function stop(){ + $instanceId = $this->params['model']->serviceProperties->get('instanceId|instance Id'); + $getInstamceState = $this->manager->GetServer($instanceId)['response']; + if($getInstamceState['server']['state'] == 'started'){ + $stop = $this->manager->StopServer($instanceId); + sleep(45); + return isset($stop['response']['error']['error_message']) ? $stop['response']['error']['error_message'] : 'success'; + } +} + + public function start(){ + $instanceId = $this->params['model']->serviceProperties->get('instanceId|instance Id'); + $start = $this->manager->StartServer($instanceId); + sleep(45); + return isset($start['response']['server']['host']) ? 'success' : $start['response']['error']['error_message']; + } + + public function reboot(){ + $instanceId = $this->params['model']->serviceProperties->get('instanceId|instance Id'); + $reboot = $this->manager->RestartServer($instanceId); + return isset($reboot['response']['error']['error_message']) ? $reboot['response']['error']['error_message'] : 'success'; + } + + public function terminate(){ + $instanceId = $this->params['model']->serviceProperties->get('instanceId|instance Id'); + $delete = ($this->params['configoptions']['backup'] == "no") ? $this->manager->DeleteServernStorage($instanceId) : $this->manager->DeleteServernStorageBackup($instanceId); + if($delete['response_code'] == '204'){ + $this->params['model']->serviceProperties->save(['instanceId|instance Id' => ""]); + Capsule::table('mod_upCloudVps_bandwidth')->where('serviceId', '=', $this->params['serviceid'])->delete(); + $message = "success"; + } else { + $message = $delete['response']['error']['error_message']; + } + return $message ; + } + + public function create(){ + $zone = $this->params['configoptions']['location'] ?? $this->params['configoption1']; + $Plan = $this->params['configoption2']; + $OsUUID = $this->params['configoptions']['template'] ?? $this->params['configoption3']; + $sshkey = $this->params['customfields']['ssh_key']; + $user_data = $this->params['customfields']['userData']; + $Hostname = $this->params['domain'] ?? 'client' . $this->params['serviceid'] . '.' . $_SERVER['SERVER_NAME']; + $sshkey = empty($sshkey) ? "na" : $sshkey; + $user_data = empty($user_data) ? "na" : $user_data; + $backup = $this->params['configoptions']['backup']; + $networking = $this->params['configoptions']['ipaddress']; + + if($Plan == "custom"){ + $ram = $this->params['configoptions']['ram']; + $vcpu = $this->params['configoptions']['vcpu']; + $storage = $this->params['configoptions']['storage']; + $actionResponse = $this->manager->CreateServer($zone, $Hostname, $Plan, $OsUUID, $sshkey, $user_data, $backup, $networking, $ram, $vcpu, $storage); + } else { + $actionResponse = $this->manager->CreateServer($zone, $Hostname, $Plan, $OsUUID, $sshkey, $user_data, $backup, $networking); + } + if($actionResponse['response_code'] == '202'){ + + foreach ($actionResponse['response']['server']['ip_addresses']['ip_address'] as $IPList) { + if ($IPList['access'] == "public") { + if ($IPList['family'] == "IPv4" && ($IPList['part_of_plan'] || $Plan == "custom")) { + $IPv4 = $IPList['address']; + } elseif ($IPList['family'] == "IPv6") { + $IPv6 = $IPList['address']; + } + } + } + + $postData = [ + 'serviceid' => $this->params['serviceid'], + 'serviceusername' => $actionResponse['response']['server']['username'], + 'dedicatedip' => $IPv4, + 'servicepassword' => $actionResponse['response']['server']['password'], + 'assignedips' => $IPv6, + 'domain' => $actionResponse['response']['server']['hostname'], + ]; + localAPI('UpdateClientProduct', $postData); + + $this->params['model']->serviceProperties->save(['instanceId|instance Id' => $actionResponse['response']['server']['uuid']]); + + $message = 'success'; + } else { + $message = $actionResponse['response']['error']['error_message']; + } + return $message; + } + + public function rdns(){ + $ip = $this->params['ip']; + $DNSValue = $this->params['rdns']; + $instanceId = $this->params['model']->serviceProperties->get('instanceId|instance Id'); + $data = $this->manager->ModifyIPaddress($instanceId, $rDNSIP, $DNSValue); + return $data['response_code'] == "202" ? 'success' : $data['response']['error']['error_message']; + } + + public function upgradePlan(){ + $Plan = $this->params['configoption2']; + if($Plan == "custom"){ + return []; + } else { + $instanceId = $this->params['model']->serviceProperties->get('instanceId|instance Id'); + $data = $this->manager->ModifyServer($instanceId, $Plan); + return isset($data['response']['error']['error_message']) ? $data['response']['error']['error_message'] : 'success'; + } + } + +} diff --git a/modules/servers/upCloudVps/templates/assets/css/layout.css b/modules/servers/upCloudVps/templates/assets/css/layout.css new file mode 100644 index 0000000..d0cf63a --- /dev/null +++ b/modules/servers/upCloudVps/templates/assets/css/layout.css @@ -0,0 +1,1041 @@ + +.module-main-header { + margin: 0 0 15px 0 !important; + height: 40px; + overflow: hidden; +} + +.module-main-header h2 { + display: inline; + margin: 0 0 0 5px; + text-shadow: none; +} + +.module-main-header .btn-back { + vertical-align: top; + padding: 0 12px; + box-shadow: none; +} + +.module-main-header .btn-back:hover { + color: #000; +} + +#uc-wrapper h1, +#uc-wrapper h2, +#uc-wrapper h3, +#uc-wrapper h4, +#uc-wrapper h5, +#uc-wrapper h6 { + margin: 0; + letter-spacing: 0; + text-shadow: none; +} + +#uc-wrapper ul { + padding: 0; + margin: 0; +} + +#uc-wrapper .mb-20 { + margin-bottom: 20px!important; +} + +#uc-wrapper .ml-10 { + margin-left: 10px!important; +} + +#uc-wrapper .mr-5 { + margin-right: 5px!important; +} + +#uc-wrapper .ml-5 { + margin-left: 5px!important; +} + +#uc-wrapper.module-container { + position: relative; +} + +#uc-wrapper [class^="icon-"], #uc-wrapper [class*=" icon-"] { + background-position: 0 0; + background-image: none; +} + +#uc-wrapper .buttons-content .big-button { + width: 100%; + display: block; + padding: 0; + box-shadow: 0 0 5px rgba(0,0,0,0.1); + position: relative; + margin-bottom: 20px; +} + +#uc-wrapper .buttons-content .big-button.disabled { + position: relative; +} + +#uc-wrapper .buttons-content .big-button.disabled span { + opacity: 0; +} + +#uc-wrapper .buttons-content .big-button.disabled .fa-spinner { + position: absolute; + left: 50%; + top: 50%; + margin: -6px 0 0 6px; +} + +@media (max-width: 768px) { + + #uc-wrapper .buttons-content .big-button.disabled .fa-spinner { + left: 50%; + top: 72%; + margin: 0 0 0 -7px; + } + +} + +#uc-wrapper .buttons-content .big-button .icon-btn { + width: 38px; + height: 38px; + background-repeat: no-repeat; + background-size: 100%; + background-position: center; +} + +#uc-wrapper .panel-heading { + display: block; +} + +#uc-wrapper .tab-pane { + padding: 20px 10px; +} + +@media (min-width: 769px) { + + #uc-wrapper .buttons-content .big-button div { + padding-left: 60px; + display: table-cell; + height: 60px; + vertical-align: middle; + padding-right: 10px; + } + + #uc-wrapper .buttons-content .big-button .icon-btn { + position: absolute; + left: 13px; + top: 12px; + } + +} + +@media (max-width: 768px) { + + #uc-wrapper.module-container { + min-height: 0!important; + } + + #uc-wrapper .buttons-content .big-button div { + padding: 10px; + text-align: center; + } + + #uc-wrapper .buttons-content .big-button .icon-btn { + display: block; + margin: 0 auto; + } + +} + +#uc-wrapper .module-sidebar { + position: absolute; + top: 0; + left: 0; + width: 210px; +} + +#uc-wrapper .module-sidebar .sidebar-header { + margin-bottom: 13px; +} + +#uc-wrapper .module-sidebar .sidebar-header h3 { + display: inline-block; + margin: 0 0 0 8px; + box-shadow: none; +} + +#uc-wrapper .module-sidebar .sidebar-header .show-hint { + font-size: 20px; + float: right; + margin-top: 4px; +} + +#uc-wrapper .module-sidebar .module-menu { + list-style: none; + margin: 0; +} + +#uc-wrapper .module-sidebar .module-menu li a { + position: relative; + min-height: 40px; + padding: 5px 5px 5px 45px; + margin-bottom: 1px; + display: block; +} + +#uc-wrapper .module-sidebar .module-menu .icon-menu { + position: absolute; + left: 5px; + top: 5px; + width: 30px; + height: 30px; + background-size: 100%; + background-repeat: no-repeat; + background-position: center center; +} + +#uc-wrapper .module-sidebar .btn.cp-sidebar-toggle { + height: 30px; + width: 30px; + padding: 4px 8px; + vertical-align: top; +} + +#uc-wrapper .module-sidebar .cp-sidebar-toggle .icon-bar { + display: block; + height: 2px; + width: 20px; + margin: 0 0 4px 0; +} + +#uc-wrapper .module-sidebar .cp-sidebar-toggle .icon-bar:last-of-type { + margin-bottom: 0; +} + +@media (min-width: 769px) { + + #uc-wrapper .module-menu { + display: block!important; + visibility: visible!important; + height: 100%!important; + overflow: visible; + } + + #uc-wrapper.sidebar-closed .module-menu a:hover { + width: 220px; + border-radius: 4px; + } + + #uc-wrapper.sidebar-closed .module-sidebar { + width: 45px; + z-index: 100; + } + + #uc-wrapper.sidebar-closed .module-sidebar a span { + display: none; + } + + #uc-wrapper.sidebar-closed .module-sidebar a:hover span { + display: block; + } + + #uc-wrapper.sidebar-closed .sidebar-header h3 { + display: none; + } + +} + +@media (max-width: 768px) { + + #uc-wrapper .module-sidebar { + position: relative; + width: 100%; + margin-bottom: 20px; + } + +} + +/* module content */ +#uc-wrapper .module-content { + margin-left: 220px; +} + +#uc-wrapper .module-sub-header, +#uc-wrapper .module-header { + position: relative; + margin-bottom: 33px; +} + +#uc-wrapper .module-header { + padding-left: 80px; +} + +#uc-wrapper .module-sub-header h2, +#uc-wrapper .module-header h1 { + margin-bottom: 5px; +} + +#uc-wrapper .module-header.sub-page { + padding-left: 0; +} + +#uc-wrapper .module-header .btn-back { + font-size: 14px; + padding: 10px; + vertical-align: top; + line-height: 1; + display: inline-block; + margin-right: 5px; + color: #45464C; +} + +#uc-wrapper .module-header .icon-header { + position: absolute; + left: 0; + top: 5px; + width: 58px; + height: 58px; +} + +@media (min-width: 769px) { + + #uc-wrapper.sidebar-closed .module-content { + margin-left: 55px; + } + +} + +@media (max-width: 768px) { + + #uc-wrapper .module-content { + margin: 0; + } + + #uc-wrapper .module-header { + position: absolute; + display: none; + } + +} + +#uc-wrapper .module-body h4 { + margin-bottom: 15px; +} + +/* forms */ +#uc-wrapper form { + margin: 0; +} + +#uc-wrapper .form-control { + height: 36px; + padding: 6px 10px; + margin: 0; +} + +#uc-wrapper .form-horizontal .radio, #uc-wrapper .form-horizontal .checkbox, #uc-wrapper .form-horizontal .radio-inline, #uc-wrapper .form-horizontal .checkbox-inline { + padding: 0; +} + +#uc-wrapper input[type="radio"], +#uc-wrapper input[type="checkbox"] { + margin-top: 8px; +} + +#uc-wrapper .form-fluid span:not(.input-group-addon), +#uc-wrapper .form-group span:not(.input-group-addon) { + padding: 0 5px 0 0; +} + +#uc-wrapper .well .form-actions { + margin: 20px -19px 0; + padding: 20px 20px 0 20px; +} + +#uc-wrapper .help-block { + margin-bottom: 0; +} + +#uc-wrapper .help-block:empty { + display: none; +} + +@media (max-width: 992px) { + + #uc-wrapper label.control-label.col-md-3 { + width: 100%; + float: none; + text-align: left; + margin-bottom: 5px; + } + + #uc-wrapper label.control-label.col-md-3:empty { + display: none; + } + +} + +@media (max-width: 768px) { + + #uc-wrapper label.control-label { + width: 100%; + float: none; + text-align: left; + } + +} + +@media (min-width: 768px) { + + #uc-wrapper label.control-label { + padding-top: 9px!important; + } + +} + +#uc-wrapper .advanced-options .options-body { + padding: 20px; +} + +/* pw strength box */ +#uc-wrapper #pwstrengthbox { + color: white; + border-radius: 3px; + background-color: #F00; + text-align: center; + line-height: 26px; + width: 100%; +} + +/* buttons */ +#uc-wrapper .btn { + border-radius: 3px; + height: 36px; +} + +#uc-wrapper .btn:not(.btn-icon) { + min-width: 60px; +} + +#uc-wrapper .btn-sm { + height: 28px; + line-height: 18px!important; +} + +#uc-wrapper .btn-sm span { + line-height: 18px!important; + vertical-align: top; +} + +#uc-wrapper .btn.btn-icon { + position: relative; + height: 22px; + width: 22px; + padding: 0; + line-height: 22px; + text-align: center; +} + +#uc-wrapper .dropdown .btn.btn-icon { + width: 30px; +} + +#uc-wrapper .btn + .btn { + margin-left: 10px; +} + +@media (max-width: 500px) { + + #uc-wrapper .btn-block-mobile { + margin-left: 0!important; + width: 100%; + } + + #uc-wrapper .btn-block-mobile + .btn-block-mobile { + margin-top: 10px; + } + +} + +/* helper classes */ +#uc-wrapper .module-body .section:not(:last-of-type) { + margin-bottom: 50px; +} + +#uc-wrapper .form-fluid, +#uc-wrapper .row-fluid { + display: table; + padding: 0; +} + +#uc-wrapper .form-fluid > .fluid-100, +#uc-wrapper .row-fluid > .fluid-100 { + position: relative; + display: table-cell; + width: 100%; + vertical-align: top; +} + +#uc-wrapper .form-fluid > .fluid-100 > *, #uc-wrapper .row-fluid > .fluid-100 > * { + width: 100%; +} + +#uc-wrapper .form-fluid > .fluid-0, #uc-wrapper .row-fluid > .fluid-0 { + position: relative; + float: none; + display: table-cell; + width: 0; + padding: 0; + white-space: nowrap; + vertical-align: top; +} + +#uc-wrapper .form-fluid > .fluid-0 .form-control, #uc-wrapper .row-fluid > .fluid-0 .form-control { + min-width: 55px; +} + +@media (min-width: 768px) { + + #uc-wrapper .form-fluid-sm, + #uc-wrapper .row-fluid-sm { + display: table; + padding: 0; + } + + #uc-wrapper .form-fluid-sm > .fluid-100, + #uc-wrapper .row-fluid-sm > .fluid-100 { + position: relative; + display: table-cell; + width: 100%; + vertical-align: top; + } + + #uc-wrapper .form-fluid-sm > .fluid-100 > *, + #uc-wrapper .row-fluid-sm > .fluid-100 > * { + width: 100%; + } + + #uc-wrapper .form-fluid-sm > .fluid-0, + #uc-wrapper .row-fluid-sm > .fluid-0 { + position: relative; + float: none; + display: table-cell; + width: 0; + padding: 0; + white-space: nowrap; + vertical-align: top; + } + + #uc-wrapper .form-fluid-sm > .fluid-0 .form-control, + #uc-wrapper .row-fluid-sm > .fluid-0 .form-control { + min-width: 55px; + } + +} + +#uc-wrapper .table-header { + display: table; + width: 100%; + line-height: 14px; + margin-bottom: 8px; +} + +#uc-wrapper .table-header > .header-title { + display: table-cell; + width: 0%; + white-space: nowrap; + vertical-align: middle; +} + +#uc-wrapper .table-header > .header-actions { + display: table-cell; + width: 100%; +} + +#uc-wrapper .table-header .header-search { + float: right; +} + +#uc-wrapper .table-header .header-title > h4 { + float: left; + margin: 0 20px 0 0; + line-height: 36px; +} + +#uc-wrapper .input-icon { + position: relative; + display: inline-block; + vertical-align: top; +} + +#uc-wrapper .input-icon > .fa { + position: absolute; + left: 15px; + top: 50%; + margin-top: -7px; +} + +#uc-wrapper .table-header .header-search input { + width: 240px; +} + +#uc-wrapper .input-icon > .form-control { + padding-left: 40px; +} + + +#uc-wrapper .table > tbody > tr > td ul { + list-style: none; +} + +#uc-wrapper .table > tbody > tr > td ul li { + line-height: 22px; +} + +#uc-wrapper .table input[type="radio"], +#uc-wrapper .table input[type="checkbox"] { + margin-top: 0; +} + +#uc-wrapper .table-app img { + width: 22px; + margin: -4px 10px 0 0; + vertical-align: top; +} + +#uc-wrapper .pr-10 { + padding-right: 10px!important; +} + +#uc-wrapper .row { + padding: 0; +} + +#uc-wrapper .form-horizontal .row, +#uc-wrapper .form-horizontal .form-group { + margin-left: -5px; + margin-right: -5px; +} + +#uc-wrapper .form-horizontal [class*="col-"] { + padding-left: 5px; + padding-right: 5px; +} + +#uc-wrapper .file-manager .btn { + height: auto; + padding: 6px 12px; + float: left; + line-height: 22px; +} + +@media (max-width: 500px) { + + #uc-wrapper .file-manager .btn { + padding: 6px 5px; + } + +} + +#uc-wrapper .file-manager .dropdown { + margin-left: 10px; +} + +#uc-wrapper .file-manager .dropdown-menu li { + margin: 0 10px 0 0 +} + +#uc-wrapper .file-manager .btn i { + display: block; + margin: 5px auto; +} + +#uc-wrapper .breadcrumb { + margin: 15px 0 5px 0; +} + +#uc-wrapper .breadcrumb > .fa-folder-o { + margin-right: 5px; +} + +#uc-wrapper .dropdown-right { + left: auto; + right: 0; +} + +#uc-wrapper .btn .caret { + margin-top: 0; + opacity: 1; + margin-left: 5px; +} + +#uc-wrapper table .dropdown-menu > li > a > .fa { + margin-right: 7px; + width: 14px; +} + +@media (min-width: 992px) { + + #uc-wrapper .dropdown-mobile .dropdown-menu { + overflow: hidden; + float: none; + display: block; + position: relative; + left: auto; + top: auto; + border: 0; + box-shadow: none; + background: none; + margin: 0; + padding: 0; + } + + #uc-wrapper .dropdown-mobile .dropdown-menu li { + float: left; + } + +} + +@media (max-width: 991px) { + + #uc-wrapper .dropdown-mobile .dropdown-menu li { + float: none; + margin: 0; + } + + #uc-wrapper .dropdown-mobile .dropdown-menu li .btn { + float: none; + border: 0; + padding: 3px 20px; + text-align: left; + } + + #uc-wrapper .dropdown-mobile .dropdown-menu li .btn i { + display: inline; + margin-right: 5px; + } + +} + +#uc-wrapper .modal { + width: 100%; + margin: 0; + background: none; +} + +#uc-wrapper .modal .modal-header { + word-break: break-all; +} + +#uc-wrapper .modal .modal-title { + margin-bottom: 0; +} + +#uc-wrapper .modal-body { + max-height: 600px; +} + +#uc-wrapper .btn.disabled { + position: relative; +} + +#uc-wrapper .btn.disabled :not(i) { + opacity: 0; +} + +#uc-wrapper .btn.disabled .fa-spinner { + position: absolute; + left: 50%; + top: 50%; + margin: -6px 0 0 -6px; +} + +#uc-wrapper .btn.btn-icon.disabled .fa-spinner { + margin: -11px 0 0 -7px; +} + +@media (max-width: 768px) { + + #uc-wrapper .modal-large .modal-dialog { + width: auto; + } + +} + +@media (min-width: 768px) { + + #uc-wrapper .modal-large .modal-dialog { + width: 750px; + } + +} + +@media (min-width: 992px) { + + #uc-wrapper .modal-large .modal-dialog { + width: 960px; + } + +} + +#uc-wrapper .modal .modal-backdrop { + position: fixed; + top: 0; + right: 0; + bottom: 0; + left: 0; + z-index: 1040; + background-color: #000; +} + +@media (min-width: 992px) { + + .show-md { + display: none!important; + } + +} + +@media (min-width: 768px) { + + .show-sm { + display: none!important; + } + +} + +@media (max-width: 768px) { + + .hide-sm { + display: none!important; + } + +} + +/* table grid */ +#uc-wrapper .table tbody tr td { + word-break: break-all; +} + +@media (min-width: 768px) { + + #uc-wrapper .table tbody tr td.no-wrap, + #uc-wrapper #cron-table tr td:first-of-type, + #uc-wrapper .table tbody tr td.cell-actions { + white-space: nowrap; + } + +} + +@media (max-width: 650px) { + + #uc-wrapper .table-header > .header-title { + display: block; + } + + #uc-wrapper .table-header > .header-actions { + display: block; + margin-bottom: 12px; + overflow: hidden; + } + + #uc-wrapper .table-header .header-search { + float: none; + } + + #uc-wrapper .table-header .input-icon { + display: block; + } + + #uc-wrapper .table-header .header-search input { + width: 100%; + } + + #uc-wrapper .table-header .header-actions .btn { + float: none!important; + } + +} + +@media (max-width: 768px) { + + #uc-wrapper .table thead { + display: none; + } + + #uc-wrapper .table tbody tr { + border-top: 1px solid #E4E8F0; + } + + #uc-wrapper .table .cell-sm-1, + #uc-wrapper .table .cell-sm-2, + #uc-wrapper .table .cell-sm-3, + #uc-wrapper .table .cell-sm-4, + #uc-wrapper .table .cell-sm-5, + #uc-wrapper .table .cell-sm-6, + #uc-wrapper .table .cell-sm-7, + #uc-wrapper .table .cell-sm-8, + #uc-wrapper .table .cell-sm-9, + #uc-wrapper .table .cell-sm-10, + #uc-wrapper .table .cell-sm-11, + #uc-wrapper .table .cell-sm-12 { + display: block; + float: left; + } + + #uc-wrapper .table .cell-sm-12 { + width: 100%; + } + + #uc-wrapper .table .cell-sm-11 { + width: 91.66666667%; + } + + #uc-wrapper .table .cell-sm-10 { + width: 83.33333333%; + } + + #uc-wrapper .table .cell-sm-9 { + width: 75%; + } + + #uc-wrapper .table .cell-sm-8 { + width: 66.66666667%; + } + + #uc-wrapper .table .cell-sm-7 { + width: 58.33333333%; + } + + #uc-wrapper .table .cell-sm-6 { + width: 50%; + } + + #uc-wrapper .table .cell-sm-5 { + width: 41.66666667%; + } + + #uc-wrapper .table .cell-sm-4 { + width: 33.33333333%; + } + + #uc-wrapper .table .cell-sm-3 { + width: 25%; + } + + #uc-wrapper .table .cell-sm-2 { + width: 16.66666667%; + } + + #uc-wrapper .table .cell-sm-1 { + width: 8.33333333%; + } + + #uc-wrapper .table > tbody > tr > td, #uc-wrapper .table > tfoot > tr > td { + line-height: 1.2; + height: auto; + border: 0; + } + + #uc-wrapper .table tr td:not(.cell-checkbox):before { + content: attr(data-label); + padding-right: 10px; + color: #A1A5B3; + } + + #uc-wrapper .table > tbody > tr > td.cell-actions { + text-align: left!important; + } + + #uc-wrapper .table > tbody > tr > td.cell-actions .btn-icon { + width: 35px; + } + + #uc-wrapper .table > tbody > tr > td.cell-actions .btn-icon .fa { + font-size: 18px!important; + } + + #uc-wrapper .table.table-checkbox tbody tr { + padding-left: 25px; + position: relative; + display: inline-block; + } + + #uc-wrapper .table > tbody > tr > .cell-checkbox { + position: absolute; + left: 0; + top: 0; + width: 34px; + height: 100%; + } + + #uc-wrapper .table > tbody > tr > .cell-checkbox input[type="checkbox"] { + margin: 0; + } + + #uc-wrapper .table > tbody > tr > td.cell-actions .dropdown-menu { + left: 0; + right: auto; + } + +} + +#uc-wrapper .alert { + overflow: hidden; +} + +#uc-wrapper .alert .close { + right: 0; +} + +#uc-wrapper .tab-content { + overflow: visible; + position: relative; +} + +#uc-wrapper .tab-content .collapse { + position: static; + overflow: visible; +} + +#uc-wrapper a.panel-heading.collapsed .fa-minus { + display: none; +} + +#uc-wrapper a.panel-heading:not(.collapsed) .fa-plus { + display: none; +} + +#uc-wrapper #install-new-tab #collapse-toggle { + position: absolute; + right: 10px; + top: -40px; +} + +@media (max-width: 768px) { + + #uc-wrapper .tooltip-inner { + max-width: 500px; + } + + #uc-wrapper .sidebar-header .tooltip-inner, + #install-new-tab .tooltip-inner { + min-width: 300px; + } + + #uc-wrapper .tooltip-arrow { + display: none; + } + +} + +#uc-wrapper .checkbox-inline-td { + align-items: center; + display: flex; + margin-bottom: 0px !important; +} + +#uc-wrapper .checkbox-inline-td span { + margin-left: 2px; +} diff --git a/modules/servers/upCloudVps/templates/assets/css/theme.css b/modules/servers/upCloudVps/templates/assets/css/theme.css new file mode 100644 index 0000000..6278289 --- /dev/null +++ b/modules/servers/upCloudVps/templates/assets/css/theme.css @@ -0,0 +1,402 @@ + +.custom-input-group { + display: flex; + align-items: center; +} + +.custom-input-group label { + margin-right: 10px; /* Adjust as needed */ +} + +.custom-input-group input { + flex: 1; + padding: 5px; + border: 1px solid #ccc; /* Add any desired border styles */ +} +.loader { + position: fixed; + z-index: 9999; + top: 50%; + left: 50%; + display: inline-block; + width: 50px; + height: 50px; + border: 3px solid rgba(255, 255, 255, .3); + border-radius: 50%; + border-top-color: rgb(0, 0, 0); + animation: spin 1s ease-in-out infinite; + -webkit-animation: spin 1s ease-in-out infinite; +} + + +@keyframes spin { + to { -webkit-transform: rotate(360deg); } +} +@-webkit-keyframes spin { + to { -webkit-transform: rotate(360deg); } +} + +.upCloudTable td{ + vertical-align: middle !important; +} +.dataTables_length +{ + background:white !important; +} + +.dataTables_filter input { width: 300px !important } + + +.alert-fixed { + position:fixed; + top: 0px; + left: 0px; + width: 100%; + z-index:9999; + border-radius:0px +} + +.module-main-header { + border-bottom: 1px solid #DDD; +} + +.module-main-header h2 { + letter-spacing: 0; + font-family: "Helvetica Neue", helvetica, arial, sans-serif; + font-size: 22px; + font-weight: lighter; + line-height: 36px; +} + +.module-main-header .btn-back { + border: 0; + background: none; + font-size: 15px; + line-height: 36px; + color: #8A8E99; +} + +.module-main-header .btn-back:focus { + outline: none; +} + +.module-main-header .btn-back::-moz-focus-inner { + border: 0; +} + +#uc-wrapper { + font-family: "Helvetica Neue",helvetica,arial,sans-serif; + font-size: 14px; + line-height: 1.42857143; + color: #333; +} + +#uc-wrapper *:focus { + outline: none; +} + +#uc-wrapper *::-moz-focus-inner { + border: 0; +} + +#uc-wrapper .btn:hover, +#uc-wrapper .btn:active, +#uc-wrapper .btn:focus, +#uc-wrapper a:focus, +#uc-wrapper a:active, +#uc-wrapper a:hover, +#uc-wrapper a { + text-decoration: none!important; + outline: none!important; +} + +@media (min-width: 768px) { + + #uc-wrapper .text-center-sm { + text-align: center; + } + +} + +#uc-wrapper .buttons-content .big-button { + background: #FFF; + border: 0; + color: #337AB7; + cursor: pointer; + text-align: left; +} + +#uc-wrapper .buttons-content .big-button:hover { + color: #000; +} + +#uc-wrapper .form-group .fa-question-circle { + font-size: 16px; + color: #337AB7; + line-height: 36px; +} + +#uc-wrapper .module-sidebar .sidebar-header h3 { + font-size: 18px; + font-weight: 400; + line-height: 30px; +} + +#uc-wrapper .module-sidebar .btn.cp-sidebar-toggle { + background: none; + box-shadow: none; +} + +#uc-wrapper .module-sidebar .cp-sidebar-toggle .icon-bar { + background-color: #8A8E99; + border-radius: 0; +} + +#uc-wrapper .module-sidebar .module-menu li a { + color: #5C5E66; + background: #FFF; + line-height: 30px; +} + +#uc-wrapper .module-sidebar .module-menu li a:hover { + color: #000; +} + +@media (min-width: 768px) { + + #uc-wrapper .module-sidebar .module-menu li a:hover { + box-shadow: 0 0 5px rgba(0,0,0,0.1); + } + +} + +@media (max-width: 768px) { + + #uc-wrapper .module-sidebar .module-menu li a:hover { + background-color: #E6E6E6; + } + +} + +#uc-wrapper .module-sidebar .module-menu li.active a { + font-weight: 700; +} + +#uc-wrapper .module-content { + background-color: #FFF; +} + +#uc-wrapper .module-header .icon-header { + background-repeat: no-repeat; +} + +/* icons */ +#uc-wrapper .icon-boot { + background-image: url('../img/boot.png'); +} + +#uc-wrapper .icon-reboot { + background-image: url('../img/reboot.png'); +} + +#uc-wrapper .icon-stop { + background-image: url('../img/stop.png'); +} + +#uc-wrapper .icon-vnc { + background-image: url('../img/vnc.png'); +} + +#uc-wrapper .icon-network { + background-image: url('../img/network.png'); +} + +#uc-wrapper .icon-disks { + background-image: url('../img/disks.png'); +} + +#uc-wrapper .icon-graphs { + background-image: url('../img/graphs.png'); +} + + +#uc-wrapper .module-sub-header h2, +#uc-wrapper .module-header h1 { + font-size: 20px; + color: #45464C; + line-height: 34px; +} + +#uc-wrapper .module-body h4 { + font-size: 16px; +} + +/* forms */ +#uc-wrapper .form-group .btn span { + line-height: 1.5; +} + +#uc-wrapper .form-group span:not(.input-group-addon), +#uc-wrapper .form-fluid span:not(.input-group-addon) { + line-height: 36px; +} + +#uc-wrapper label:not(.control-label) { + font-weight: 400; + font-size: 12px; + line-height: 34px; +} + +#uc-wrapper label.control-label { + font-size: 12px; + font-weight: 800; +} + +#uc-wrapper .input-group .form-control.first { + border-radius: 4px 0 0 4px!important; +} + +#uc-wrapper .input-group .form-control.last { + border-radius: 0 4px 4px 0!important; +} + +#uc-wrapper .input-group-addon:not(.first):not(.last), +#uc-wrapper .input-group-btn:not(.first):not(.last), +#uc-wrapper .input-group .form-control:not(.first):not(.last) { + border-radius: 0; + border-width: 1px 0; +} + +#uc-wrapper .advanced-options { + /*background: rgba(0,0,0,0.05); + border-radius: 4px;*/ +} + +#uc-wrapper .well .form-actions { + border-top: 1px solid #E6E6E6; +} + +#uc-wrapper p { + line-height: 22px; +} + +/* tables */ +#uc-wrapper .table { + border-bottom: 1px solid #E4E8F0; +} + +#uc-wrapper .table > tbody > tr > td.cell-actions { + text-align: right; +} + +#uc-wrapper .table > tbody > tr > td.cell-actions > * { + display: inline-block; + margin: 0; +} + +#uc-wrapper .table > tbody > tr > td .btn-icon .fa { + font-size: 16px; + line-height: 22px; +} + +#uc-wrapper .table > tbody > tr > td.cell-actions .btn-icon .fa, +#uc-wrapper .table > tbody > tr > td.cell-actions .btn-icon .caret { + color: #8A8E99; +} + +#uc-wrapper .table tr th, +#uc-wrapper .table tr td { + border-color: #E4E8F0; +} + +#uc-wrapper .table thead tr th { + font-size: 11px; + text-transform: uppercase; + font-weight: 800; + color: #A1A5B3; +} + +#uc-wrapper .table tr td { + font-size: 12px; + line-height: 22px; +} + +#uc-wrapper .table tr td a { + cursor: pointer; +} + +/* buttons */ +#uc-wrapper .btn { + font-size: 12px; + line-height: 22px; +} + +#uc-wrapper .btn.btn-icon, #uc-wrapper .btn.btn-icon:hover, #uc-wrapper .btn.btn-icon:active, #uc-wrapper .btn-group.open .btn.btn-icon { + background: transparent; + border: none; + box-shadow: none; + border-radius: 0; +} + +#uc-wrapper .fa-question-circle { + cursor: pointer; +} + +#uc-wrapper a.panel-heading h6 { + color: #5C5E66; +} + +#uc-wrapper a.panel-heading:hover h6 { + color: #000; +} + +#UCLoader { + background-color: rgba(0, 0, 0, 0.1); + left: 0; + top: 0; + position: fixed; + min-width: 100%; + min-height: 100%; + z-index: 60000; +} + +#UCLoader img { + position: absolute; + top: 50%; + left: 50%; + width: 300px; + margin-left: -150px; +} + +.modal-loader { + width: 100%; + height: 100px; + background: url("../img/ajax-loader.gif") no-repeat scroll 0 0 rgba(0, 0, 0, 0); + background-position: center; +} + +#uc-wrapper .icon-editvm { + background-image: url('../img/editvm.png'); +} + +#uc-wrapper .loader-small { + background: rgba(0, 0, 0, 0) url("../img/ajax-loader-small.gif") no-repeat scroll 0 0; + display: inline-block; + height: 16px; + margin-left: 4px; + position: absolute; + width: 16px; +} + +#uc-wrapper .icon-vps-details { + background-image: url('../img/vps-details.png'); +} + +#uc-wrapper .icon-vps-details { + background-image: url('../img/vps-details.png'); +} + + +#uc-wrapper .dataTables_filter input { + margin-left: 5px; +} diff --git a/modules/servers/upCloudVps/templates/assets/img/ajax-loader-small.gif b/modules/servers/upCloudVps/templates/assets/img/ajax-loader-small.gif new file mode 100644 index 0000000000000000000000000000000000000000..63570fe42f256acc62c1411ea1cebcbb27fb7cc6 GIT binary patch literal 673 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 E1D&+2IRF3v literal 0 HcmV?d00001 diff --git a/modules/servers/upCloudVps/templates/assets/img/disks.png b/modules/servers/upCloudVps/templates/assets/img/disks.png new file mode 100644 index 0000000000000000000000000000000000000000..c2cdb0aa0efb9b17715e3665c89428b2a94e07aa GIT binary patch literal 241739 zcmeFa37lMImH*ug4j_o)z93GqsEAbh?n?}Ty8=QWBw=%}HEBt@n_dXPkx4)nmk|*K z0by`O7EwV_1Qk(n2XR+WMgegd=yya1M+N@h=T>K-`&QR;>uKMc;S)7NI;m67d6u*O z&N**gdc?uI?)-wC2L=XqJ@k-x8UNJz)-M0 zqxOHw+jjoxj`!~Q!FQhip|juit-t;1O&>bp$2UFvo9{mQnV&4a|LTV>x$gEIZ{F!C z=O1DoWv_qA72%7X{-39R{j0bB>fQwhZPp7q$%eji@=n(fxVWxJav&h-x4Y5lVX4m#zX;YSA!O1E2o z$@^ZieBiPt4ZPvojru(UOREEKcI7Lyv7AGVsaI4%~d(Zo6DNu+vipw4=^G<7ER+ zdegugzOnb-Cl9>k!hvU{e>`^4f4}^i-R|Fyox1SaMK|o@CmUY9Q}x6hk3Dw5i}tW#>K{ZXbH&tC_M`ZbS!@Wl^4Y1Qj?xoq;`x3{l- z+CiTgxa@a7KlY(fu{X&F-gU#zesVvpZu2kUB5Z!;J<%%m&g9T#}D@U>Y?=; zj@$29_Z;<%6Wl<@ZB%_+Ka#cSo7C+@Bg>$7vF6UeDaNpw|mpM)sMZl zvHkxX_R`&so4EPc4-X9d@y3xa|8U=(wqL*M^k3b+_OYo0A36B47Z0p&ANuYS2L@gj z@3UaVx2`+z>pKk$#FuT@?~}pqfBN$C_P=?D7k>Gl@A~pCe}9AXmT;f1-V*K>?%Z7e z!m&5J?CR6Peb4;vC->Oz4TC>CXbo+;a5io_fIO)Bal9>C!Lnd((y| zKPmXePS33E`SY{4tH15Iz2CXhb1yvOFMEFZkS9Ik?fX=|dG@o8+Uu}$?L+isZ(C70 z`j9>Tc)-{{`DeW*{rtJF_{M;7+TYK->0tdm8?HU-fFC~PGtmtnb6@b5^n<&6uQ{P# zbk1`wyZC!!pMJps?{GeF|M#w1^|YU#vpD*?cJptax1;&h)!KqvzY>1R+yBYuHNO3+ zy{>-BRkyz2lgC`W{f%$^bK~|;t$ornHY7Jc*?H31uu^+baL93oA9uu05BXT-h5J}f z|M`>MCvX3jqi%WWUgv%6uus3_^H2ZZuRgDC=w}^w$o_l(_!;Lsb8xpeogIDE-RBu^ zjf|Jx@UDM4^iv;R@%?9fKmGpjvb}$|{}mTohrD3#^S=4><_XvC=)Y(G%FDlT#=V!^ z`;~jmdw0Kg&p*C;=S%7bo%rFuEdTYs%f9u-S3du~-R`sQ+oAP7BRKj)7cKk6$3C#) z>GnCV3P1jV>z94vsIxxyOl$HY|GTF@=g`AG^6?M8_XjUn_1skt4}Idd6Q6(jFW&p2 zBQE&fvETpp?+*U$1;5??PivpD>zU6!{p)A1-1qqxKX1q9t$hC4=Y8URhkx|>KY07| zzrI3q^*_At{tKUW;mFa)?RWit|N886zqRvwU-{mbym$Bg23K5htSeW3Y-GiOA3pW?N8Y-_ z@n1RoD@R5jKI*bhz5bq?ZmsOnc>Z-K@4x2>yIytXM?CrdeJ{V@p;O=X%V$l!?AI^b{iD|5 zH|VX`oN(p&7o30GRfk-4)Kv%n;S0_+4_to9N57EVc>LcsJT>_GD_{DWmma+GoZJ5S zlKYqcZ22!P-}&7b;ve7m@f$CH`sFtqao!PEUHapdU-{&JU;oYP zulVfqKY8CzcewMK>(^ZW=Gw~ob60-$Yu9|`!SlXw-kU%F=6`?WfE{-|>t|2BYL8d# zaq9S|$DXzDn1#Q*=(hJic;K7A_NViIH@Nb++jqPDr{+D^Kl9r!`|8jo%P;=H!7HD; z@`mx#A3FQ(Z$0d^Z!frD@q&wYzx5a2_`#ub^S;+~q&stDSK7)u$dA9l2rUXYSv7urYY< zu^<1*CqMg755IytDN`*Z%B*;2oFU5Ij3L z=d_QXcHfsjzUQiM{MWlazURaDzwfqlZyS2*8B0(7?T^2=W9>J!w}0i-r@Zuxv(7m7 z%md#!diHTIxI@3dUVFz~(fHl*U*q%RUtYXYKhpn1^_BL1i?3Yp#zjAOKW}~hz*`rz zkN?h(j=SWzLsuX6LVv-2*Eqwgzj*iGA9(1jUtjX;UG90)bKf`kjn|H@`@n6t{OOF# zpL4~6XY9Z5{DTfZ`v~tn|6j1%ejj_zJ6`<9XRdwmJ9l1p%O1C^+-vX-=Z>>~8og$} z7aj4(L-t#8%DeyT+?(um`+Uv($o}`;@RpZe@vfT=-}UfcT)gno=UjABWsPyb!Y}Bj zzx&*GofuxZ=aTcTU-|o$ziFTJ?WI$PhTs0#!56)E$8Vl+-Mg;-=i{q8Umt$- zw<}(??@#vI@!FNGW7}W)?pwd*pMTY*FaPcX-?{EvcfQMg|I~jx_P{f4dB(5aef>wy zJnY!&9>@LW3s=9+an7o@f8_%g;P!hpQKT?rqoax8_GjUiHAS7hH4d7hZD8 zk`p$(?#tgg?aQCp?u0+=a`evko$=;B-1UhOu-On2N)|W24>ph>o;;OIS(7e6<{r2yE^ULa`uYT(JS6=X&3!XZ> z>(74v>0R$W=F<=S`G1l-2QT>UzIWbu`t{KbANtaw&)w9z{F}FZ_Z0`O`0$E9t@!H- z=fUs);xo0M{AKu#2X}wdP5)#4*N^Ue^-T{yviO@{yz}Voj$ZPk_K#YBJM?eYJ$&Fb zZ@uz&e>wkeYhL^DBi5Ywz}L6`{`QaTxc5`9|H%zM{LvoYKVZeObAJ5tKOFqjum8&z zAH8I1ZFN^?*Q5Xa{kQ&l@6mYOy}x_-$#*~eqFopM{G@lh>FQtY_=A5rX^)kA48AeE z=#Lkr7cal)Z?CSu`twJAXYn0B{{1gc`TN0l{>#(eZ!J20-76nj@y(&1|L(LOoVW6i zx4+|(_g?bmQy2X2U2nMagV(?5{>E*0zwv1!Q=i{$;#c2K9(()!uej+Ix4q_%pPTsO zwRhh4%ws=y>_wkg`1&=!pZNWvN6-7gkzc>@{Wo58%?+B6^hv$Fd&RcJ}^+jL&=EsU?;P0vl3xz3IBw#&C5JC;jI3EuYmU@g z3r=1$iXl2Mu;{>(M{A7}S`(FeYvs`J0eel|_@%unL(K#BI@VsIEg6kktA-9aWvsRQ zlp|I&PC23BH}^Vl@y?4*UI>9Tt%+LYBGe}Qg0uJ7(&G5GzU<_#KJRr43x=0eS=O#f%E#XIwF zi^iJmh0Ee}`s?`J0eh{Qm>69+IJkE0+68OP1tViC2X)`~2Q_2RFsl5G>iD|hiQ384 z;qkpYL^jfiTjPzfq0xzQG3{b!o5HKUupXn1^j@7Tq`lWU`c z`T}h*??cVTrq7H{j;+p?)@%&6R=3u)h9}1PoSyr+(T&FcrjI}FD>wad*DvNwmMqEt z@W%D7S(E$K;}gkAY{B%FKJIHbesX-py3y9)vex*>)p{8v?r1Ljt z&{#6k9BQxINW+FeRdZJ8=0d|?XgFKa*y3-_q`^aM)+TCFRJQ!PThM7X7Pd#m*3>2z z53Q-KYz>YMuUs^^`G;Hl%}v*HVKlOOWQ=9C7MqI(^M0KBDIDIV9e;Q6__Bk;%ArZ* z=)U7rjRpFaWVZNwxpY>vPM$ctwyrfczQrkX)|m(iYbziB(~WywXrj@>_q}_R(peX+SW`DdSJ`A;^oBi>U_z>dmaXO&*ndFlwiJz$r?@>FM zJObNz5z0(S^By|US~Ff5ZH-k%F;W()ol!4hC7qY8#?|bPm&AuYdnK9mG}Ff6;pZAj zIiD|y6GM@coX?lU$*<48RPA=<5h#h5y?xzuY`ape98S1lO=W0wd~!{tDSVR3_|QZJ zOMPPhN@HYryw#X!O-zn0SfFSr&C56bAKI-FwrloRmbG7=Wx0mS?OHxLIyy2oQJEML z;WM@8v$rhOvbgT!P0c>CLSnw3M>YE^%i1s5^DMR{vQ*3By67#bRBFwk(V_9i(D2Gi zOYsS*efjF4i3OD-CWjl9NmRt}M5{7uIVJ7tiq`l<8h`L znuQJLBRbe?>$jbwz&$O@A_Unr?6qOr?&`L?st~OgNu8?ZRx#!@ccJE~3D%y}O1iB* z*HuXD^rW_p%YR@_rswW&JIfAub_*rabI-Qj*ni-SjV8w@M%J{(s>@oVtJkSX*dDY> zI>$qYt7NTLjJ>4NgPd`u9&gQBTyPI?^lx%Q>V`+u#FdGR7Kvl z7Lwh01WGy=VTo2&WT`quIWQ-slDL~X?URZxD2G5k(V3-CGjFqnacrV4mDci zm3U-LZD@GBa%`)P?LIysl3vFwUB3C>j~rS`qNh33Km|9&=hXH=@ykyFK|N~$CGF!A z>3+`!nHG|%vp-(anal>67UE;}$4lZPA7EODmwXZ>@skHJEyPV8fo;4fTT{JYrG?I@ z7qODgOA!z``{O0?k%KWUbUZnqFNu>JjAw29X&i+8&b>wccD=dN1LDgJe=2NVq2SJG`x9aZz~ic8Fv*$g|^i_w@nLr zVzSqf1oLn)w`!51dMmejp)y|#Zna{?oF&Z**~yz;@hmbW>AXz~>WGym8hcxa71gV` zbqf~7gWLKA%RDi-)e06xcXiV{-bw|_6OFws1&iX<+`0q{>9@)jzh+&hLh5b{LW1Zl zVkIfPLx)cq0TWT#d)9&qao3$p$$W{9d-KCuE~S#Vizi!^-1QVXw60W2;wsBEs2x^5 ziIVsUYpZL+4aMu%jYvs6b-~^ix_4b@l*CaU{I(D~c?3%1Wj6e_ATP5CY~w{4aRU5y z9^yG$5r}sqL#m$S*2R*V?%9fX*~bv5249VzZbV9w2f|vJHZ#Z77@Lz)N!(4Hp^CN6 zAy5`CIk4|yS}2D=NxUdRx79AH;+Jp3jSD#NrvJ=a{~$#S((I3ybhj1rg0nwf5+CA( z6$4NCBue5ZFFaICer`SSr6&?Ey`bKO&Zrl$lJ4GY;CCUeW`DdSK5}s1g&4{Cd`X<- z;JyoSlJohJILU*37h)xkKuNp=jftU?TC|=Wr<}D(kzgc3eXIGG6|J!~L&I6&;8OmG z+CoJmQ01FS+MVfQ%ApAgE31{IjJ2W}d_}KJ&_T1A6<-e1{CNT6o7DD3;d@Hj88H%M z^=jJVFQ*&R#41sFy5$DZ?{s8UYi#pAsqfLed}B$wrEtE51dGD=Y}=V`FL~R}Y-KyM zk8V)vq$Y8@&?xDw_wm8Gt8$c2qa=>jj0{h#TD`6^GTfq{{hC%~;}2&3Q|%ZxQY-#7 zCGCr$S9PNbHp{H&4B*qnzgiW3q-8&~D+Z(erhmHm@2YLmW;#mWS<;Smd8vi;Viy|Q zILc=Jsn03wvcLa_&xkxmWyUYHM>vC+AU=y=N%!%Xq1Ia3#7xwNR*%!KZRJ?4*&2*T z)(&U?#`M1vC&k2oj%T>KmQA^xwe~`1Julx_(r!(yKM&yBM=De6XM^7gZNglH%HoY% zuXeKHn$Mz87B|~E+HIbFuoY$X^OjQVFF zSt6;>ed|T6B>A><48*f4XL%$_;wKzhy;=kkqjindihRvYswDo7U?O*=D_FVEjhvfQ zN&I!e!xmz!3yqRETC%P({q_~1T5}O9iMJ5!rSj$!v%c%GLkC`G!0{(O6Km z9&zirk{gFrDE9B|PNt-5Id$3@YKkn6KuNrC>Gsui_ho7fb)`}gS5v25t;SFufs%OX zX0jGi#NCLL#FMUxTc>#Gx)CXfr>WD=SG;pM1WMwit7Tf~(siX$5?9?WOd+Pa5h;nM zya}C!_{k$s5-;7X&qDlkBT^Di-L6X^p1Kh!iKnF_&DE{(;abHpDn|^BpD?n2#dNEd zrd5`X&@QN186T~!IcS0+kn!<}$>z|AdIiY*d}T@dwU6Oh?gF+)eL}}vluF`m>Mg3) za1McOyeK#986nZroVSJ}J*ky+V*6u`ND;`>i&#lKPOU#z@pa`8 zD2tbz%7{YOa4RaUNW|L;8jX?3VW5B0B^FC+!=j9Q`n5#0F{!C)ZS(BdbToD&t%us&FUvCp$1a-fB#=CML%!wdT<1(0D^&W?QR=CKgnV znj9KGaZ>HpD1Nyp_Z)5vHN}PJzVN?Fo5hXqU#V420yi=?R;$z|S7MX4$5=R!-&$po zr5`#xAxzaZmDWUMO%2y_vevBSfyTeR~ zYt7tPS*v!CbI>V?vt_MXv$6!Cp~hHuG9~f#L~6Ph`GLDs@*pNz!Vo5hpuS83at{4Z0 zm6+IiW%+20*%lVRT^1~<=$fYSuPerC?e2p3}7{}u(6gwo$N>@ zF?3I>Y?_0%Q5Vh<(nVg_V#O~PiumYp5x)}(czC=`55(!;Z=tGD(Up@cC}~nthAncw zv{6|}1Z}L-K1B>YLXQt><{%BA>`FQ`Mu(N9wROUzt^{kcc;P;_wsHJ5pKeT!iGazb zKirtCBBs4X*>vSd8g8u+4YwXwBAhj6>=0iHgPa(M@zaG(}hnPBc-#J zRp_955i5zukS?N=ql$038U@C{??RbCW8GKLWQRAEU-uHzFnR)X6ZVBV%hxhJhGUW3DH)Qn}oi z-J1^{yV5F?!}&P9*)&SxXxdtwudCUcTuFQe&F08(rL(-*n_tN8nwwNf{KZEspC1Ed zEowzM{O0uB4;<2-TKolHr59lBKQHs*Z3Bu;1?tHw$mfzo(6 zOO=<|1h(;_3_+XP@L|Ps$sthIxtw*8A}=`v%HkzwTMJp|c?Vk-FQ>gr@mz8Ul*P;X z3l(|EAy5`CIon$7j^z+2iI->$x3&|RR*whvB32TQqGzhAW0XsxEPj?BK5s|1d~RB0 zaX2wDswOU$=TRt&o6)=`(uE|(@>~)n@pD9Lg35*y)DEZ{k&<{?K3p3eUp1mEC%Td; zi7yi8t+ln)is_K9R7&D1tK2zeiP{b9Mx-R3x^(s~CMHl*le1M_NgQ>O@M?$Dg+{3y zRbDw#pS{V24ywaeKB1C$J9w-%x=N9)E;LHwXhrRW@ye=BS)ST0oRd;X+$~=<$&B5# ziZSG_R7&D%S!-x`e4?hPuewqxiK`<4Jk{fwO`xpf z*>JfcFF6EC;$@2#Nee59=ub5|HZ-o-WJi{0Njy(&xJ>o@W)mojmzd%OaEZ)9y{-N>X!7xL5A__+ApSY>UiPUqv+OUEm1(PE-9v1)8&a^^=%IMh0#7JXgb)~_|sl0{Wg39qMe=V(eD#I;)RAa3}W6kPlZERwl_zU7gE5~YU zX0n(u*7l;ei4c+rA zgOy*OeQ8D2n3@u;S93NcDL z7pba;LKjhrVoBU~t$--RVOJ_8adpVZ8pR^4TnZ&|vz+7z{WBFU&8}2R;;P$7ib5B! z8fQ#T?d@w8+e(;~^osWt|kgHB1DEuS169T`(>eASIeNjx1XrlYCd zzkCuU@iQ+*Z#N<(@icY1>g0qR0%h@X=5=bfZ7PRAS-hNfwIVM$1WM!O5>;Mi6DW(9 zv;JL?mmC6R@v`CbioE0yD2tc%svTW&2$aN2Hx*IHa_dHnwzm*Jc?7ocqTJmr zYr8&uSLSDsxfI3HPHtP({Gz--^43~4fBJew4V6Qnq`Mc53{TV=6XSzq8a4vf6~icU zBunCV>MhqQo?Q-sl6YC(YD|s|Q5xGdxL8P+^(I#mpHn%b;|g(>N1!BLI&;+znU8@3 zbCW8Gzp2yDSA1VN1WMv1Xj51|L?w@+K%9e4Nt`XEh@uh>De~2oN=aNTTTUK(vN<%O zb_2VUDT%MV)L$|AxAo)>srnk3R17G0p;6N5bW27QI-zbvO5$niwArDpLj2?rD2tbz z20O)g$ste@FJy=g9X@G9J=HlEp^|ueB6ap-dqwr7s^QR`Oj&321dQZb5Gae6 zCz^azI~hdcECMB+%(B+_=*aLmy+bQ2R#Cq&y9Z(+Yoj;0lK6CHZFnKRW`DdSK1?+} zW`DdSJ`6QJW`DdaK6FJ!W`DjUPNoa2)Y$1pq$Hm5l1GK!eI9|5c-gWVPVJE9q*NAn zl$_2}4qKG*bthF8f1-c0`mxO>Q4&8{0(16i3O%kaG)m=Yi=s@m)9Nrc7rB!7>{QB5 z7ptoAI5(-1_}g4|tH#_MbV}lEx{7$Zd|2`E^&(ahkDWs3`C+jKt&%v5YZJA4ZCq6w zbSG01UmNSw)m~j!DkX8%DZ!gdj2BXQy~<XVyeO?dbAJl*P~T!z-Pvj_Apt$k^Pp zO5^a5!7ckcsPfo@T1i~aXm+8-WG`YR@i^TuM2)v@L`vdmbK?>yj> zr6jI4?sC^@WQDlvNv$L;lTV@~e)30^6*{YY z5+(7|eN0&)p1PAMiLVTPn{^1z`n0MkxL(9c;<3l%u0jXcgH}l#=9`9v7|SP75`ksDKKq)PP>A)uzM-Vum~)mwA+G17R1$Z2 zLlp`!l}Df~UUG&i6yqg_Kv}%x3{@z`OAdjOcS6u zyFy&gNvS06^2A;trt%1s#Y>LZE5=I>fwFkX5qrgW$ste@FTDj_AM}{Jq|@oFrgFl_=uk~9yu5YswIu{)+jO(>ZPU%h3h9RVn{Kvf zP_%gtYbzITp7*xpLlf`*@Y=f8*!Y%zVRM_`c%(5YaMi&ugL@`M9{1NOjFy{Cou6*} zgVjTeHP_Re$gv~K*R{Zm!z9*i-%8^o^?k#%JL}^wZeHHw3Fx*Jr*5nVp&jbFZ+LMO z#I_Md0iT9!Zvvicm_e)sabjA5VFZ>IMVgrey6fw4to0;d*ly%Hh9A1ArMtFf>JA_F zE#H7I%b-A6Y{DEY7*3ExhV41A!#_gbakLcphHV9jl?0}18bOf6uANw3su@&g_c%rf#CjOShL?na8zly-cS9{S z9nbPO$le4@*bOv0auUZjjU>`S-PO4my6!ni)cYQoW)vk}Y6by3^Ah-nSiEo>+p?d}?c;5(Y%@;&%S97B{ z?Y)8^)MDLD(#SIt6A56&hGs@?%GbM*%qfK2J_~7*`f(zjgcZiN$HmrD(@i6ZQwWnF zH6txZG)s#EZj(@5Crx}9_Cv?B1K;-|BaRc>aY8!^-QJJM3JuduG{e`Cgbp9) z_C%VeX<-96J};W9v01FuA{B5Ty}^6PT7OAy6z%Am-+ortA8tX(kcZz(X)Q zIwI0`dmf{$X_069rfF!pp18a#o`P??E<(z66R$S`H)5}h*iDllj+{VaBZ5TNT!#(y zEr}HbuBMwwsBvWYiO>mn|fe6Q5ZXVlvq*V*q+<_ z6l}CPFV{Epz_B7bNp#2b5)aj;yS$0sn#s|9)UxX(wt+B?!qoKi#6!e{o|~pxZ;2#M zOLqew#bR=)6U}u~BZw`{OVGPUZ@u7{8k`16m`1)4TB#QJQG%K?w7_+APlAB&#p3$u zN$Lvi#EV8d=xA!5m}B&o%1(?x@b$<*nZ_Ez)W^=T6C>45h3h zrkJSgXJlxJrhDAzo}1{Sn^PY}qKBHxJ5Djvxq}$`DF(mR`xJa$Z;S}T)=q6sliL$< zy(81J4BkR-b(c}2exJzp~?GAaJFKH>ujYuhC&KkJqZMwt682F3!TELAshI#Zw9FoT3n0X>K#i0 zgGA#(8#V$J{cHp%W^{sOYACDTE5J_W*mRHUmueQeJxDbj!yhj|=f4sJ5GmYw%tg)7 zw7`on+7Yb*1_ztaTc995gptTw#<26S4-?;T4HruTRb@rJuQHBijHeL=CW6SsS4r_7 zgt6{$GeV=ck%i=pQfyte-QzwXT{P?k#EIz{wjp6F2QJ6uizkGAl14UC0Sie-y7PHm z?=264NOKa@a?I5abi)dDCp6MDx-UX(y4*GepzIVWjB^)=F;! zBjg0p>xst&@H8EZ86VWqk?Bc-;n@2*<6k3F0`8n+;&8Jbm&3sGkC0IuaBmiHb#1Po z$DxNIw!4L1MJnR`2a#j+RxDwNqY_5=>G(Z-n!R&4LS0MD#4?lK1_s;BR8x%_;0QrVC|u)LJW=GVx5nM2tl1g_X7{VG1hTkq7?Vf!pquO2D33Q!j()$?W(6NYr;4xds zq=ZAzk^p@`2!R{rOROLvbd%s7rJj)KSjRj`tdkUxir($Lf&{%l6wFM0A|UwiiLOU3 zUYLf4O%1E}V@hnBHETW=fst|{5NSFwj1+~5u1$OEg~UOe52jI@@2R&~$1_6+@SQ}Cc+WTt1~#7OnHJXxZ?N|Y zA{{r)#8(WA1m7bx5;qbC6N1(YQVAAfB2T8nMw*__b>I$J$Y>jW@Bvc^781@I64+TO zPAi|!oPY6>G1A=L>OH|T@w6zwSI6AvN_e`k44{LI@g>AMkyDefffo@}wJZ@uv$zWu z=gDg_^xhY#)*Pv~7B)v3L(PRl!_DTxrr+}IR;ymEyN+4a^=7;38T{5W+g_vQnN6qN zTh47sb#oA1)7!dbH>|2&Z~KCDy;^I#PSvy8M%%8NE!{7+;4Mg7MyugDwW@72n}T$s zTCck--EHbMzpWW{36r2{;)5a|%4n^mu&wW`gUW)kdb)oM<==UweWy2;sD ze!Hq$j@Kbwx0-gf<~psGt?La!3jLC9H(bAMXw{}+8lAna>vcA~Rj)a*7>vP2Xy_nvHt1-S08#4i~|xLrZh(omKmFr&(>`Nog%hZ+m^; z5F#>`W7^GX-Ko_CX}{_>eY5Jdy|&e8c{q8!rDpyy;!xG>cEhPQiC+rRHNM2PtIfLM zwOdxr<>~Z!jC#Gn$Gv9NY8u%MX>)?QUiG}1Q`hxc!)f-t*R6J2tGVK#aGP1Rc#W1; zZI~^$(L@r}C2aHj4Z%Tljh5wCwVIb5BfHK^uQqv|tv2txZuYxs1An~H)*Uv);@t@G zG+!HH_@<@Rx!1beMn+{1UAMeyo1e9{dadF4z3rm_ZltW8%DM1 z65cawjh0{UcSB6pW_liy!>x%`w~T7tZ|POvuHk0-Ew3dJ@5|q7vsL$-c2lbow#+17 zql)im2vtCsy450p(eLH643d_fS+6$QhSNDlgsNR_H`-dGZrFt0`jsJ8O?dRY7RRY) zy0KM7*&;}qE!%3=eaF}OJw}@it$S9j+BS?#H#XVk7ShVMT5a9tY8(A(8M_YG?MA(- zX{askb%Sf@Xw`Phtk-I-nng;b&&!93v5`Uso68Po51rtdQEeE!LxgI>Yxccrj19Nh z(yCU?nl^Fj4b85$9LsgxmRE1}Ew3H+!EAa~we6t*vWM=OdaK&7+vxKahEA*A=Y}|* zR>uNCXtdqz80#FPsaG3~b{$)@rn_3BU($8m;~h4u1{!60uZ8v3cIyNkbi0X;@0WC= z;bM|m)rOAbU_)wEzg5$!bxX(Ha_bF8>-UDZ4%V05s#OVWWp)VWp2soPZQE`cHOpw& z{k}#LXAK{fTh;1F4pxoLSMyrPD68c+T9#Ya`&~6Da?Lb(E$tdo8q$a-(#BP6vZmQY z?CEB|58dO67=D{$G_b0(RTClb+4`F8<5!Uo>G#Qab-hOPpDW!$BWA0v8BK9RnzaUQ zlh(gb_1bN}=~#`bW$77dgr1v;Cyit*hOpW9Lx=me*0PN%x+W8`eLflA1 zYv?@pen~eqzXjj_0Ma#_q9%~0s?L)Z8{$=K4w|9SY_|YsV0}0Hm3%d??U;41f!gH_ zLfXe_H@#}BX10u`)3Ci-zYo1$YqlC@T@#0wnUlgM!{KN*F<>$02@mw^Ce`bnhZ6I# zo;qFuMzDc(iFj&j?Y7BY^}8W;HUj$>-Bru9%sk$ZdRwnsM!Q|rkfma;U2Hzxtok+2 za2p;ra;;zMsX^G9SS`01Tew5I);bnRKldb~+z)omR%WHg@y56of+pg7g zEyRG^uTX7R5D}J`Rqwc@+z?Z%Ra_$~{o7v^_ zMSzIc*v19+nr)kKS)UuyVmmyej$h>YnJPf?S!ndSW!If{n~UQ1D??iBhhw!HX!H(g zarxRhCLlr;8Q5m``(3qd*o0~@m$1*oUe|c&h}R|n65An6Wc3{nX|+73PFx$OpLh$T z@f({6?RJwJ18T7W)~wG}vzwl0=*=n#o9q}-yI7FCMwEfk#t^i6XJzv*U%TzNo@2K0 z_qFWuVUrOU;>p+z5e?Cr60dP`k~pfSk&hwg2@nFbt*!?oZ)1=x0om)w4tr<1I#hIn zdp+aQaH$B$_#-Wq4@qo#BWaTY#vZ*716zfp2$PT{=`qEt4k?QV^Y_)g*V9Y#tC~fmJ84XU42Y#3_V!n%*8;Ab zyekaY=Cnly=6#;BU0rSAPBCnBX*ZGG^u<*5Q&z!K#(POHz3Uuiv&FI0e06X z2Mi*JOK3$d7!gS-A!t+Z`T*=yZXJndFm8AQJ`f#F8t^!2e58UA=%7p_*AgXw2f#&X zN^41Y&;f@HoxIb~gZU1MrWigdG4r>MhbcCk+HLHWPpaN^3zBkp^_h z#3^v3YZPqwE_wAt7q>1Tx5`$VJZu0X5((OjBk&!m&7w05noOQr2jJlY+^f+jZgvu#GCfOC8CPW5U~p^Me>Yv3*31GRvfTywl4{V3jpyw z1RmIs-u7;XBdR_?Erf>BN$UiP%oLO{T*Oq>cp5?+a094WKz4yFN48i!mo-7RqG5sB zfoF&{piYL1?v{IvAP)e>k?H|mCiaDho&iiMVNIX{T-lSxX;6QSI#nfB2uR0Jx-US= zBd#yt#aM9UkrfB90jL#FDVSc#eIa0@+XBU$KqHO;OMw;#eMhVgXU4HV!e6?F$%Mj3CpEIW!r3SVuAdvJC;SI8^q;0D_RaCYVn_ zmC8E_82dC8K>VOT6<+{1EwaR|oAv{!R*Efhf@NKNXb`} z0)_LOK%(BN!%>1lO96XP;v-%_$V~=)#nJ1OJW05E(^L|yLMnDVJ(J#8n*m0V4RIU_ ziEw*|N{SXiZ-@f8OxC4ofN_=>lq6!4OIq+$KG{Kp{f_yS*_iAuz@0b=EFHsB@-)%XXK74eIl#3k6Oj?n}ts$B_WHXrQiTeOLq3Gs!zM1>7lg zBNB^^1+f*gCJHVkITAIWpaiLPqbd$6z!yOuQXZyjG013HDsBk&8^|sGgs{Ce1H2Yj znctx#Wp|fq%NUvIgut(b4xsoCE$so@0@RvHYFLo`0HQ(7Dd|SThyXnXfp`bmYZM2C z+@xeG@hBXU+!qTNq2mL_7uZAY3!9RFBgAU(5fZXzM9qxDp`uXPl{gH+X9LOO;)sxi zL@i_I)&XAxhX#5Ll&^?0#34?jp=+roO6w$Lo#BG0gcF0ikx>B|7;^`)gDGB-=E$?e zmxdALeA!8(Nnj7pivmf2qmsLeBrq*KQj2-ucY^qK1madfdSi7Um?-;IvUf#!9FYKi zVWlErya;Owh zGGbx;P=truDBiRmVDJJirjiiUwI#c|ahMX7qM}&C9)k+Vdkj=6Jkcf8xJkYM>VK$> z#ggNQGOb40cal}#*u`kp}iF#Yg)d1j9Zz{JhCg5-GDh0x(xUeAcb#ZrnFpB6pOH$Smtie{*(S;Pq zaMIXwRI3t@m^Yw|*bC6#sC&s5fJ&o`AKbL&bZjYl15mqU zf+werS(AK?AdwS9N*ZdSu?4pd(GnOIrEo}xPy%(8$rq|`Bl7j!U`i_ZC>FdetaqLl zK9B61#%&PZgy#aVhlozqny9~XZH#ZuOVYQe!U!yd#nGZYAmfRq2RQZA$N8tuFLuOVUE7sCi4Bn*o1AY+gFYN4$Xrqus zzHejiQCT8Ubl>5KdI9R{xV3Z0g}8cZV^2w*QAm+4-4sNOE0c9tGeo;EynT{tp6sndBAG%^ zaYC7AL;Y!FQ37iQVEHWBwICDd@8S@yvpd!yL5h^^rV7%JB+6(zCoTMGGHm2%gf}7F z`b@xR38#jGF1arW6||^iDv?oe;vKmZk&y_%;ju|+$x?~2x+5>9SWCP{WG?k&P7sr~ zqi#bo7KAxTBo=#{8n6x(1ZXIVJE`rX!clH5ga{D;Bxi_UW|DytI6ol*LL7%O7)gmq z%$pi%OGA;Q;-q~7Z|o24foK$j<0!ipdT>yx8+c?m*q6Yw5`>dzK){L1#-%Jir~Vo( zxMX=b|;V zRDO(6hyOrj5PF(Ommed|g^M5Cn(Td{;gOBAN;3sCGk4bvOqy(vf+Eg9q*d~y9sFj4 zmMal~N7fSPB(bglYdnc5e8jv9WcP*4CoyH79zsRf45$@hzX&fSvSl#@B(fqL zD*^*1u?Fr7z9Q~5je4l(2Wl#LMlk`ktU6Cc+k=4CacyFvstUO*z(fI6{Y08OexpYw zfvZCDNB3hll+@Eu!84-QgiOqS z$3mo%+i)c-9{lGYB%IIhhn9wzYv|k{5cU>N%L&O7=Ri=*sOD7?RgGEm8 zO!iIt0&?K;q=og(T1102Xb>JCZY>+J=Hk zF=;~*5otif+gX&DU`0axH(^7rBUSX0YXNa%p;n1?bR?#*p2>-!qUawen{lVFjPT5G z@~~v!2%{Z~8O5m6;RlybaxF1!c@mOG+z{b8k^usCi`Afk^hjm1PaU!@=)S77LU?%K5~ zRT`8i-xFZUn3f!*UR7cr z;4erzq}j0vZdymV`H2T$9xW z*klYudxfqJH zX9V?{p#VHo63`7gWa8vVT0_J=(1l={g;6E;g;)sJgkHRaR!9=atp${diw_(TbjU0a zjIs>1go5OK$!0oSoKS#G)WL(z5w<4TQ``*U+Zq@mk`^nrYCr%d07{>s>WP2T{{%OzS2o>z2hqFJ;HWK&YUU^LsFiE>MTd<*@E z$qMifKah+A0#NddfL+l=mzKb|Hez?V!Q_W=Zt>cP%}7$A-oytGWYCOKP$6edNEOfp zZFcwr*|pG?3a~Y}6{X!J4@QG5B9SW923YJEr!-$9myqcrUvv!y%NSwS&~{XIUusR$ zt<$`xX1Y~Ml&E5(`%BfJqODe=(T`rMOH!goDC(JxVAzHtjz~6?{&b?3E?&BFM?vGG+J1wzM>tRm zBuRxD2Xq?1710`(lNKEid0E&b^rF)x?M#uF_ER^c=XB=f&_W)tgJyxN1UV{;KXEDP z5=}xyy-#-1faJi)p&sdGP3e{_M-;Q!M3`xvk)5=wf!B-iOGQ{-)+=^NG}1K;rxPtA zG+T^;h2@R!7rS&hlUz##LPVo8C(YEdXe633iZEEEdbn|f1j7-3Yv1lQwU^xS)B+L<_R8_Qzh>OwPn@SS;_lq}8W8$oHGGGTh9ZAXH zfdU}0#g!*)Aa>W_`CwxToHFP?$u~`ugMb-f20;GVo2IY|)GAp_{G>oO9?!nw#^9HP z2q+;L@TY-zip&x~1yAxdVg%CDkFF<5HGQD06DbSTfz4)O>uj-Pz5jZn&Cxf?$ z)y+whFL6X$Z~#llDT#JXSwsib$lb;6r{_s_>qJaE!oU;RLqk?i1D_`_Oe0+8*od{@ z)-u0?$t0r97G3Nm_l4%G4*4sf1KDc?s!UxSWxAXj(N5VHAW+p985rLk6mDNNpfp#< zw*jb)ha^rK1g=B!TBN!}jhMAz@C8DEX4K8HA1P3i!KH#Lc5u zE_piIb16wo(#m)t!j#Iec1&SHOQWI*KHyl$$~x?9Y;BR4wnc>s*b^oy(IXsqJKd>e zUnBmel@fXfwIW^sK49j2G9d{+Bapo>R0m)tQ)HmDFQ`*WNf@C674aHHPh)IvqQ8XF zWl;$pC}Vn{|$7%(#r?}+IfG@g!$>Z;s2v=FHP0x=Yp3mrhe zBWkJfcc{wa0!Y3iO2ct&1VWjbF`kYKHBO795E6tffz3zd>WlTZyo5QKDQ;4w2omLnivq8+~)ein_6;Y_fc zy%AE1+BHz<4A;T}5CMJ3eW6jwJm>&|mt+9|TcF;FD3NDic7Y2TfbelB(Uvx|$b6>b z8N~7xWgW$+)C`NZ1Ra06uc|YVRA3*e5m&k|V)B@S5m#(cFvN$7588GNfr=ysf zxUVdgSPYCp!@`q@w2A)`Q5QikU6+*%q#pp#Vn6{E5_%e+*x)@duac-8Qwk;5f}|t6 z%)Y3|UCdDn`9xw*lwzp=O$!G;F z5O9nLsVT`is8D9i%s_$cXLreOV2a_!;veAO%kD1s3-px;=Xbie5PTs6DP99mGg+J{ zKs)*>5Lh4{kXeU#(t$?KoI(n2out)_K}ob66b^|eK_z4?3)U-hj(iLmiB7R^q z1WG7WM=B6<5&Ob(=1miYWX>5VLfKnK?K0U#&^H3u#tR_n&hSq%pwU?;j%mi^PDSin z6G$IP`4TeI&u8Uoac_cY9&tam0$Up$&|h~OsE9^<^bimusCfa*n50k_RY!V(u$|;d zdpsPGZ~@zq>1i-10CdEdKLP)ir(z))`QB-Z%p@mFqf8zs6xfM+~8& z1r68=fx8ucBZIvIMp9xO;(kdCu*`HoHq)2;B1RHtZ<-(^QKeW&E%cP6w#S{JM+kr| z(!yeQ8Cfop_F%Y0`HU$UXP|c$ffJYPTIWquHBX@hG- z#q7qCqUkJ^L2|D#LJtXGg<6IqBJE097jadU4nzWehG-%$Na#ms5{7D17eZi&pc_do zNsfpUQaG2=1fWxMgK82+8JRKgr-7snpxVF|Lws?zGJGhypMo(!T!3Jhe<%BnL>D{Y zF7)zH;fQ=CVwY+eqIAN>Bn(RV7_F5Eo>3Ddxi8!~jq!DA)iRjaw9IAnHj!9yMk3(M z2t8ajy2mo;Q*JFtDCQp93^hddLj|o!&tyz7hC7n^mW)8+f8!rfE~J#pBCTdH(2=qb z#$&P;8VQkkG*X-|T8BuCBhREaROl}V8a}i#EDpkn>q138T@yqX2g!YLuv@4)5|va! z2hh-$G6M{HFiRvdC2t*e2GW&2hoYW?`@%aCmHZAvopE(!tMS+mu6#^$ClwzSSIPmN z?NbRSaIg=zlivRj8ChF>fYYGS0e>{v;$fMT0<(t)NMegJSz3BZ?k<5tfie{J8<}Jfxkg*m#F3do zh09S%bjp^5b1Jug2*rh%mA^lXK4E*xw|5BYXNB> zInGDf66y_s^DxF(qY0;M#5%$7X3RACMyx}z7RokJ|J++<5EJ&2JR@8Z`d5=+ArwI@{gvmK>zz9OtA??5x(Zzy=p-N=eLLQ1TSdLjCa*|NL0sH_R zOUg)`G*Ji8A|U1Pw26C2u7!c%BrWkg=@TM2f&vA)R2~2m!lRU=LQ4>^&BzB=^zkQ- z$;OgP0w)LRRbsk!=k8KyA?hu-EFFo-r}0U^eDh4{4lTJBF%ud50o86DJOjAUh?;|x zNDwo)WzUF?74#+}a!JXW*j+&Nw9XPKCG!8WzCE2Yh=oFi+QRJCq48r}N2Dm@zhz^p z)YS0msJTe3sBZ{ z7#0mxWYGHlCf<`7qCu|0K6w}lOp-B%nBAJrAE82(B zjE}G*zArijfCte50IC8JhQBYjFI;rK9H2EA>&$-XtFImgKON;!U_7KutC3}xX} z4mXAlCz+lm9FKcLp)dc%`jxydj93w0Zxgo9)Ox$xeMQEIssJ1Unosy*^aB&5$n8or zE!j*6G(5<2>^aI>cncur009ywFfCF1USLHt2myb>7{*ehI2x#r0wG=$(Udevi0L`K zBL>}A6MMuQcp$nca}aZVuLyCD|0e1>snn)LxjZ4%2wpY|pa_C(r(Tze9K1La^gSTq z0)%F4c6MqM){~DE7OoN@d}kUAkp(1=twacEO}ing(#gxK5u%!s2bUP+B>E@Bg`{5> zy%P{BWW<#SiCQ^Kbs}RDjm|e+NM6_D-Wn?ciA_^_jc~5jTQbf?h}1j{1H^nYiD1B7 zgaEGruLC8BK2zEwn(WiyhNw_%E8L?DMTwgT;*U3{wnwC)M3XWw1rji+x%No(4WM~d zW-Kc0kq9-@;1u6lVzl90E23l_PXG|0CQZGCuqkx2*b+Z#3M-t~^Y#A18 z|9j+tMgy)&%9&!OMB&j2LJ_CQ!`UG%95n$k)H7%`AV4??umgPQU*rk`bax3@~y?Pv_oIwr+bQ2r>PK zh%uGFG@a=C9tlEpR`uv82Ovjc9@tzf62K+V4w~Sk+GdN&A<^XkFF@`1DFG!rMcjat zx6&RFju+Kv+#`t|H*@Wg=#@>&CCV!#TDHzf2*4<)MnYdv|GtPg{**?pM2u*$B{3jo zt`&j#C!!3zfS%Jzo1J3Nx&aM5$jS6iNOV7-a0*xz*<%UBa#z*_A*KKm9v59L`@uORsVTF~W)h?jld0_?kpj+FW}?O&aG#wuTI~5^FROBjl~o zBCSYlWQJZ7n@xa)C@j?*RIDkjh^z$iRwLFdf#siTk35fgY6wmLpGXh?fY9zwH^t0=qkjQ@nwDfEmq=SC#n-K7Cu$pK!idzK+q<=#6 z=koz=5Zt8xSE3ZO+Y0Ol|C+P-2rpqwfa4D9u#ZY!MM7 zbW0%^No|R;hHm$Was&|3I#VRcu44uC%^D5RD7>R9B@dRX)c@-js0x~n(%KuyAQjO4 zuZU@UF@6@1DUD_IFZ{uSBGyP|QNT^QrvFm|gHQV~l0YugFQ`2$z#5$bNPGgVL$fBD z=suTH7y#xFi@lHs2_dkcRHAf-U=R`!(-9c8M)PKg!R4Fp5y@zRmy~UGs#1XFfw>k7 z!-OcJX$>(}(sD$nsx0M003o7?A9N=}gd`G|n^pw00=OY4Dex;Iy9TUTl)=&39f+`B z;6egGCP&CbV3H05Lq(VdM-5n)zcwT|LU&z} zXsQJ&j8QYR3y>I>Iu{`}2>n6dc$tZYT?r9Bq%?tE=NfsaxPV@pxknIU)C)Zlu}bKN z-2d_O))+%V^ni>ef?JJ{j~t=lG$j@atg#}{Nz|W+fMyYyJ#&u;K#38+$bAD?kiv>^ z*65Z&=RHP((6zt+Jz_?L=-`g!&j1Mt1W(s{MEDS-Ho#IqqKpyhe?<_&2SH#0PnDT& z+-*f!uuh|ZUvv{@r}|z||G~N$$1ey)%od;)kRnuxQmC%1;X%@y1{<4ZlHl(8XH9t7 zcs2CGi-2D%5JI25;8LtBpE2+LP%P=757glYt2aU2@eDdr$CHt=88nVpUOj8U#7H3 zEFh0^2#Im_%y|(dJq2X;OhRZ8s+(v7NDPT+UH^L|v`AmVf98rH1OStoBW7pI%;4Vi zXlD}Q5(V^mq~A3zsRXpJD1zzG}^u~`=y8xkwXYgMAZVN>a{#QhuBO^4JD@g?gXe|XofgR8ep8joA|40-o zbh}5ud0plTFhbeYfJgSf*Fp+y+QMs9^V7%r#$@y{fL7NPGwZAU0bBY)UG zA%s8uYQ)$P+%}1UwwpG4=8A;D9?|}Wq!{G{1;ofqLda}@yu9F!M({)A2Y})Om zxPI8l5)+%c5z4GuQld2G!eT3eVx7U7kdXAlN<5|m5FjZKKblF1v@`Jx+C#7?`j7T~ z{Gg3R`jP3f9$*n}B03_}BS9aDVJ_VW2?-g}?3DI7O!$lo+NZQWQft*ffiA2F; z*A)o`iJMJsjD{UWw8)GV2{A%N4G-3JY0TXJX4B>oH;vjnhj2Y@IQzaw;zCj^sL?x; zp6f**F*8^LPbGSPiy;F9Yb84CcH3+w8u#gC&sY(eN%Q`xU zacE2iqVE>c#g>@1yXn*jq&!X@A!F&rMwpdm95gMIXDx>i>3RGajV5A6?$Jy_NJy?C z)7NR5i`wpgMGz7;B$GwxZc-#KGIK>Dq+v3Xg!q`i#P+|Uz76&nE6S`|0Ao}O0Qm?D zxLJ9G7$ir#SmA9Kuxe)zLRf>VFvAo8JF^Z7__Q+!G5&{67HBmBk>J@1NJudgik3yL zzD239>GC+}sKSQHpBmE$7@tMdkrZbur^~wXj*~?KI^iH*%a9987t~PRy&hW-&zDapH{(u7ga?q zC8}{~o8SaGfhZwHXfBH3Px_#UgcOt9whTngC!v)7%mms*y?+6}f98t%R!2F6aI-MC zH4G)Q!0XQ;#Ar3TT-cO~7AY5+NeG!8F8V33#|KrsaX zG>QdDAjofaG+!$c!z;kPkqFE1kaVrU&0pc^DrMxwm~-WC0K2(M9DV-{cy+nnA%6k%@G zw2Yy0Xd#X&kr>Il$ryzY-EGje9YVs*BDX5WlzN1I3kWvqxg(la#BN9U3qp|?g^U{? zgc2=9H?4?@E~->bW@B&{MbEQ-;jq+$&da04b_J9{D_w9R0>vr@e=yh_S3nqkuDqxP zhAsN^dQ5GAG0Ym;j3i8g0%!{|u7C{*u7{lku{LdJ1z^~5PbgpjIsgPw`iVeje%_i6 zqHA8>TDHBoA9$Y~$B!w83^_p*&`Tq=#Ck)vfAJH2lsc{fXEo@-3sU55(6Odg4Q z&RqZ#Lkgh)ZiE^303 zSPiR`zmObID4J>t;G*cfMJ|l$XU>gy7F{Cc36TLI1j(%-H;aiz@d}B4>K5^j0FKG~ zB08D~LFf|*Wm~}c^BX1#h;b)O6DokJ*Ga|Gh!@z}^zn;e9oa16b;Pw(dEt-eL1Z37 zm@yn;vwhk}gJA?;5ul_BxDdL{ixS-~=`NpbHZqQ(EaJN3t{2GoA-`yx93rzR62YvY zT?M5l427X1GoS`}cO61>Orv!!O$ItEB2Z6nCmL*JwB(Q91^`KoF%`6({^2&m*=fm7 z^@>fnzJSa|KEoZBKFtf+Y%XM$H~^(V6=Q+057DQ25+hD%gM4EC&t^?a!HJm-52Go) z{2tLW13!!2)YBKTUtdHgAWs5=`C8M2FxdM7F(bajoJx@<$~uaNVjK?{F;M48LikUf zHL$8Cc@X4SrbV*SJYiLMbm_E<96v^Az(91sn4RMni9_an(LI-%s#M-x7n0i2M1_!; z0iOW#ps4Fw4%pER^`+8 z@smbp8V>22X}=#kn-1YNp#UbJ>+((wooNXjh*?RB0R#{o40EEFAbE$((4xDr=yeZe z6hHyUFs7^P(hn0;IBT=XeH7zG$R2QhGA&{^TD7zRL~9V>WxLTvoEp(yQM^vyWlGQF z-7MZhf;0qdO#Q4leyYug_zF2rkhOqwOpeYJ2w~nWg?$hLoW;-vAP7`w(F0umt&wV` zr71{r;*UZ?VjD9$nfM{j5kRd1gd98~P#GS{D_9Hh9q26_XW(<%pcEi9U!oQ;RDgZ% zGZ`vlEeyzIxt>8LGG(HV{EEn}QX4=$pXytSutF@xH;C@q6p50JpoyMNW`uMUU0VyR zNLc>(G7KWjHrvt+W`=>SW9kg^N(&I0FEN5sWYF&f12((aycK%?(lZ#BR@61huPCPO zg-(7OMJ+{|5OlG#C?Yf^rWI^@v@w}0(e8l%h|Ly0E%7J%JyT6dOXUJ9a%fS2!^kN6 z?9nn=C`J#F-x3Z<5!PIe*+lx_Mex>$BQO?|BrSmj?AszMqHi?~IB-6)`PpI^CFQ*s zO({*w3Z$S>W%G2i(HNYwg&PUNgPY9;iD_FzOX!A~7I^)l2`GtlCPPpG!>NgCO3}8$ zL;ccySN_xzTJOfXUNhXC8L!cH5;F@m;=a|CWtTMjX?oo#KE=-)!0m z&(mfTV8z(NIGa}4B2AAlN<=t7v*aVAqMpmuC|{p`v_gz9{1-eNw<#{*K|%;BqLVju z^c)+bbZF|~ijgRgE3!uj3>WSQWM^UoLrYADz^J6vtX3d4LJ*RElFabjD33(+m@Kv! zD;t6Ml0P+a4kjHTfOg~Y@n})ij7q}TB0I|PA$dZC2^f!pR|`vm5FQ27FFYEAxj4E7 zPK|ONmjTFtsWJsg$OohcMc(u*V(yDP_LwoD^K|^osKsKS)#dc5A$ZA2h&FQ=ZbjZ2 z6FeBZir;5va}1a<#3&r%bX3%vVSx}U33)U&NGg1z%!Xv@78yDV@Hrij3g98>?!e7q zwiy%SI)w1u(V3zWCi#Eu-3gqWWtA@QfU+qnql}_}1_TXCp!TYc80o6&mY^U+L|GJ@ zP6r~85E3>OmjO|5*MNZHO3;x(#RU`;Q4zRuN3Nm>A`apxil``pAYQ%y^HmjTIvM+W ztLt~~o!_tTnhD)+zVH3s<(%g{=XvO8=KuUQXa({(!h9kIV#A2$B^QCRx<~R~*?=SU zPm0MX>)Cl!wW>mHh;M9?;y{VuHe(VoRreT?9Ah`A8yD)@=@V8qsT&v@Z3#TOY-LxJ z!owpKp2hz%{~U2{X}iSO;&tFdAveDhnj=aYg?9cFf@upG7b!%vjl@kWHyI8a+~%30F2*09sd=Lep$W^?L{BuOVb1>&o z*t0v-D5nN`EUdvf44N(FL&`-7CnWp0!R$32C_Y>C2)eozp|l#BWLBXXSe&={=Ll0Z z{J6L`h_V6WY%+8(!RzX9oCJ+1+BBuORmmN-Vjbx_pu^-NTA^Hy626*S(HbPe(oG>4 zYYj;uEeDFF^@Me#7)}-lQ?$eN)6`wpC)lQY$9O0~960VC-k#M;Z}&$K5xIKP#i`Jd zf3imgwX)%j3`IoLo%1NG(>7?sDVBQ4ggFYq9Fa&kf@fc?-#RVp91L?RJU#R{cq1is zu~yqEP0$$EekD3r2kTCDM$ro*Gzzkwq5S7jkW59O5Vr0d1^E;mPV&!+D2Js}4?qz4 z=`U&d#qK~T!98uVX7`zcu3}KoPUa>?_@|QCVq@EUTcHQfU zu!}9@bx|zx_?>*n?I*%r$2OLt{kmLBgwPJ;)%a?h_%zu_-`14w`-; zf8P-oY@`_3`Xr2v=hdh}R6(nF%0P!t$g-BspQu$2pC5+o^BZFmwfRGprAtcniDGSR z?C|`rht)@_O6hmqS_*QloL1ys%TH!1*NToq=xA?syB}S=6;G-=FM(UjAkqS_wB@#0 ztj(zu8z~goqRDqCe?(HQx$fyf+gzPA0^P%*Ao7x4IB)aMQH@Cs>=CP>GZ2Y7PpHjbcS^?mJ({UgeB0uN=#DOxJ4W(^oJ+bRRKps0XzSz z2}oUrT6@-oHhs}zT&)Pg(Pz-zCaN`kHRqNap(tz1bqVotN;WEPBsv&T`_KPsLrqC~ z)g{n*wWb;^7qieFpzJuDAJl~VIPSsYWzlRFNJQr{{aj7#!79rCL>L0n>tG0O0*6xD%pE7HY^!y8KO ztAT04fmHh;9Zdg7{>c^xxyq_+U|?FlS~Ymp;4Bp_tnY-$cJA2d4z)&y&cKP0)gFN# zumaLY{wD%GO8ToZD=>>Rlw=94#-{|13or8zq%%xpn&@(4DRO%t6g35S0q z*x24YUo&zgsK1SeQf_1Ki2f5ct?XQdor|r@EfVA=l$J{kRwU9@J>iK)2ldrd`>_~F z9!);8HLw)iCqSY6lP#Mb%;E2*N5_)Ytb3wiiX*;33#&2V%w`6z!fTIoy)gSF=^}iH z7CEj9YiGnl;Qn46QN*g_#$|`UrcAI{6h#5}_53Sa#|a*VS930P2BH#C)R(Cxwo)C< z|J8V8IO0`idI)mA{9_6Q%;eejiX=NHBqMBgrfPpJYI^xENX3;PGN99RiY$(46?Ore zBZ?+h{xh4*oknabMJ6QX2w?!?hR{Gy2MsL$nXLc;KCB{Tty_?Ng*65m)MUfh>``(q zSum;&5&F>l=117D)e_cG88*UXUU7bAuto*hrvPCSq<5|(2_NfamA9C+1nDBFjLT3W zF;jt;3fWybts|^6N6`Y?mI&G+lqVAE+4xVIGK52TqS-AXG@>^X>IH}~36>5)_km)7 zYr_Xx5TOh~xC}`SM6O7h`>3tJ!L?*C9FDTFf!afjwzgb-$CUjOXv54geIh(zBEFr0 zqVdI!B2=LY-h$#J35riV=|VL-y7BMMn*!2381D zsx0zncOq8C8*EocheliZF{DZjElR*v@g~`o-S@qwN*{1W*lxtD`Tv7SM&x_A2zpJp zMc5GvJsS|oj66VS3cqH=&eOO+&UH)T8%re2CsFbDQFaZ#RMfiv9T&xNGAr} zKY~#h2eNIOL21xj@;{M+EOsZ~zRnXxs|W5e(Xu8+Qi3HY-7=rwA{ecA&LhE5D-vZ> zB=5=}9;-r1hl^44onge*5U~Lbh*e}{Cf$9(XVZ<`)U%aPVV6CMj~%#OXsij}ZbQC2C-P4=@wMoO(hAY}{0=ruk6{?cP?0=;!aydu9h$r)_ma;a2(moO0m~{935&7(uO>DE9=Z#)96&_8BTMu`T#nIymT*FX>Sg8? zPMP+87T#Em%rnqg2z(?=wqM{u9^8n|%64w60lDZxk1^$7YuU28y&~Svu5978CxU2d zYf%wOn4>@oP?x$3GGa)5;>8l;g?Y*k9};mI&`m3y28KhO!{f@8-G-9_ZNfT*CUe3G z*&fxhh8WYuYNLK6M8YNt63B3VYy>KAYl&lfdrjG=jY|T&qgk3zh{6{_Wm+c{;_!D| z^8-UoQou?0YVgaJ*1S*`>fE&Wm7oNwGo`Y`IsfM$Aq&KD<>C5Kl(Lm2kmRG~Ght;T zRw;$*4+G7(MTYUkNpk2xK$TEy&HqIBS&oQBdQ(r500`1KAe_``U* z*HOR9Rd8DU0b$%nVkC|cB9?*ER;h6cxAMQ5)QH!X3)}B@RQ$ zrsBgSu@%mpaj-#+Dc==o7h&8e`X>xzzf%_P&CxK)N>UoKwF;yA1o2znY*geKa%0&A z87CyBw<{`W=?MtC7$4*Z(&(k@BDQ8Uj|!OGN%;@9YK7e%`m>(Mj1|@J4cT7kh9~Qe z8<&5M6d?!m`DuW+xj#Zzl*63a#Nk8URZ?MHCS;IRZJh%-kD}*DuF!z7c7Z6=5yG<# zsK3%!!elG(k*Cz{F~b-V1fp;h9?89ktwKyIXlRs1Roe4^H5_w$p;^U0jxB;=>!xgy z^)5={6H*AFOJRuWz{4@51u3sVZB^q`G|&G;T5-YGklD=5Nh*r(ODV>QU^gdVwGswo zvna1Y{UWDT<&C}3#UEV}tzB`8$UzjuIExX$ zj#D`QK>R4tg`zl;9V0ALZ5dH~Ms7}sA+_}Q+)7YE@wOlp>7+D5Zf0d-=KKM>H)aZM{#ive9coeuNd1sC_hlRI3CkU(jdV zZS@dI=+zdhBM%03GvU=neXX+N$X<;(;xz}P!9p$&_Z&*t{m5r}!zAaO!#~td+g0I% z5Ct1GWgwS^grVAvYD3x)yvoljFo|-?KrlkyZFQM;7HCENxsEc%vV>kt{>iQt0FmJ* zrwX2ZHCoepUdZ6!#&I zov#+y2=KUwV=4DaNY{1dk0^<>>y+Fxj18(VPt;ey zj#yhA5(#5`WN~)%9O>_qKn26ti`N!=PCU2&9D_ zGM?~6F2tx>_tY!gA|z*MRx6FBXtBc`LIMK}t{Np80s6vf=@v?lMe_NB2rR?!#ZO+$+|G0lwUpN*K4J@opxtz-6|}_VUq`UR@@Px5$+}nTOWTk5k-eoKr+175 zw-(k~Q5nT&&3|U|{lh+w`?32(Vn}f|qe~~P6gmH^Ng+Z7NurkZuM4f)LyADdQFCB`23Bkn&U7^ ze&?%Y#e>Swa0PPr?p_6iPp$^c4A1*+iFs-BA1f#RQ{DM6TzZ}j)Z+%gf%LdC^2E{?Wjt)*R*1k z+F%VyRSbk+se#$)FNi=z$q&}~cLg7rcvgoHr=8Lbhaab;sktfttLY8H($d00&M&@N z+eHU*Clm%(EG9gWE@9_vdoqnt*KsKL$6 zC-PToNQ*V4By<=@vkO3+lS#!mI@J~uIyBtP;1OtucKR%`Eg4hyFP5J2L;|-~jb2xm zbFz0y#+lTQ<0-Bjo12-XwKpLW;nafv`Uo<&Zk5U~oiv!XnSVmMHL%c!N3j z;z(EmIW~I-!Xcc`y(sJMN5xA_>UFQySFDXu4J*)B+Y3kwCSIM!0gTX=^3@RQ0c~!N z&J#typ!J{u5fxnvTK-p~=phCf#re(xQkI~ycU5V9{+3k!8k^o}U`-h-TPfUBfyy=6 zwt!p8HN}Ju4Xw&Wz>`{y>Zk)ZlbWZb!_h~tS3-wIot#b^E|2m*GZG4(iKxxt)-*{D zrBKfeq88R}cyQ*2>-`1-BoUq7UK48%X{*2;kRR9bM{RH@T6e0>5ve;Rg@4k4ke=1E zo6<8{kIt7C{ed{h1QO8WpjlfPttXPjIwL$b)9YbftI>Hb@75*R`;PZyP;C9iA3K9D58}>iLHm4UtJZHP+2yRu1K%P z98k+pYdAlINP$g|7hL9O$#t*|Lq{k?y0dXyh7$H#Yh=hxf`HyxkbTQv>s1s$=sOc$ z#S=A&#rDM=5%^OCLq3JJW)YjCAJI~Ff`N=eod2kePO}i4lyCzP%+Y9Beaj0gMq}!v z4uk?X(}8rVqtQV8rRee1^o)eqqmnkbGl8h1c`gcukmv4Kv+Wz;X|-dhO*?<|A3`iF zS7?1GN5lm=g6yd}M$ZOsKY^!Z-66m++pdd|JQskQ$hVqu+eHo(&OO-zJhdA1(_XR8 zMFa#&mk1A7l`zBzVXh7Vwf}*jP*r5A-V-^q-A_6#nu5!O2X+B}`$nzTptq~{-~7#I>6eazw5YgU5(Ol=3iKR0>) z6H;M+Fzd*L?Tcxm>cvM2z6$r)3<&93oASRJ^;?TNL=QWkAK?pND`E!Nk|RnONKw?$ zL%X;`98@3I=QV^{N!2bvn+5T_87(8xN|%9Ag16yOvHvI@GxR0@6VW&}L4@wRuNFZ> zByqwTsu2lugfrG?k#UBPN5GDOU@6waeZa&G<&Tdr`^&^4Dqd}7DTIM2Lxo6T_thLm zR)TIsU3<0BlqYJciNV$AI%Kbgq}xIO1^%@0;`1XBW_dv8kqi$f7YAnDtARHlFQl|9 z2@xAPElUuC1TQMPMKCPHa0E@5IyMswZ^Ph#!Vq~NLe6m=1tA5m)#_IEt0k->KP}Fd zgQBxJM##l$ulFVKTcgQ3|H>vPA5xLg=aHpZaToQ&)KHKa&{xy;hVD<^2`cH;RG;YB z(!W*rA*G*ij8MNTX%9lv>$EYbv0=!;IgKmi&Ob*s=wRlEmI^BtmSsr4LUyT%{puF*ULwFHy?85!OhNwfzTq&`Y}N z@=vzX!C;~hC%=y`$wJkQEm9p0kQL*@o+B8V@I-tmHJQh}%xsIO7S-qs5YO!N_-n66 zKgAw`P2e-;eYfP( z0fr!Wt?xyE6@eB#mYTx|CshXs+IRjZQl%fvy(Y+ux-W))hWc6Pnnm);U}w zL5^QhGz9AjLq^fgtJT#W01C2!5U)viHCmTJtR}{pl{1Fhl|l%Rm^uh@0!>e!;$V&u z)n1~)t`8N&Z1w5ox><#6CA?ZF0uEwj2gs`=4j%0UT2Y5oWP@*p>urK^6>U)m zQ0O~%1`6jBdJkdF=R8V~cO(gp@2#n0rr~jjxQVN%j&`3&rPh9`phB{0eenIspg3*3e zP~v8dx<9Zdt7prLnbbz5l&WUZUW=4px-PEm)clp8m5p((G{VZ@^3M^aIecn00|`mo z{nRInLX*2N!mlo2i;x>~H->k+#6~dcQX@5kpj{SeCv+YTF-oKm#X+%yLjsL){1LQZ z98{U|ME!nZV;+L%Az6dS*(^5l)WJZDso6iGv!1o@Gz7JU#ITD7Hr=d#}erpKr`X1 zIoVvS2p-*m;#xx*YC=kA2j{P^4S6rN?I8bkyFXMyOQhWb)y}^~h6goAma$GBxhzrO zz##?v=nSG97DJ;6{F)(29$=)5$F4`yUQ_4M8X@f_up}f%c(s<&oN!1`69-%0))%P8 zI>T@ll&@n!I=uD#QbdAH+kvSUR5{ zbqKmHeP<9C+|>L7v4X-`YxLA~+-Y=CFtqHMl8RZvK*AsnZap{8zH3MDun|{34#Eu6 ztO#Yp9HIx+>p&gWok`ch^MYZyF@9y036 zM$s!O9-H(8%gbnau7TL`Mzd73Zyf~nZ)z3nd{Kw8qc#j-NxQqWwT>px&%^O(FehXv zGKkqJLuOaD9!>bK9q8{#g(O_r+;b!yE=je+42e+XC4*2^0@N3g5FZh7q%qSPB`Y7; z2#`)Enj(Ze0_!H>^JfF;SeWTR!#V@0C(#8F?@Is_N%aZJNVp;3pyWSBh6|L}np`2e z3o-(BO0O*!6!JzwOSOTDU{OqwOpjfCjb60X9dI_tJpWf4rYa~tR@16?yrJ~Qati@w zGMgh9lY7%*3=HNNp>^6c%R&jAOHrsRh}=iHxD#u_)5w2}*l7U%@C09Xun~@ss%aOb zM3(>6q$DCma!)$LAf@h?5n#f;nlFbYiJAvo;sA6Xkr3qsomD`$5I9oe(LChA!T=egQmLp zBtn5IS(B7kqcOvJ0jQNB?87>>Tyq6p+O#JU;6seHg1U%8eYasS{2ii8U%?f_B|qk` z2X8PpEm?7JZ)C>nCR=ga=0(=SmCFD5E9#e0b688K;%DcRBUQC|N=cQpCtNgSlas4(n*F zNGJgu`Fo%TSAeO>x}`*%!pBKsTT{(l8q2e9jwLleaQIN@Kj9wqvPR{>g?x zhcH7VH`xj4Xmr&UO^-!cO4$7bQ1E?dly?m#F#M=9q&xJ z@yLk(iBb6ADX#`ggFW`Nv?3T)I!HHl zL8=U&17K?RIuaj^lVSOPpU|5rm)*PdI#Hy6~<2v7>6sN%*%8N?jQKahTE*-S?F z4fs%yM*SS&L&WUjOt@)>FuDkh_Yih0m_S7X6^|<_;ngDZlVO*hSm&G2%@f|Wsej|Ibn`)vnrv@ z(xZEbM*7?RR8I$Uu;EbwRb;f?1&I*n8=`+IN>fwIKiPn6J%eKHPRR#FMpav9Ow~fw zSDi2r7=-fm!|1Us(ihWLE4S5*+e0iS00i|mZ9WZXxnRpBkpGF)n;o@nvOJw?mbQu} zMx@p}Hh&42Kds{d9sWw7DAdu0pJ|Tttuze&WW^*D1%hXHKXgkGiwiF>*jwhGBV=KI zvRZN_AtpXLhvfbe_>vk@g6T~VA4F>+VW+gOM$#$leK-z>HB2GlU<+NK2tIj%X7?KH zbZAX{fnrq@neb}Nb}*@WUB2PO!}rO|Y*vq7<_- zzb(zuE#hv?c0WyZ*#KcxT10yE@_)6U(+^@n%4Mi#(J5vq6MKzPydy=4gNT5Ynm=Cy zWDI8agLfhrkw(t23K7Dac+YGv1bfy|vjlyHl&L@altcM(NWX~XQ(?WRw2B_5(;P19LJ%(`H-ubUP{l!~8i zYI?JSO;?_HnFMK-x^jX5UbxiAAu)#pdJ&9*+^{w=qN5qHkw2%k7|AHs+}hn&bL4{R z82N$_hf4X&VojhZ+NxnX17!sk$qZ>8pE7|lj@A{vqzppzWBdHvFIA5Ng9^#wgvML| zm`-(FUeU5*pr~I%B4uo}V?^!w4>l3L3{S15`)W~NQ!9z<2po{V3gL7{sw%qFFk>Rn zl46NZwTcX(jV8$Pk~$dBf+WFTMutSCG(%yXIs{~kN`$uqF65shSCKMJSpt&5hX58^ z72?j*NBJ&)5W%!v4YXYD(S`y(1V8H-hz{Qb+Y8Nf&|?1G>P~SU_1(bK9Y|U`n1Sr8 znGQs=2#qO7#~rNJ9AWrtT~v$IMv(uj`7JKeB_;=*zHGH{k_h_#su1dG!Z9+eDZdgu zXWBesuerd39j~dfK_xU{i;#yL%&U=17mPAX2?LdY5{sOa)e^NMhBQz9Iil$*tO@A1 zLmAdNMgX^>B?5CJX^sYUuenXJ;Uo{;y+(b+(Na*+9A$r#aQ7>slgz6N^=A0hBCg>~ zVyI5V50=0M19o55h+fX0(}rggUIwe8?or#X0G2<(X296pYubgH;F>T^6gwrXY_$uOnarN3*TBU4ki%PZL<3O5K$^PXDh|pSBJ2dc zt(kzJi8I)=+Hp|;hAkm~nUvH}C=wy7rFG{BHWKx0)?B3Y5;|DIZS1Rfjtn3K z!Hv^MqMfj^QQ-zNN4Wf9-tjP47uV5<4y&k%7VG(oP%e1#D@s{KUXCU^a64qwptW)c zNH9)hZD|BoZLciWFYF`63(Fm-Pr$sM2=SmptQQ~Q{N_I)#px;sKpy=fv5xu<*eO&H zzYrF6TNnakgGtg-G)v8dV`LPy14kT1Dj-=F3qp0`Hs1+t$yLB*WHy_G?C@wmx;aH<)J0v4=Pj(A0SeU0r zObII*PaFkGt__CsWEHCUk%$%)S_nH*z&MEPD3~nxKeidoN(F#~CxS#o19i_nCCFad zJrN0jwh4k$=$x2l@!>iLTi+JNh-UIWs&Hg^W7CGDBTNaGjY4-T!G%e=M^>eAzB$v+ zPY+@s=MgmyZhkNurRa#*aI^W;p=l_L*!=k+Rs&H4U{)r7?X7G*O*W3BE=3(4?-ev! z0E-E>gIhyy!o=6%WmMf08oV7jg9&TGID{gw@ln55w)l~`j7ATl@^RAw{RBt~H+4ZI1TX&+!FC68_}2!tAO-4!S&-Uk zM7hISKOkc8n?-6#&{pKN^GYN<@z1>-XYVwg+sL&y|l zkCBvt^v^JZD9Qg^kP0*|N0+9uIHA6T_{g~`8N&9$Kwf9=mrs)0yYaq4LeF~3A>*{6je{>SSRl%(4kFK zom7<6ND8zx^}*|yrgT4l1neqM8c~71wS*@cMmiRyab4;N1t58V$;-4SsiG#R4H-h; zkrQ9@ z-$ERiS;Y^Ha!^k+Qq*6HwTKoO5t~b8q*&E4Xroa5$UoVv(c#S_Xjz&1ncN1kMUN*( z4-s#E{0L@<-+>?2qgCTVLEJ_WeMYFMCR|4&iV#jQnQWHRVtarODMq6CGIC9LH8Ri= zEkb1+;iFO$(uZV=foCul?=;f4zX;q30g=7bCzFF_aGpoE42-yGOX*RQ9?7S4AV&05!nD7EEh`>^dO z5kUav9pFNW+l zJE9DYnyd4mSA!IcFz`hKfI*w>{OhP(3CGK5^~iK0!p2$Z?oxgq`_O%#koqcScnaT33TpGXJYZtFlMZd(a8A z`t_#|w7C$1vOVsx^xyf9`rTBgPiU%$bGF7k523D878^opVZhWV`~0r|{I4c{)NZb? zr$f+H-6Hhn+q);ZyC)pAw0zVE*;L6uGDw3q92_tTL6oQSW3_EJ#Yij|=T9$#M75Dt z>RqIOnDACw{4jK=RB&%-^v~bo9W-yXR5lYpzC!iyp>W!U-AXHs#Pi z?Oxdc&EaUX;s*}-Pgszgv0+ESW$dU8A;d5)E8_TLBBb23nUkjo7MC@`V}vm!VuNE4baef1Bfl3Jx8yi2g zA^3ijj|CAlz2u@4kfMV;!DA%O!vKucRJ+})K(SE@<{(=uEa8M~g>C8}CL6C)n~6SH z484dUsbPhekBz6zHUT~)cdu6ssNRLf!d$Y!{>~b}_h~1rY+}hHA$yG#m8GJ=77fw) zu!Ph&od5H;L_-vmLqjnOayH9IGp(Z?!GH~CKYujhlQX=q1kl(c&5FJeoKb>P zJgmSRxhH#srl_D=;P2cB0D-{ohzN2RT*lxz{~TrI(1!C+t8xs4IjV0Rz{yf&m=8|E z6A7zZq|kfZq5jd>` zOiG&n)vTCcF;P>>00fzlR5bG9f>xw<{>WdGIJ7VrFzXhimvTEdzzc4_nCKSb^A5^!w(5gE}qS~OMD!B)hFk#aY zi76to`M+K)L;YypE7Frzqw|_@i|7oF8v{I=6k z{!YRLSs&O}{F;W>(cT0QR>t;WHX`32z5^w zsAzvmWLq6ARyjYK4PyY|pgBtT{EAn&N3wN2i(ZZ>v%ttz!t_Q*Wy0>4^HnueNkuXi ztL7yUzpDVISu7-IY1r&4;;IKAyX^D3RNN%mSy8O0(Bz*ZwO-^<;sPC&7=i&MIRKB~ zh({C5Zkfv{%Z5X4_ZWd0s)yEbEd^jHw+PglR{{Zy&OmT&g08Szo0&^-e%QxlfeJ6A z>XG4LwS5r>M8yL-YQd8__V7?X|$i z)qKc?qHm=fY*K6xJE@X&hksD2L*f}any^I#qUyT7Ro8!wFi@o36^3kW=~n)g9b$~S zy9tFjL?T@e`MOG(exmlDdP1ND&#HK@=$`MvhkbR_DS(TPB8Gvh3PwY)GEB5Z->Lx# z%3cNvH&I|HEW~xB{WR=%1r=<8Q^ck+8a(V8(9&W1Xg!a{e!)_e!bZYPD`FVT!N%5p zFe_Wz2_klBq{3rFFe+`6I7TZ9cAGdk|2mRUwFQ2?l1~&5!W59`z$ln2VP#8UDn+#s zrhC~})5AbNKWg>#!dv<8HE0^03JtW~x=7zxZ3x36R=A$KE8AV!m#C{?jhccs=q~Kj zH^eI43_h)FvHL`kLApR#CsFyX=bvl}QPnDA2o1HJqn6~Eu2{s%NKq2%BA^s5Nn}{? zJL4G9EU4TJp#}-gmE{FMWCfloW)0V;SEXfNN7Nc`O|d+$shvNT34y@-5B5$5vxb? zA0rgASY7L$u2r*j#1N=$=ZD63Dde^CzZ&z6a$O~DQhd}vMx=;9)o~AaI!zKL2_H@jnv>I_S{$%lVU<^`9ubj-q%=2SE&-m6)iP4K#20p8gOR+sAyB&^Ze?{cifPDwbkr_NEtN0g5! zaqU2r3x^K0gcEWDPsG9u3^YQd zwVn_|PT90EkgZQ4R5hZV;0C+it3e_tsll?Ty2D@kUq=R#yEnu5UJJ<#){%jzGPL>3 z(~FDq)fQ!~El|JC4a^Z|6Q$k{a4^gob_53L~pqOe)*rT>A{)^rZl8A2Dy zQ01@GtoQlFeHbA4Cvu6I+#-D|S4@2(XOWg%QAE_VQZx|O8^{JhJ5rS9pn^fz_hf6* zVhRB)g_v96{U0NtS=Sh$pU**;7JL7nAOEPx($f*CVAo1u`%ImqrV-?`qX-}vDG8F! z>kSm5bC~B8G^r_8`ky1@OzPsoM;g(}nh9vlloOJlVDM=b4~Hp7&LD-*R$Wn(a%^*~ zCyCehI#Mkp3+x&R#&;z_AaK3OZURcUw`&r|luLm$rp^(#4cO-{p=P8O+5fAl_p7dx zROWsuk?(&XkV|m0N)E_UH9_8iV^*my>QuSNqKN3KY zuJ?tpZD=4$A5<&0 z8dTnBR_A{rukwg3;;Zqf#kR=ERSBPe>OhWKfge7-gVJ5n_Y;L@rkbcyrVU56y8nT+ zGJp`M=kjpn>CpE;e3(gsvl*^RjYktWqjatYRD^0)bqkg#-Osd|g{}UG&+j|}XhyM? z?@-Eu+`vGnmNoiQdMXmcfpt4S@`)U5np%|an#4L1giF>NC>A75MNtK~EOk4u zU(k2XWLz4VaEwfOwW#>@hiGkRAaN~pjEde2COh_8gL#3LM&@-1wjd6%U@$6!bt1NG zj>2eI>SqZg9BkwmkzXPFJ>6s23fid?Q#WcU;ih#oYd7$raUr6AO)RSiWt zWU|<83HCDh+*4kS?XCT)#czaqQNl4YWuRC`p)5 zG@=a#jD^8e4J_VwTygAz_KNZ2kC|A$dU*Nx#H!URj$1YP=T182*nRd{uxRm` zOBU3J#>?Xq)ur>ti;c1Q6V=Mn`Hium(){tE>R5S6c}dOx-edRmetps-bpBiKUpMsF zk`;$fELgH)-0(Lvy~zaZ8picmIB3#e2WDZ2eC;{p)wep7g8#_4p?qIk9{pvS)d|Z&YX;P^>N}SNxyn z7D@}E!r2)r`>FqWkX0*|u0C<>xQXE-tjmSNu`cHB*ITf2TQ9B7efhHGOIDn?syE&v ze_`st8{QWKnf_H{C(Lm2N2?fw(e&jSMNte;fV{FMgCO>_U7z4MGu(tHug?9O~RB8dIH`}&bdFPK_ClG&ph zWp!5PXzZzcfs2r3@x;;vlLCkcy&{avdz?=B^+)W*?&t!?EROvzBgyglSYautC6Q!d zyc0<_#_Hqq$Cnfi9~&DV8bAE-rLphh-Cy$FQPEL$oGny7UhnTFjh6klS432K^zm8L81iL>!tTZRf4jdr`9Dwn)r0== zuTK8YQ-5{cZ@z5#vegSEh1tVQ`Rhr)HF46ir3+dsj$U!xOOG8JpNLnR`d3qbdcS2S zO&tA-_A*(_s)&gzu#oI`rv8Tw{^CLFelc|W*8Sq7|Iq!#&UR@}tQtRV*|FI~#((Ji z?c(<6!pHb-3oLE7&e){yHF?Jk_|NO!Xutzc9&Yv@nLHcQ{&{w~rk$e6KQKF8lV@Yv zKhI9rv{N+s2WF>h@@!1|=h^9+c8Vtd!0dEQo{ee$JUd;}PSNBan4PZ4voY~u{#MU#JEcDg3d#3iBP5yz| z>6$zn)BbsOx~83?$v-eVU6W^H+CR@u*R)eK`3GjFYw~PN`{&u|ns$mN|G?~YO`eTu z|2#Wg(@xRkADEr4$+I!-pJ%6Q+9{g+1GCdLc{Zl~^Xzm@J4KU!V0OAD&&ITWo}I2~ zr)csI%ud(j*_igvv(q*06ixnt+3A`*8`J)IcDkmWqRBrnJ6)4!W76&(mCjY?fbWNU(Y5)8Wr|U6$yx=5^{-Y*V&z)GlIN^?+_FcK-M^D*&WRsE8 z7C+`^AAjQ0pEmNiEt+$8*zb_1pEUfOCtmjHxx3x*tViFm_&Gc5y+yNm$6KC#AW9ZzRh2saoywIw(VBe&s+NE&F*~Fx!?WEhvzKZXuFM0_|v~UrMc^0zP{g& z7hUj@(#kiFUv=4=pZ)nOKezIv+wM5xg#RdPcE>ZXdgj+Yyz6OuZvLpXPh7M3$cy*e z`%zEZYMayd-299+n>M#O^5VaIW8UriT=n&bhR=D_!N1vS#V|}x_s*w{?fHrCee_kIz47SU_YNN4<}0uH$tKtR;!QVyWV5Xv zz0rMt+4948uKmtlTYl?TSGOKobL+F`pLSJY%L~@bTYKzn&-%g_%I*EvZ1(xhPP+HF zJ-54V(|gYS;6bpx?gg zqbF6jdg~V!Zn^PCc3O1ym-pQEqX%ES|E^Dd@AqGP>Ma)>dcvm;`1HAR7w>&XLzrW2Lyy7v8`0kk72$>wTv+KlY|IrG2h= z(a!Js`Bz_j{Ox=1aR1s*{;yYley=B<^}7AHyLS8M&foR*i}(Kcyd4+4?*~7;;-$ww zZ}|Z?ZS>pC_I~-sfB4&nzVoKBxBcY2_wKalX`dQ?T6z9&9{k(ucf9VizufUjTiv|o zvoCqWC5L?EtbhLTQ?IS;^YMA#IpD%qK5*B43l{Hv+Ezzi{L;t1=QpPxeAJ6?yt=UE zn(ut^PtU*Vd)Hrb_4ZfY^wk@8+xpCNH@dv=^Eo>$`u@rtcKG8)YZjk#;i8Z2_p2YC zdG|)6yKQ#(Var>e*!t{=#k+mrbzj=@{O3IX8QVVicKVbI{T>r!CSH0uFA0IQZ!+__|$odE6JDyvwJazV(}S zT6FAfzx>E$E4IJ&#v7mb;X5z<#!;KR{Ej={e(Q=`-hAXCpQ)aB=>6yIb?Vty{{E2t zxBgmVuNNPA!>T`=vcn-y9oc>F^KZNO_$wa2gG{jyZV)D7C(CUq<{a~&iB3JpxY0+{UfLD`TD1Q z`3Vnfdi!VJcgn^4KKqJyKk6BqzTrQfaL1ExUv&HG$L#R(joKR@z1Lg6bnRxBT)pMz zHr?j1ZEjwB_(s?6a@jREjJ|)TMV~rgyE8Z0_63_StKD|i#*f+b&0F1e{%gK*>5-KW z{`I%dc**`39r)#wuRZYSTYq`?EgyK`_dj^*HP^oImP+Zx+q~r~-@4&TYp(p;PY*ur ztOx(_%y+N))jj|F^?!Wx4S)E-8SkIB_N066J?zfwe)iA`llZ~yw`Tm-h8aEV8OqvdG4lj?zwC3@BTD$`}kFt)>^My`igVj z{NuO(V)U86`^rlWzUJopzQ6pA`l)+<^|24kD{OY?N8YvDwmU95{-HUq{Q57>9s1y< zCtmQf?;W!9w|Cog`Nlup?5`(m^`al#c-KisE_~L(`@H&Nul~fEyZ*G_;=PZ)sCvt{ ze|_F@n=D!So&_KK!0!&*pBLe`4#T2fzK9 zgI7NA!_U6)+HZX8&-Y$+#FbZ-Z`gN-({6w6VGk|b?D40Zx%&(L`pWZ`KX&;KpZvUU z6&ip3T?mgmF|9STh4%+wK%Xi%JneX`Yi*Nh$U!HQsrnm39 zV|AZHfAot7&iMO&7Y*J0<l{8_ub>y&;s=ER)E0kx|A#;9 R4xh7VWZ(8@ny>zs{{!)Uoe=;4 literal 0 HcmV?d00001 diff --git a/modules/servers/upCloudVps/templates/assets/img/editvm.png b/modules/servers/upCloudVps/templates/assets/img/editvm.png new file mode 100644 index 0000000000000000000000000000000000000000..fd2ccf1e7a7db5f3fd945bd3bcc44797cb27f07f GIT binary patch literal 238816 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 t-}Ad&F1`0+2_*f{x>{|6*n35Ngx literal 0 HcmV?d00001 diff --git a/modules/servers/upCloudVps/templates/assets/img/graphs.png b/modules/servers/upCloudVps/templates/assets/img/graphs.png new file mode 100644 index 0000000000000000000000000000000000000000..962b15df0d5104616b9a885d1246ac207db615ad GIT binary patch literal 226601 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 literal 0 HcmV?d00001 diff --git a/modules/servers/upCloudVps/templates/assets/img/network.png b/modules/servers/upCloudVps/templates/assets/img/network.png new file mode 100644 index 0000000000000000000000000000000000000000..b59deac496a7b823c154303a19b78752ed07670d GIT binary patch literal 231673 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 literal 0 HcmV?d00001 diff --git a/modules/servers/upCloudVps/templates/assets/img/reboot.png b/modules/servers/upCloudVps/templates/assets/img/reboot.png new file mode 100644 index 0000000000000000000000000000000000000000..32917c198f5abdabd4e58a4d0e4318b8920ce5e0 GIT binary patch literal 226336 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 literal 0 HcmV?d00001 diff --git a/modules/servers/upCloudVps/templates/assets/img/sort_asc.png b/modules/servers/upCloudVps/templates/assets/img/sort_asc.png new file mode 100644 index 0000000000000000000000000000000000000000..a88d7975fe9017e4e5f2289a94bd1ed66a5f59dc GIT binary patch literal 1118 zcmbVLO=#0l98awuV{uMt6P_}u4rcI3idKFP2SpU%ZJIE?RL`X zK?Oyo=*5GG2SxDYK=7akJqV&hrl{aWJa`y*5xh+1%i2y4V}gO?edPc9{r;a9vjc}) zn|Cxb4AYwFmvVG%_ui)U^y_4!ujsO!qzYuv8YUIR!Aw%KiWp=JrG#@>(I!s4#N7H->?w+cxsH2#GA};A>g8lyFDGPKh!5)vuP_{)}*83+N zJUBU!S0_i+E{*Lu1iGsNB``2iK-CyCU7?y_mv{xb_pUh>ESZqe1Y2{eAZLMSIT%EO zFrdOH1W^=3p>Qk~I{J+k#s5zQ@j{%aIA!l^GQjJ zqA1Uc2%!{8qBKfMNh#9DCnKS_*uZ8?mnf!+8@f8xtz#prVg=E`3bCBLWsNmDAX~PG z<(4fQh=UOzE2?gKXRkc9XeI3Er?HlHECVd%SI}3`hy1_du3@$R$r(qT;k@Sft63UX zv;)2Ea_iH>^6+4jPK-lGM{Zw37Tz>~~zlHzO61x51(V4jcaKrcIVDG$-d>)z}S|7f!xxYhfUE}Kj zug_h&HZN}go22$5Ym1}P8~vYNx7-~$TWFJ;_nh!wFYSAQJF{CCo=xpK8^7?iY1^!H haOA^1D_`VC7fU=jcT literal 0 HcmV?d00001 diff --git a/modules/servers/upCloudVps/templates/assets/img/sort_asc_disabled.png b/modules/servers/upCloudVps/templates/assets/img/sort_asc_disabled.png new file mode 100644 index 0000000000000000000000000000000000000000..dcd7b7b8cab2304b374e6e4b9dc8c05faa2e1130 GIT binary patch literal 2916 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 literal 0 HcmV?d00001 diff --git a/modules/servers/upCloudVps/templates/assets/img/sort_desc.png b/modules/servers/upCloudVps/templates/assets/img/sort_desc.png new file mode 100644 index 0000000000000000000000000000000000000000..def071ed5afd264a036f6d9e75856366fd6ad153 GIT binary patch literal 1127 zcmbVMOK8+U7*1U&zKRu5sR)h{1;yRWWV^4}ShvZpU2*HWU2!iy(qy)cZ89;Lb+`3m zMbruv!GjkO!3qksP*5)lD)k}=Dp*ht-n@8G5m8XoN!zU+ih_Y;=AZe$?|)|~*Ri8v z(dtDU$2DZy)jV65`|pB!_H}d7Cv0h=sUqzpC0fy3%q0!dg+a#Bx^W(BM*oq=xP{{a zC9_bZ#q2IgCss)FbwX9kVQ7wPX{|b%-is;d!ri7V^Y8E8=YeU+{JuyQW*r6hnC$~D z?i}bS=mWia!r)uCftISo2rNuBP__DOPpZoN6tBeg{;|M=DHYl)^V3chvpJv;7lTL$ z26Y&PAc{gL+#HL=wg3?#C_qs_Vi3iouqZ(YW*(kdbB&UeSJN}Lm?ZN(lsb|iR4SEF zB^)Adw}29fgwG+0L8cM(`faLJgSNN6#-L(PcTI+l@K3y+Xf(g*^61+0|J+O6zN2mb?UNGh6GU@A{1+eF%d@N2(^XdVmhis(y25|iAr;gV=io5OsYy0 zB}Gv|2&GUGrBPB%s*yG^841Ug8a88lRI_zlvuiTDGuXsmv6A9qjS{y&NMEf3ay^6+ zuZK85>5PD^rkl1e`{kLAR>iJ)6dP%mSYRr@k~xQcDE=$%X{_--ITM&Og5Ml}G)wJ> zb)dhUZG9%p4iC23#JFrUCcmwHz{cugMoku~ue-kg{Mj0~%`FeCcz9jAdg}QET-kSG za`+2B_+lRTaeAVz>E`F1pN7h>B=BbGqcz13d%ywZR&4OjkNNrF_U}#EcXDGa@V52B z>JnIW7#s%CHi literal 0 HcmV?d00001 diff --git a/modules/servers/upCloudVps/templates/assets/img/sort_desc_disabled.png b/modules/servers/upCloudVps/templates/assets/img/sort_desc_disabled.png new file mode 100644 index 0000000000000000000000000000000000000000..7824973cc60fc1841b16f2cb39323cefcdc3f942 GIT binary patch literal 1045 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

-d^A-PIa{imvM(_pn(tcu}U;TD-ruTPNwD+fCm1 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 literal 0 HcmV?d00001 diff --git a/modules/servers/upCloudVps/templates/assets/img/vnc.png b/modules/servers/upCloudVps/templates/assets/img/vnc.png new file mode 100644 index 0000000000000000000000000000000000000000..72cfab709652d44c1a028c2acddae4579da36867 GIT binary patch literal 225941 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Î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 literal 0 HcmV?d00001 diff --git a/modules/servers/upCloudVps/templates/assets/img/vps-details.png b/modules/servers/upCloudVps/templates/assets/img/vps-details.png new file mode 100644 index 0000000000000000000000000000000000000000..c66095c16eb6db0545d28f45032675f7b7d75034 GIT binary patch literal 2430 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 literal 0 HcmV?d00001 diff --git a/modules/servers/upCloudVps/templates/assets/js/Chart.js b/modules/servers/upCloudVps/templates/assets/js/Chart.js new file mode 100644 index 0000000..15043ae --- /dev/null +++ b/modules/servers/upCloudVps/templates/assets/js/Chart.js @@ -0,0 +1,14456 @@ +/*! + * Chart.js + * http://chartjs.org/ + * Version: 2.7.3 + * + * Copyright 2018 Chart.js Contributors + * Released under the MIT license + * https://github.com/chartjs/Chart.js/blob/master/LICENSE.md + */ +(function(f){if(typeof exports==="object"&&typeof module!=="undefined"){module.exports=f()}else if(typeof define==="function"&&define.amd){define([],f)}else{var g;if(typeof window!=="undefined"){g=window}else if(typeof global!=="undefined"){g=global}else if(typeof self!=="undefined"){g=self}else{g=this}g.Chart = f()}})(function(){var define,module,exports;return (function(){function r(e,n,t){function o(i,f){if(!n[i]){if(!e[i]){var c="function"==typeof require&&require;if(!f&&c)return c(i,!0);if(u)return u(i,!0);var a=new Error("Cannot find module '"+i+"'");throw a.code="MODULE_NOT_FOUND",a}var p=n[i]={exports:{}};e[i][0].call(p.exports,function(r){var n=e[i][1][r];return o(n||r)},p,p.exports,r,e,n,t)}return n[i].exports}for(var u="function"==typeof require&&require,i=0;i lum2) { + return (lum1 + 0.05) / (lum2 + 0.05); + } + return (lum2 + 0.05) / (lum1 + 0.05); + }, + + level: function (color2) { + var contrastRatio = this.contrast(color2); + if (contrastRatio >= 7.1) { + return 'AAA'; + } + + return (contrastRatio >= 4.5) ? 'AA' : ''; + }, + + dark: function () { + // YIQ equation from http://24ways.org/2010/calculating-color-contrast + var rgb = this.values.rgb; + var yiq = (rgb[0] * 299 + rgb[1] * 587 + rgb[2] * 114) / 1000; + return yiq < 128; + }, + + light: function () { + return !this.dark(); + }, + + negate: function () { + var rgb = []; + for (var i = 0; i < 3; i++) { + rgb[i] = 255 - this.values.rgb[i]; + } + this.setValues('rgb', rgb); + return this; + }, + + lighten: function (ratio) { + var hsl = this.values.hsl; + hsl[2] += hsl[2] * ratio; + this.setValues('hsl', hsl); + return this; + }, + + darken: function (ratio) { + var hsl = this.values.hsl; + hsl[2] -= hsl[2] * ratio; + this.setValues('hsl', hsl); + return this; + }, + + saturate: function (ratio) { + var hsl = this.values.hsl; + hsl[1] += hsl[1] * ratio; + this.setValues('hsl', hsl); + return this; + }, + + desaturate: function (ratio) { + var hsl = this.values.hsl; + hsl[1] -= hsl[1] * ratio; + this.setValues('hsl', hsl); + return this; + }, + + whiten: function (ratio) { + var hwb = this.values.hwb; + hwb[1] += hwb[1] * ratio; + this.setValues('hwb', hwb); + return this; + }, + + blacken: function (ratio) { + var hwb = this.values.hwb; + hwb[2] += hwb[2] * ratio; + this.setValues('hwb', hwb); + return this; + }, + + greyscale: function () { + var rgb = this.values.rgb; + // http://en.wikipedia.org/wiki/Grayscale#Converting_color_to_grayscale + var val = rgb[0] * 0.3 + rgb[1] * 0.59 + rgb[2] * 0.11; + this.setValues('rgb', [val, val, val]); + return this; + }, + + clearer: function (ratio) { + var alpha = this.values.alpha; + this.setValues('alpha', alpha - (alpha * ratio)); + return this; + }, + + opaquer: function (ratio) { + var alpha = this.values.alpha; + this.setValues('alpha', alpha + (alpha * ratio)); + return this; + }, + + rotate: function (degrees) { + var hsl = this.values.hsl; + var hue = (hsl[0] + degrees) % 360; + hsl[0] = hue < 0 ? 360 + hue : hue; + this.setValues('hsl', hsl); + return this; + }, + + /** + * Ported from sass implementation in C + * https://github.com/sass/libsass/blob/0e6b4a2850092356aa3ece07c6b249f0221caced/functions.cpp#L209 + */ + mix: function (mixinColor, weight) { + var color1 = this; + var color2 = mixinColor; + var p = weight === undefined ? 0.5 : weight; + + var w = 2 * p - 1; + var a = color1.alpha() - color2.alpha(); + + var w1 = (((w * a === -1) ? w : (w + a) / (1 + w * a)) + 1) / 2.0; + var w2 = 1 - w1; + + return this + .rgb( + w1 * color1.red() + w2 * color2.red(), + w1 * color1.green() + w2 * color2.green(), + w1 * color1.blue() + w2 * color2.blue() + ) + .alpha(color1.alpha() * p + color2.alpha() * (1 - p)); + }, + + toJSON: function () { + return this.rgb(); + }, + + clone: function () { + // NOTE(SB): using node-clone creates a dependency to Buffer when using browserify, + // making the final build way to big to embed in Chart.js. So let's do it manually, + // assuming that values to clone are 1 dimension arrays containing only numbers, + // except 'alpha' which is a number. + var result = new Color(); + var source = this.values; + var target = result.values; + var value, type; + + for (var prop in source) { + if (source.hasOwnProperty(prop)) { + value = source[prop]; + type = ({}).toString.call(value); + if (type === '[object Array]') { + target[prop] = value.slice(0); + } else if (type === '[object Number]') { + target[prop] = value; + } else { + console.error('unexpected color value:', value); + } + } + } + + return result; + } +}; + +Color.prototype.spaces = { + rgb: ['red', 'green', 'blue'], + hsl: ['hue', 'saturation', 'lightness'], + hsv: ['hue', 'saturation', 'value'], + hwb: ['hue', 'whiteness', 'blackness'], + cmyk: ['cyan', 'magenta', 'yellow', 'black'] +}; + +Color.prototype.maxes = { + rgb: [255, 255, 255], + hsl: [360, 100, 100], + hsv: [360, 100, 100], + hwb: [360, 100, 100], + cmyk: [100, 100, 100, 100] +}; + +Color.prototype.getValues = function (space) { + var values = this.values; + var vals = {}; + + for (var i = 0; i < space.length; i++) { + vals[space.charAt(i)] = values[space][i]; + } + + if (values.alpha !== 1) { + vals.a = values.alpha; + } + + // {r: 255, g: 255, b: 255, a: 0.4} + return vals; +}; + +Color.prototype.setValues = function (space, vals) { + var values = this.values; + var spaces = this.spaces; + var maxes = this.maxes; + var alpha = 1; + var i; + + this.valid = true; + + if (space === 'alpha') { + alpha = vals; + } else if (vals.length) { + // [10, 10, 10] + values[space] = vals.slice(0, space.length); + alpha = vals[space.length]; + } else if (vals[space.charAt(0)] !== undefined) { + // {r: 10, g: 10, b: 10} + for (i = 0; i < space.length; i++) { + values[space][i] = vals[space.charAt(i)]; + } + + alpha = vals.a; + } else if (vals[spaces[space][0]] !== undefined) { + // {red: 10, green: 10, blue: 10} + var chans = spaces[space]; + + for (i = 0; i < space.length; i++) { + values[space][i] = vals[chans[i]]; + } + + alpha = vals.alpha; + } + + values.alpha = Math.max(0, Math.min(1, (alpha === undefined ? values.alpha : alpha))); + + if (space === 'alpha') { + return false; + } + + var capped; + + // cap values of the space prior converting all values + for (i = 0; i < space.length; i++) { + capped = Math.max(0, Math.min(maxes[space][i], values[space][i])); + values[space][i] = Math.round(capped); + } + + // convert to all the other color spaces + for (var sname in spaces) { + if (sname !== space) { + values[sname] = convert[space][sname](values[space]); + } + } + + return true; +}; + +Color.prototype.setSpace = function (space, args) { + var vals = args[0]; + + if (vals === undefined) { + // color.rgb() + return this.getValues(space); + } + + // color.rgb(10, 10, 10) + if (typeof vals === 'number') { + vals = Array.prototype.slice.call(args); + } + + this.setValues(space, vals); + return this; +}; + +Color.prototype.setChannel = function (space, index, val) { + var svalues = this.values[space]; + if (val === undefined) { + // color.red() + return svalues[index]; + } else if (val === svalues[index]) { + // color.red(color.red()) + return this; + } + + // color.red(100) + svalues[index] = val; + this.setValues(space, svalues); + + return this; +}; + +if (typeof window !== 'undefined') { + window.Color = Color; +} + +module.exports = Color; + +},{"2":2,"5":5}],4:[function(require,module,exports){ +/* MIT license */ + +module.exports = { + rgb2hsl: rgb2hsl, + rgb2hsv: rgb2hsv, + rgb2hwb: rgb2hwb, + rgb2cmyk: rgb2cmyk, + rgb2keyword: rgb2keyword, + rgb2xyz: rgb2xyz, + rgb2lab: rgb2lab, + rgb2lch: rgb2lch, + + hsl2rgb: hsl2rgb, + hsl2hsv: hsl2hsv, + hsl2hwb: hsl2hwb, + hsl2cmyk: hsl2cmyk, + hsl2keyword: hsl2keyword, + + hsv2rgb: hsv2rgb, + hsv2hsl: hsv2hsl, + hsv2hwb: hsv2hwb, + hsv2cmyk: hsv2cmyk, + hsv2keyword: hsv2keyword, + + hwb2rgb: hwb2rgb, + hwb2hsl: hwb2hsl, + hwb2hsv: hwb2hsv, + hwb2cmyk: hwb2cmyk, + hwb2keyword: hwb2keyword, + + cmyk2rgb: cmyk2rgb, + cmyk2hsl: cmyk2hsl, + cmyk2hsv: cmyk2hsv, + cmyk2hwb: cmyk2hwb, + cmyk2keyword: cmyk2keyword, + + keyword2rgb: keyword2rgb, + keyword2hsl: keyword2hsl, + keyword2hsv: keyword2hsv, + keyword2hwb: keyword2hwb, + keyword2cmyk: keyword2cmyk, + keyword2lab: keyword2lab, + keyword2xyz: keyword2xyz, + + xyz2rgb: xyz2rgb, + xyz2lab: xyz2lab, + xyz2lch: xyz2lch, + + lab2xyz: lab2xyz, + lab2rgb: lab2rgb, + lab2lch: lab2lch, + + lch2lab: lch2lab, + lch2xyz: lch2xyz, + lch2rgb: lch2rgb +} + + +function rgb2hsl(rgb) { + var r = rgb[0]/255, + g = rgb[1]/255, + b = rgb[2]/255, + min = Math.min(r, g, b), + max = Math.max(r, g, b), + delta = max - min, + h, s, l; + + if (max == min) + h = 0; + else if (r == max) + h = (g - b) / delta; + else if (g == max) + h = 2 + (b - r) / delta; + else if (b == max) + h = 4 + (r - g)/ delta; + + h = Math.min(h * 60, 360); + + if (h < 0) + h += 360; + + l = (min + max) / 2; + + if (max == min) + s = 0; + else if (l <= 0.5) + s = delta / (max + min); + else + s = delta / (2 - max - min); + + return [h, s * 100, l * 100]; +} + +function rgb2hsv(rgb) { + var r = rgb[0], + g = rgb[1], + b = rgb[2], + min = Math.min(r, g, b), + max = Math.max(r, g, b), + delta = max - min, + h, s, v; + + if (max == 0) + s = 0; + else + s = (delta/max * 1000)/10; + + if (max == min) + h = 0; + else if (r == max) + h = (g - b) / delta; + else if (g == max) + h = 2 + (b - r) / delta; + else if (b == max) + h = 4 + (r - g) / delta; + + h = Math.min(h * 60, 360); + + if (h < 0) + h += 360; + + v = ((max / 255) * 1000) / 10; + + return [h, s, v]; +} + +function rgb2hwb(rgb) { + var r = rgb[0], + g = rgb[1], + b = rgb[2], + h = rgb2hsl(rgb)[0], + w = 1/255 * Math.min(r, Math.min(g, b)), + b = 1 - 1/255 * Math.max(r, Math.max(g, b)); + + return [h, w * 100, b * 100]; +} + +function rgb2cmyk(rgb) { + var r = rgb[0] / 255, + g = rgb[1] / 255, + b = rgb[2] / 255, + c, m, y, k; + + k = Math.min(1 - r, 1 - g, 1 - b); + c = (1 - r - k) / (1 - k) || 0; + m = (1 - g - k) / (1 - k) || 0; + y = (1 - b - k) / (1 - k) || 0; + return [c * 100, m * 100, y * 100, k * 100]; +} + +function rgb2keyword(rgb) { + return reverseKeywords[JSON.stringify(rgb)]; +} + +function rgb2xyz(rgb) { + var r = rgb[0] / 255, + g = rgb[1] / 255, + b = rgb[2] / 255; + + // assume sRGB + r = r > 0.04045 ? Math.pow(((r + 0.055) / 1.055), 2.4) : (r / 12.92); + g = g > 0.04045 ? Math.pow(((g + 0.055) / 1.055), 2.4) : (g / 12.92); + b = b > 0.04045 ? Math.pow(((b + 0.055) / 1.055), 2.4) : (b / 12.92); + + var x = (r * 0.4124) + (g * 0.3576) + (b * 0.1805); + var y = (r * 0.2126) + (g * 0.7152) + (b * 0.0722); + var z = (r * 0.0193) + (g * 0.1192) + (b * 0.9505); + + return [x * 100, y *100, z * 100]; +} + +function rgb2lab(rgb) { + var xyz = rgb2xyz(rgb), + x = xyz[0], + y = xyz[1], + z = xyz[2], + l, a, b; + + x /= 95.047; + y /= 100; + z /= 108.883; + + x = x > 0.008856 ? Math.pow(x, 1/3) : (7.787 * x) + (16 / 116); + y = y > 0.008856 ? Math.pow(y, 1/3) : (7.787 * y) + (16 / 116); + z = z > 0.008856 ? Math.pow(z, 1/3) : (7.787 * z) + (16 / 116); + + l = (116 * y) - 16; + a = 500 * (x - y); + b = 200 * (y - z); + + return [l, a, b]; +} + +function rgb2lch(args) { + return lab2lch(rgb2lab(args)); +} + +function hsl2rgb(hsl) { + var h = hsl[0] / 360, + s = hsl[1] / 100, + l = hsl[2] / 100, + t1, t2, t3, rgb, val; + + if (s == 0) { + val = l * 255; + return [val, val, val]; + } + + if (l < 0.5) + t2 = l * (1 + s); + else + t2 = l + s - l * s; + t1 = 2 * l - t2; + + rgb = [0, 0, 0]; + for (var i = 0; i < 3; i++) { + t3 = h + 1 / 3 * - (i - 1); + t3 < 0 && t3++; + t3 > 1 && t3--; + + if (6 * t3 < 1) + val = t1 + (t2 - t1) * 6 * t3; + else if (2 * t3 < 1) + val = t2; + else if (3 * t3 < 2) + val = t1 + (t2 - t1) * (2 / 3 - t3) * 6; + else + val = t1; + + rgb[i] = val * 255; + } + + return rgb; +} + +function hsl2hsv(hsl) { + var h = hsl[0], + s = hsl[1] / 100, + l = hsl[2] / 100, + sv, v; + + if(l === 0) { + // no need to do calc on black + // also avoids divide by 0 error + return [0, 0, 0]; + } + + l *= 2; + s *= (l <= 1) ? l : 2 - l; + v = (l + s) / 2; + sv = (2 * s) / (l + s); + return [h, sv * 100, v * 100]; +} + +function hsl2hwb(args) { + return rgb2hwb(hsl2rgb(args)); +} + +function hsl2cmyk(args) { + return rgb2cmyk(hsl2rgb(args)); +} + +function hsl2keyword(args) { + return rgb2keyword(hsl2rgb(args)); +} + + +function hsv2rgb(hsv) { + var h = hsv[0] / 60, + s = hsv[1] / 100, + v = hsv[2] / 100, + hi = Math.floor(h) % 6; + + var f = h - Math.floor(h), + p = 255 * v * (1 - s), + q = 255 * v * (1 - (s * f)), + t = 255 * v * (1 - (s * (1 - f))), + v = 255 * v; + + switch(hi) { + case 0: + return [v, t, p]; + case 1: + return [q, v, p]; + case 2: + return [p, v, t]; + case 3: + return [p, q, v]; + case 4: + return [t, p, v]; + case 5: + return [v, p, q]; + } +} + +function hsv2hsl(hsv) { + var h = hsv[0], + s = hsv[1] / 100, + v = hsv[2] / 100, + sl, l; + + l = (2 - s) * v; + sl = s * v; + sl /= (l <= 1) ? l : 2 - l; + sl = sl || 0; + l /= 2; + return [h, sl * 100, l * 100]; +} + +function hsv2hwb(args) { + return rgb2hwb(hsv2rgb(args)) +} + +function hsv2cmyk(args) { + return rgb2cmyk(hsv2rgb(args)); +} + +function hsv2keyword(args) { + return rgb2keyword(hsv2rgb(args)); +} + +// http://dev.w3.org/csswg/css-color/#hwb-to-rgb +function hwb2rgb(hwb) { + var h = hwb[0] / 360, + wh = hwb[1] / 100, + bl = hwb[2] / 100, + ratio = wh + bl, + i, v, f, n; + + // wh + bl cant be > 1 + if (ratio > 1) { + wh /= ratio; + bl /= ratio; + } + + i = Math.floor(6 * h); + v = 1 - bl; + f = 6 * h - i; + if ((i & 0x01) != 0) { + f = 1 - f; + } + n = wh + f * (v - wh); // linear interpolation + + switch (i) { + default: + case 6: + case 0: r = v; g = n; b = wh; break; + case 1: r = n; g = v; b = wh; break; + case 2: r = wh; g = v; b = n; break; + case 3: r = wh; g = n; b = v; break; + case 4: r = n; g = wh; b = v; break; + case 5: r = v; g = wh; b = n; break; + } + + return [r * 255, g * 255, b * 255]; +} + +function hwb2hsl(args) { + return rgb2hsl(hwb2rgb(args)); +} + +function hwb2hsv(args) { + return rgb2hsv(hwb2rgb(args)); +} + +function hwb2cmyk(args) { + return rgb2cmyk(hwb2rgb(args)); +} + +function hwb2keyword(args) { + return rgb2keyword(hwb2rgb(args)); +} + +function cmyk2rgb(cmyk) { + var c = cmyk[0] / 100, + m = cmyk[1] / 100, + y = cmyk[2] / 100, + k = cmyk[3] / 100, + r, g, b; + + r = 1 - Math.min(1, c * (1 - k) + k); + g = 1 - Math.min(1, m * (1 - k) + k); + b = 1 - Math.min(1, y * (1 - k) + k); + return [r * 255, g * 255, b * 255]; +} + +function cmyk2hsl(args) { + return rgb2hsl(cmyk2rgb(args)); +} + +function cmyk2hsv(args) { + return rgb2hsv(cmyk2rgb(args)); +} + +function cmyk2hwb(args) { + return rgb2hwb(cmyk2rgb(args)); +} + +function cmyk2keyword(args) { + return rgb2keyword(cmyk2rgb(args)); +} + + +function xyz2rgb(xyz) { + var x = xyz[0] / 100, + y = xyz[1] / 100, + z = xyz[2] / 100, + r, g, b; + + r = (x * 3.2406) + (y * -1.5372) + (z * -0.4986); + g = (x * -0.9689) + (y * 1.8758) + (z * 0.0415); + b = (x * 0.0557) + (y * -0.2040) + (z * 1.0570); + + // assume sRGB + r = r > 0.0031308 ? ((1.055 * Math.pow(r, 1.0 / 2.4)) - 0.055) + : r = (r * 12.92); + + g = g > 0.0031308 ? ((1.055 * Math.pow(g, 1.0 / 2.4)) - 0.055) + : g = (g * 12.92); + + b = b > 0.0031308 ? ((1.055 * Math.pow(b, 1.0 / 2.4)) - 0.055) + : b = (b * 12.92); + + r = Math.min(Math.max(0, r), 1); + g = Math.min(Math.max(0, g), 1); + b = Math.min(Math.max(0, b), 1); + + return [r * 255, g * 255, b * 255]; +} + +function xyz2lab(xyz) { + var x = xyz[0], + y = xyz[1], + z = xyz[2], + l, a, b; + + x /= 95.047; + y /= 100; + z /= 108.883; + + x = x > 0.008856 ? Math.pow(x, 1/3) : (7.787 * x) + (16 / 116); + y = y > 0.008856 ? Math.pow(y, 1/3) : (7.787 * y) + (16 / 116); + z = z > 0.008856 ? Math.pow(z, 1/3) : (7.787 * z) + (16 / 116); + + l = (116 * y) - 16; + a = 500 * (x - y); + b = 200 * (y - z); + + return [l, a, b]; +} + +function xyz2lch(args) { + return lab2lch(xyz2lab(args)); +} + +function lab2xyz(lab) { + var l = lab[0], + a = lab[1], + b = lab[2], + x, y, z, y2; + + if (l <= 8) { + y = (l * 100) / 903.3; + y2 = (7.787 * (y / 100)) + (16 / 116); + } else { + y = 100 * Math.pow((l + 16) / 116, 3); + y2 = Math.pow(y / 100, 1/3); + } + + x = x / 95.047 <= 0.008856 ? x = (95.047 * ((a / 500) + y2 - (16 / 116))) / 7.787 : 95.047 * Math.pow((a / 500) + y2, 3); + + z = z / 108.883 <= 0.008859 ? z = (108.883 * (y2 - (b / 200) - (16 / 116))) / 7.787 : 108.883 * Math.pow(y2 - (b / 200), 3); + + return [x, y, z]; +} + +function lab2lch(lab) { + var l = lab[0], + a = lab[1], + b = lab[2], + hr, h, c; + + hr = Math.atan2(b, a); + h = hr * 360 / 2 / Math.PI; + if (h < 0) { + h += 360; + } + c = Math.sqrt(a * a + b * b); + return [l, c, h]; +} + +function lab2rgb(args) { + return xyz2rgb(lab2xyz(args)); +} + +function lch2lab(lch) { + var l = lch[0], + c = lch[1], + h = lch[2], + a, b, hr; + + hr = h / 360 * 2 * Math.PI; + a = c * Math.cos(hr); + b = c * Math.sin(hr); + return [l, a, b]; +} + +function lch2xyz(args) { + return lab2xyz(lch2lab(args)); +} + +function lch2rgb(args) { + return lab2rgb(lch2lab(args)); +} + +function keyword2rgb(keyword) { + return cssKeywords[keyword]; +} + +function keyword2hsl(args) { + return rgb2hsl(keyword2rgb(args)); +} + +function keyword2hsv(args) { + return rgb2hsv(keyword2rgb(args)); +} + +function keyword2hwb(args) { + return rgb2hwb(keyword2rgb(args)); +} + +function keyword2cmyk(args) { + return rgb2cmyk(keyword2rgb(args)); +} + +function keyword2lab(args) { + return rgb2lab(keyword2rgb(args)); +} + +function keyword2xyz(args) { + return rgb2xyz(keyword2rgb(args)); +} + +var cssKeywords = { + aliceblue: [240,248,255], + antiquewhite: [250,235,215], + aqua: [0,255,255], + aquamarine: [127,255,212], + azure: [240,255,255], + beige: [245,245,220], + bisque: [255,228,196], + black: [0,0,0], + blanchedalmond: [255,235,205], + blue: [0,0,255], + blueviolet: [138,43,226], + brown: [165,42,42], + burlywood: [222,184,135], + cadetblue: [95,158,160], + chartreuse: [127,255,0], + chocolate: [210,105,30], + coral: [255,127,80], + cornflowerblue: [100,149,237], + cornsilk: [255,248,220], + crimson: [220,20,60], + cyan: [0,255,255], + darkblue: [0,0,139], + darkcyan: [0,139,139], + darkgoldenrod: [184,134,11], + darkgray: [169,169,169], + darkgreen: [0,100,0], + darkgrey: [169,169,169], + darkkhaki: [189,183,107], + darkmagenta: [139,0,139], + darkolivegreen: [85,107,47], + darkorange: [255,140,0], + darkorchid: [153,50,204], + darkred: [139,0,0], + darksalmon: [233,150,122], + darkseagreen: [143,188,143], + darkslateblue: [72,61,139], + darkslategray: [47,79,79], + darkslategrey: [47,79,79], + darkturquoise: [0,206,209], + darkviolet: [148,0,211], + deeppink: [255,20,147], + deepskyblue: [0,191,255], + dimgray: [105,105,105], + dimgrey: [105,105,105], + dodgerblue: [30,144,255], + firebrick: [178,34,34], + floralwhite: [255,250,240], + forestgreen: [34,139,34], + fuchsia: [255,0,255], + gainsboro: [220,220,220], + ghostwhite: [248,248,255], + gold: [255,215,0], + goldenrod: [218,165,32], + gray: [128,128,128], + green: [0,128,0], + greenyellow: [173,255,47], + grey: [128,128,128], + honeydew: [240,255,240], + hotpink: [255,105,180], + indianred: [205,92,92], + indigo: [75,0,130], + ivory: [255,255,240], + khaki: [240,230,140], + lavender: [230,230,250], + lavenderblush: [255,240,245], + lawngreen: [124,252,0], + lemonchiffon: [255,250,205], + lightblue: [173,216,230], + lightcoral: [240,128,128], + lightcyan: [224,255,255], + lightgoldenrodyellow: [250,250,210], + lightgray: [211,211,211], + lightgreen: [144,238,144], + lightgrey: [211,211,211], + lightpink: [255,182,193], + lightsalmon: [255,160,122], + lightseagreen: [32,178,170], + lightskyblue: [135,206,250], + lightslategray: [119,136,153], + lightslategrey: [119,136,153], + lightsteelblue: [176,196,222], + lightyellow: [255,255,224], + lime: [0,255,0], + limegreen: [50,205,50], + linen: [250,240,230], + magenta: [255,0,255], + maroon: [128,0,0], + mediumaquamarine: [102,205,170], + mediumblue: [0,0,205], + mediumorchid: [186,85,211], + mediumpurple: [147,112,219], + mediumseagreen: [60,179,113], + mediumslateblue: [123,104,238], + mediumspringgreen: [0,250,154], + mediumturquoise: [72,209,204], + mediumvioletred: [199,21,133], + midnightblue: [25,25,112], + mintcream: [245,255,250], + mistyrose: [255,228,225], + moccasin: [255,228,181], + navajowhite: [255,222,173], + navy: [0,0,128], + oldlace: [253,245,230], + olive: [128,128,0], + olivedrab: [107,142,35], + orange: [255,165,0], + orangered: [255,69,0], + orchid: [218,112,214], + palegoldenrod: [238,232,170], + palegreen: [152,251,152], + paleturquoise: [175,238,238], + palevioletred: [219,112,147], + papayawhip: [255,239,213], + peachpuff: [255,218,185], + peru: [205,133,63], + pink: [255,192,203], + plum: [221,160,221], + powderblue: [176,224,230], + purple: [128,0,128], + rebeccapurple: [102, 51, 153], + red: [255,0,0], + rosybrown: [188,143,143], + royalblue: [65,105,225], + saddlebrown: [139,69,19], + salmon: [250,128,114], + sandybrown: [244,164,96], + seagreen: [46,139,87], + seashell: [255,245,238], + sienna: [160,82,45], + silver: [192,192,192], + skyblue: [135,206,235], + slateblue: [106,90,205], + slategray: [112,128,144], + slategrey: [112,128,144], + snow: [255,250,250], + springgreen: [0,255,127], + steelblue: [70,130,180], + tan: [210,180,140], + teal: [0,128,128], + thistle: [216,191,216], + tomato: [255,99,71], + turquoise: [64,224,208], + violet: [238,130,238], + wheat: [245,222,179], + white: [255,255,255], + whitesmoke: [245,245,245], + yellow: [255,255,0], + yellowgreen: [154,205,50] +}; + +var reverseKeywords = {}; +for (var key in cssKeywords) { + reverseKeywords[JSON.stringify(cssKeywords[key])] = key; +} + +},{}],5:[function(require,module,exports){ +var conversions = require(4); + +var convert = function() { + return new Converter(); +} + +for (var func in conversions) { + // export Raw versions + convert[func + "Raw"] = (function(func) { + // accept array or plain args + return function(arg) { + if (typeof arg == "number") + arg = Array.prototype.slice.call(arguments); + return conversions[func](arg); + } + })(func); + + var pair = /(\w+)2(\w+)/.exec(func), + from = pair[1], + to = pair[2]; + + // export rgb2hsl and ["rgb"]["hsl"] + convert[from] = convert[from] || {}; + + convert[from][to] = convert[func] = (function(func) { + return function(arg) { + if (typeof arg == "number") + arg = Array.prototype.slice.call(arguments); + + var val = conversions[func](arg); + if (typeof val == "string" || val === undefined) + return val; // keyword + + for (var i = 0; i < val.length; i++) + val[i] = Math.round(val[i]); + return val; + } + })(func); +} + + +/* Converter does lazy conversion and caching */ +var Converter = function() { + this.convs = {}; +}; + +/* Either get the values for a space or + set the values for a space, depending on args */ +Converter.prototype.routeSpace = function(space, args) { + var values = args[0]; + if (values === undefined) { + // color.rgb() + return this.getValues(space); + } + // color.rgb(10, 10, 10) + if (typeof values == "number") { + values = Array.prototype.slice.call(args); + } + + return this.setValues(space, values); +}; + +/* Set the values for a space, invalidating cache */ +Converter.prototype.setValues = function(space, values) { + this.space = space; + this.convs = {}; + this.convs[space] = values; + return this; +}; + +/* Get the values for a space. If there's already + a conversion for the space, fetch it, otherwise + compute it */ +Converter.prototype.getValues = function(space) { + var vals = this.convs[space]; + if (!vals) { + var fspace = this.space, + from = this.convs[fspace]; + vals = convert[fspace][space](from); + + this.convs[space] = vals; + } + return vals; +}; + +["rgb", "hsl", "hsv", "cmyk", "keyword"].forEach(function(space) { + Converter.prototype[space] = function(vals) { + return this.routeSpace(space, arguments); + } +}); + +module.exports = convert; +},{"4":4}],6:[function(require,module,exports){ +'use strict' + +module.exports = { + "aliceblue": [240, 248, 255], + "antiquewhite": [250, 235, 215], + "aqua": [0, 255, 255], + "aquamarine": [127, 255, 212], + "azure": [240, 255, 255], + "beige": [245, 245, 220], + "bisque": [255, 228, 196], + "black": [0, 0, 0], + "blanchedalmond": [255, 235, 205], + "blue": [0, 0, 255], + "blueviolet": [138, 43, 226], + "brown": [165, 42, 42], + "burlywood": [222, 184, 135], + "cadetblue": [95, 158, 160], + "chartreuse": [127, 255, 0], + "chocolate": [210, 105, 30], + "coral": [255, 127, 80], + "cornflowerblue": [100, 149, 237], + "cornsilk": [255, 248, 220], + "crimson": [220, 20, 60], + "cyan": [0, 255, 255], + "darkblue": [0, 0, 139], + "darkcyan": [0, 139, 139], + "darkgoldenrod": [184, 134, 11], + "darkgray": [169, 169, 169], + "darkgreen": [0, 100, 0], + "darkgrey": [169, 169, 169], + "darkkhaki": [189, 183, 107], + "darkmagenta": [139, 0, 139], + "darkolivegreen": [85, 107, 47], + "darkorange": [255, 140, 0], + "darkorchid": [153, 50, 204], + "darkred": [139, 0, 0], + "darksalmon": [233, 150, 122], + "darkseagreen": [143, 188, 143], + "darkslateblue": [72, 61, 139], + "darkslategray": [47, 79, 79], + "darkslategrey": [47, 79, 79], + "darkturquoise": [0, 206, 209], + "darkviolet": [148, 0, 211], + "deeppink": [255, 20, 147], + "deepskyblue": [0, 191, 255], + "dimgray": [105, 105, 105], + "dimgrey": [105, 105, 105], + "dodgerblue": [30, 144, 255], + "firebrick": [178, 34, 34], + "floralwhite": [255, 250, 240], + "forestgreen": [34, 139, 34], + "fuchsia": [255, 0, 255], + "gainsboro": [220, 220, 220], + "ghostwhite": [248, 248, 255], + "gold": [255, 215, 0], + "goldenrod": [218, 165, 32], + "gray": [128, 128, 128], + "green": [0, 128, 0], + "greenyellow": [173, 255, 47], + "grey": [128, 128, 128], + "honeydew": [240, 255, 240], + "hotpink": [255, 105, 180], + "indianred": [205, 92, 92], + "indigo": [75, 0, 130], + "ivory": [255, 255, 240], + "khaki": [240, 230, 140], + "lavender": [230, 230, 250], + "lavenderblush": [255, 240, 245], + "lawngreen": [124, 252, 0], + "lemonchiffon": [255, 250, 205], + "lightblue": [173, 216, 230], + "lightcoral": [240, 128, 128], + "lightcyan": [224, 255, 255], + "lightgoldenrodyellow": [250, 250, 210], + "lightgray": [211, 211, 211], + "lightgreen": [144, 238, 144], + "lightgrey": [211, 211, 211], + "lightpink": [255, 182, 193], + "lightsalmon": [255, 160, 122], + "lightseagreen": [32, 178, 170], + "lightskyblue": [135, 206, 250], + "lightslategray": [119, 136, 153], + "lightslategrey": [119, 136, 153], + "lightsteelblue": [176, 196, 222], + "lightyellow": [255, 255, 224], + "lime": [0, 255, 0], + "limegreen": [50, 205, 50], + "linen": [250, 240, 230], + "magenta": [255, 0, 255], + "maroon": [128, 0, 0], + "mediumaquamarine": [102, 205, 170], + "mediumblue": [0, 0, 205], + "mediumorchid": [186, 85, 211], + "mediumpurple": [147, 112, 219], + "mediumseagreen": [60, 179, 113], + "mediumslateblue": [123, 104, 238], + "mediumspringgreen": [0, 250, 154], + "mediumturquoise": [72, 209, 204], + "mediumvioletred": [199, 21, 133], + "midnightblue": [25, 25, 112], + "mintcream": [245, 255, 250], + "mistyrose": [255, 228, 225], + "moccasin": [255, 228, 181], + "navajowhite": [255, 222, 173], + "navy": [0, 0, 128], + "oldlace": [253, 245, 230], + "olive": [128, 128, 0], + "olivedrab": [107, 142, 35], + "orange": [255, 165, 0], + "orangered": [255, 69, 0], + "orchid": [218, 112, 214], + "palegoldenrod": [238, 232, 170], + "palegreen": [152, 251, 152], + "paleturquoise": [175, 238, 238], + "palevioletred": [219, 112, 147], + "papayawhip": [255, 239, 213], + "peachpuff": [255, 218, 185], + "peru": [205, 133, 63], + "pink": [255, 192, 203], + "plum": [221, 160, 221], + "powderblue": [176, 224, 230], + "purple": [128, 0, 128], + "rebeccapurple": [102, 51, 153], + "red": [255, 0, 0], + "rosybrown": [188, 143, 143], + "royalblue": [65, 105, 225], + "saddlebrown": [139, 69, 19], + "salmon": [250, 128, 114], + "sandybrown": [244, 164, 96], + "seagreen": [46, 139, 87], + "seashell": [255, 245, 238], + "sienna": [160, 82, 45], + "silver": [192, 192, 192], + "skyblue": [135, 206, 235], + "slateblue": [106, 90, 205], + "slategray": [112, 128, 144], + "slategrey": [112, 128, 144], + "snow": [255, 250, 250], + "springgreen": [0, 255, 127], + "steelblue": [70, 130, 180], + "tan": [210, 180, 140], + "teal": [0, 128, 128], + "thistle": [216, 191, 216], + "tomato": [255, 99, 71], + "turquoise": [64, 224, 208], + "violet": [238, 130, 238], + "wheat": [245, 222, 179], + "white": [255, 255, 255], + "whitesmoke": [245, 245, 245], + "yellow": [255, 255, 0], + "yellowgreen": [154, 205, 50] +}; + +},{}],7:[function(require,module,exports){ +/** + * @namespace Chart + */ +var Chart = require(30)(); + +Chart.helpers = require(46); + +// @todo dispatch these helpers into appropriated helpers/helpers.* file and write unit tests! +require(28)(Chart); + +Chart.Animation = require(22); +Chart.animationService = require(23); +Chart.defaults = require(26); +Chart.Element = require(27); +Chart.elements = require(41); +Chart.Interaction = require(29); +Chart.layouts = require(31); +Chart.platform = require(49); +Chart.plugins = require(32); +Chart.Scale = require(33); +Chart.scaleService = require(34); +Chart.Ticks = require(35); +Chart.Tooltip = require(36); + +require(24)(Chart); +require(25)(Chart); + +require(56)(Chart); +require(54)(Chart); +require(55)(Chart); +require(57)(Chart); +require(58)(Chart); +require(59)(Chart); + +// Controllers must be loaded after elements +// See Chart.core.datasetController.dataElementType +require(15)(Chart); +require(16)(Chart); +require(17)(Chart); +require(18)(Chart); +require(19)(Chart); +require(20)(Chart); +require(21)(Chart); + +require(8)(Chart); +require(9)(Chart); +require(10)(Chart); +require(11)(Chart); +require(12)(Chart); +require(13)(Chart); +require(14)(Chart); + +// Loading built-in plugins +var plugins = require(50); +for (var k in plugins) { + if (plugins.hasOwnProperty(k)) { + Chart.plugins.register(plugins[k]); + } +} + +Chart.platform.initialize(); + +module.exports = Chart; +if (typeof window !== 'undefined') { + window.Chart = Chart; +} + +// DEPRECATIONS + +/** + * Provided for backward compatibility, not available anymore + * @namespace Chart.Legend + * @deprecated since version 2.1.5 + * @todo remove at version 3 + * @private + */ +Chart.Legend = plugins.legend._element; + +/** + * Provided for backward compatibility, not available anymore + * @namespace Chart.Title + * @deprecated since version 2.1.5 + * @todo remove at version 3 + * @private + */ +Chart.Title = plugins.title._element; + +/** + * Provided for backward compatibility, use Chart.plugins instead + * @namespace Chart.pluginService + * @deprecated since version 2.1.5 + * @todo remove at version 3 + * @private + */ +Chart.pluginService = Chart.plugins; + +/** + * Provided for backward compatibility, inheriting from Chart.PlugingBase has no + * effect, instead simply create/register plugins via plain JavaScript objects. + * @interface Chart.PluginBase + * @deprecated since version 2.5.0 + * @todo remove at version 3 + * @private + */ +Chart.PluginBase = Chart.Element.extend({}); + +/** + * Provided for backward compatibility, use Chart.helpers.canvas instead. + * @namespace Chart.canvasHelpers + * @deprecated since version 2.6.0 + * @todo remove at version 3 + * @private + */ +Chart.canvasHelpers = Chart.helpers.canvas; + +/** + * Provided for backward compatibility, use Chart.layouts instead. + * @namespace Chart.layoutService + * @deprecated since version 2.8.0 + * @todo remove at version 3 + * @private + */ +Chart.layoutService = Chart.layouts; + +},{"10":10,"11":11,"12":12,"13":13,"14":14,"15":15,"16":16,"17":17,"18":18,"19":19,"20":20,"21":21,"22":22,"23":23,"24":24,"25":25,"26":26,"27":27,"28":28,"29":29,"30":30,"31":31,"32":32,"33":33,"34":34,"35":35,"36":36,"41":41,"46":46,"49":49,"50":50,"54":54,"55":55,"56":56,"57":57,"58":58,"59":59,"8":8,"9":9}],8:[function(require,module,exports){ +'use strict'; + +module.exports = function(Chart) { + + Chart.Bar = function(context, config) { + config.type = 'bar'; + + return new Chart(context, config); + }; + +}; + +},{}],9:[function(require,module,exports){ +'use strict'; + +module.exports = function(Chart) { + + Chart.Bubble = function(context, config) { + config.type = 'bubble'; + return new Chart(context, config); + }; + +}; + +},{}],10:[function(require,module,exports){ +'use strict'; + +module.exports = function(Chart) { + + Chart.Doughnut = function(context, config) { + config.type = 'doughnut'; + + return new Chart(context, config); + }; + +}; + +},{}],11:[function(require,module,exports){ +'use strict'; + +module.exports = function(Chart) { + + Chart.Line = function(context, config) { + config.type = 'line'; + + return new Chart(context, config); + }; + +}; + +},{}],12:[function(require,module,exports){ +'use strict'; + +module.exports = function(Chart) { + + Chart.PolarArea = function(context, config) { + config.type = 'polarArea'; + + return new Chart(context, config); + }; + +}; + +},{}],13:[function(require,module,exports){ +'use strict'; + +module.exports = function(Chart) { + + Chart.Radar = function(context, config) { + config.type = 'radar'; + + return new Chart(context, config); + }; + +}; + +},{}],14:[function(require,module,exports){ +'use strict'; + +module.exports = function(Chart) { + Chart.Scatter = function(context, config) { + config.type = 'scatter'; + return new Chart(context, config); + }; +}; + +},{}],15:[function(require,module,exports){ +'use strict'; + +var defaults = require(26); +var elements = require(41); +var helpers = require(46); + +defaults._set('bar', { + hover: { + mode: 'label' + }, + + scales: { + xAxes: [{ + type: 'category', + + // Specific to Bar Controller + categoryPercentage: 0.8, + barPercentage: 0.9, + + // offset settings + offset: true, + + // grid line settings + gridLines: { + offsetGridLines: true + } + }], + + yAxes: [{ + type: 'linear' + }] + } +}); + +defaults._set('horizontalBar', { + hover: { + mode: 'index', + axis: 'y' + }, + + scales: { + xAxes: [{ + type: 'linear', + position: 'bottom' + }], + + yAxes: [{ + position: 'left', + type: 'category', + + // Specific to Horizontal Bar Controller + categoryPercentage: 0.8, + barPercentage: 0.9, + + // offset settings + offset: true, + + // grid line settings + gridLines: { + offsetGridLines: true + } + }] + }, + + elements: { + rectangle: { + borderSkipped: 'left' + } + }, + + tooltips: { + callbacks: { + title: function(item, data) { + // Pick first xLabel for now + var title = ''; + + if (item.length > 0) { + if (item[0].yLabel) { + title = item[0].yLabel; + } else if (data.labels.length > 0 && item[0].index < data.labels.length) { + title = data.labels[item[0].index]; + } + } + + return title; + }, + + label: function(item, data) { + var datasetLabel = data.datasets[item.datasetIndex].label || ''; + return datasetLabel + ': ' + item.xLabel; + } + }, + mode: 'index', + axis: 'y' + } +}); + +/** + * Computes the "optimal" sample size to maintain bars equally sized while preventing overlap. + * @private + */ +function computeMinSampleSize(scale, pixels) { + var min = scale.isHorizontal() ? scale.width : scale.height; + var ticks = scale.getTicks(); + var prev, curr, i, ilen; + + for (i = 1, ilen = pixels.length; i < ilen; ++i) { + min = Math.min(min, pixels[i] - pixels[i - 1]); + } + + for (i = 0, ilen = ticks.length; i < ilen; ++i) { + curr = scale.getPixelForTick(i); + min = i > 0 ? Math.min(min, curr - prev) : min; + prev = curr; + } + + return min; +} + +/** + * Computes an "ideal" category based on the absolute bar thickness or, if undefined or null, + * uses the smallest interval (see computeMinSampleSize) that prevents bar overlapping. This + * mode currently always generates bars equally sized (until we introduce scriptable options?). + * @private + */ +function computeFitCategoryTraits(index, ruler, options) { + var thickness = options.barThickness; + var count = ruler.stackCount; + var curr = ruler.pixels[index]; + var size, ratio; + + if (helpers.isNullOrUndef(thickness)) { + size = ruler.min * options.categoryPercentage; + ratio = options.barPercentage; + } else { + // When bar thickness is enforced, category and bar percentages are ignored. + // Note(SB): we could add support for relative bar thickness (e.g. barThickness: '50%') + // and deprecate barPercentage since this value is ignored when thickness is absolute. + size = thickness * count; + ratio = 1; + } + + return { + chunk: size / count, + ratio: ratio, + start: curr - (size / 2) + }; +} + +/** + * Computes an "optimal" category that globally arranges bars side by side (no gap when + * percentage options are 1), based on the previous and following categories. This mode + * generates bars with different widths when data are not evenly spaced. + * @private + */ +function computeFlexCategoryTraits(index, ruler, options) { + var pixels = ruler.pixels; + var curr = pixels[index]; + var prev = index > 0 ? pixels[index - 1] : null; + var next = index < pixels.length - 1 ? pixels[index + 1] : null; + var percent = options.categoryPercentage; + var start, size; + + if (prev === null) { + // first data: its size is double based on the next point or, + // if it's also the last data, we use the scale end extremity. + prev = curr - (next === null ? ruler.end - curr : next - curr); + } + + if (next === null) { + // last data: its size is also double based on the previous point. + next = curr + curr - prev; + } + + start = curr - ((curr - prev) / 2) * percent; + size = ((next - prev) / 2) * percent; + + return { + chunk: size / ruler.stackCount, + ratio: options.barPercentage, + start: start + }; +} + +module.exports = function(Chart) { + + Chart.controllers.bar = Chart.DatasetController.extend({ + + dataElementType: elements.Rectangle, + + initialize: function() { + var me = this; + var meta; + + Chart.DatasetController.prototype.initialize.apply(me, arguments); + + meta = me.getMeta(); + meta.stack = me.getDataset().stack; + meta.bar = true; + }, + + update: function(reset) { + var me = this; + var rects = me.getMeta().data; + var i, ilen; + + me._ruler = me.getRuler(); + + for (i = 0, ilen = rects.length; i < ilen; ++i) { + me.updateElement(rects[i], i, reset); + } + }, + + updateElement: function(rectangle, index, reset) { + var me = this; + var chart = me.chart; + var meta = me.getMeta(); + var dataset = me.getDataset(); + var custom = rectangle.custom || {}; + var rectangleOptions = chart.options.elements.rectangle; + + rectangle._xScale = me.getScaleForId(meta.xAxisID); + rectangle._yScale = me.getScaleForId(meta.yAxisID); + rectangle._datasetIndex = me.index; + rectangle._index = index; + + rectangle._model = { + datasetLabel: dataset.label, + label: chart.data.labels[index], + borderSkipped: custom.borderSkipped ? custom.borderSkipped : rectangleOptions.borderSkipped, + backgroundColor: custom.backgroundColor ? custom.backgroundColor : helpers.valueAtIndexOrDefault(dataset.backgroundColor, index, rectangleOptions.backgroundColor), + borderColor: custom.borderColor ? custom.borderColor : helpers.valueAtIndexOrDefault(dataset.borderColor, index, rectangleOptions.borderColor), + borderWidth: custom.borderWidth ? custom.borderWidth : helpers.valueAtIndexOrDefault(dataset.borderWidth, index, rectangleOptions.borderWidth) + }; + + me.updateElementGeometry(rectangle, index, reset); + + rectangle.pivot(); + }, + + /** + * @private + */ + updateElementGeometry: function(rectangle, index, reset) { + var me = this; + var model = rectangle._model; + var vscale = me.getValueScale(); + var base = vscale.getBasePixel(); + var horizontal = vscale.isHorizontal(); + var ruler = me._ruler || me.getRuler(); + var vpixels = me.calculateBarValuePixels(me.index, index); + var ipixels = me.calculateBarIndexPixels(me.index, index, ruler); + + model.horizontal = horizontal; + model.base = reset ? base : vpixels.base; + model.x = horizontal ? reset ? base : vpixels.head : ipixels.center; + model.y = horizontal ? ipixels.center : reset ? base : vpixels.head; + model.height = horizontal ? ipixels.size : undefined; + model.width = horizontal ? undefined : ipixels.size; + }, + + /** + * @private + */ + getValueScaleId: function() { + return this.getMeta().yAxisID; + }, + + /** + * @private + */ + getIndexScaleId: function() { + return this.getMeta().xAxisID; + }, + + /** + * @private + */ + getValueScale: function() { + return this.getScaleForId(this.getValueScaleId()); + }, + + /** + * @private + */ + getIndexScale: function() { + return this.getScaleForId(this.getIndexScaleId()); + }, + + /** + * Returns the stacks based on groups and bar visibility. + * @param {Number} [last] - The dataset index + * @returns {Array} The stack list + * @private + */ + _getStacks: function(last) { + var me = this; + var chart = me.chart; + var scale = me.getIndexScale(); + var stacked = scale.options.stacked; + var ilen = last === undefined ? chart.data.datasets.length : last + 1; + var stacks = []; + var i, meta; + + for (i = 0; i < ilen; ++i) { + meta = chart.getDatasetMeta(i); + if (meta.bar && chart.isDatasetVisible(i) && + (stacked === false || + (stacked === true && stacks.indexOf(meta.stack) === -1) || + (stacked === undefined && (meta.stack === undefined || stacks.indexOf(meta.stack) === -1)))) { + stacks.push(meta.stack); + } + } + + return stacks; + }, + + /** + * Returns the effective number of stacks based on groups and bar visibility. + * @private + */ + getStackCount: function() { + return this._getStacks().length; + }, + + /** + * Returns the stack index for the given dataset based on groups and bar visibility. + * @param {Number} [datasetIndex] - The dataset index + * @param {String} [name] - The stack name to find + * @returns {Number} The stack index + * @private + */ + getStackIndex: function(datasetIndex, name) { + var stacks = this._getStacks(datasetIndex); + var index = (name !== undefined) + ? stacks.indexOf(name) + : -1; // indexOf returns -1 if element is not present + + return (index === -1) + ? stacks.length - 1 + : index; + }, + + /** + * @private + */ + getRuler: function() { + var me = this; + var scale = me.getIndexScale(); + var stackCount = me.getStackCount(); + var datasetIndex = me.index; + var isHorizontal = scale.isHorizontal(); + var start = isHorizontal ? scale.left : scale.top; + var end = start + (isHorizontal ? scale.width : scale.height); + var pixels = []; + var i, ilen, min; + + for (i = 0, ilen = me.getMeta().data.length; i < ilen; ++i) { + pixels.push(scale.getPixelForValue(null, i, datasetIndex)); + } + + min = helpers.isNullOrUndef(scale.options.barThickness) + ? computeMinSampleSize(scale, pixels) + : -1; + + return { + min: min, + pixels: pixels, + start: start, + end: end, + stackCount: stackCount, + scale: scale + }; + }, + + /** + * Note: pixel values are not clamped to the scale area. + * @private + */ + calculateBarValuePixels: function(datasetIndex, index) { + var me = this; + var chart = me.chart; + var meta = me.getMeta(); + var scale = me.getValueScale(); + var datasets = chart.data.datasets; + var value = scale.getRightValue(datasets[datasetIndex].data[index]); + var stacked = scale.options.stacked; + var stack = meta.stack; + var start = 0; + var i, imeta, ivalue, base, head, size; + + if (stacked || (stacked === undefined && stack !== undefined)) { + for (i = 0; i < datasetIndex; ++i) { + imeta = chart.getDatasetMeta(i); + + if (imeta.bar && + imeta.stack === stack && + imeta.controller.getValueScaleId() === scale.id && + chart.isDatasetVisible(i)) { + + ivalue = scale.getRightValue(datasets[i].data[index]); + if ((value < 0 && ivalue < 0) || (value >= 0 && ivalue > 0)) { + start += ivalue; + } + } + } + } + + base = scale.getPixelForValue(start); + head = scale.getPixelForValue(start + value); + size = (head - base) / 2; + + return { + size: size, + base: base, + head: head, + center: head + size / 2 + }; + }, + + /** + * @private + */ + calculateBarIndexPixels: function(datasetIndex, index, ruler) { + var me = this; + var options = ruler.scale.options; + var range = options.barThickness === 'flex' + ? computeFlexCategoryTraits(index, ruler, options) + : computeFitCategoryTraits(index, ruler, options); + + var stackIndex = me.getStackIndex(datasetIndex, me.getMeta().stack); + var center = range.start + (range.chunk * stackIndex) + (range.chunk / 2); + var size = Math.min( + helpers.valueOrDefault(options.maxBarThickness, Infinity), + range.chunk * range.ratio); + + return { + base: center - size / 2, + head: center + size / 2, + center: center, + size: size + }; + }, + + draw: function() { + var me = this; + var chart = me.chart; + var scale = me.getValueScale(); + var rects = me.getMeta().data; + var dataset = me.getDataset(); + var ilen = rects.length; + var i = 0; + + helpers.canvas.clipArea(chart.ctx, chart.chartArea); + + for (; i < ilen; ++i) { + if (!isNaN(scale.getRightValue(dataset.data[i]))) { + rects[i].draw(); + } + } + + helpers.canvas.unclipArea(chart.ctx); + }, + }); + + Chart.controllers.horizontalBar = Chart.controllers.bar.extend({ + /** + * @private + */ + getValueScaleId: function() { + return this.getMeta().xAxisID; + }, + + /** + * @private + */ + getIndexScaleId: function() { + return this.getMeta().yAxisID; + } + }); +}; + +},{"26":26,"41":41,"46":46}],16:[function(require,module,exports){ +'use strict'; + +var defaults = require(26); +var elements = require(41); +var helpers = require(46); + +defaults._set('bubble', { + hover: { + mode: 'single' + }, + + scales: { + xAxes: [{ + type: 'linear', // bubble should probably use a linear scale by default + position: 'bottom', + id: 'x-axis-0' // need an ID so datasets can reference the scale + }], + yAxes: [{ + type: 'linear', + position: 'left', + id: 'y-axis-0' + }] + }, + + tooltips: { + callbacks: { + title: function() { + // Title doesn't make sense for scatter since we format the data as a point + return ''; + }, + label: function(item, data) { + var datasetLabel = data.datasets[item.datasetIndex].label || ''; + var dataPoint = data.datasets[item.datasetIndex].data[item.index]; + return datasetLabel + ': (' + item.xLabel + ', ' + item.yLabel + ', ' + dataPoint.r + ')'; + } + } + } +}); + + +module.exports = function(Chart) { + + Chart.controllers.bubble = Chart.DatasetController.extend({ + /** + * @protected + */ + dataElementType: elements.Point, + + /** + * @protected + */ + update: function(reset) { + var me = this; + var meta = me.getMeta(); + var points = meta.data; + + // Update Points + helpers.each(points, function(point, index) { + me.updateElement(point, index, reset); + }); + }, + + /** + * @protected + */ + updateElement: function(point, index, reset) { + var me = this; + var meta = me.getMeta(); + var custom = point.custom || {}; + var xScale = me.getScaleForId(meta.xAxisID); + var yScale = me.getScaleForId(meta.yAxisID); + var options = me._resolveElementOptions(point, index); + var data = me.getDataset().data[index]; + var dsIndex = me.index; + + var x = reset ? xScale.getPixelForDecimal(0.5) : xScale.getPixelForValue(typeof data === 'object' ? data : NaN, index, dsIndex); + var y = reset ? yScale.getBasePixel() : yScale.getPixelForValue(data, index, dsIndex); + + point._xScale = xScale; + point._yScale = yScale; + point._options = options; + point._datasetIndex = dsIndex; + point._index = index; + point._model = { + backgroundColor: options.backgroundColor, + borderColor: options.borderColor, + borderWidth: options.borderWidth, + hitRadius: options.hitRadius, + pointStyle: options.pointStyle, + rotation: options.rotation, + radius: reset ? 0 : options.radius, + skip: custom.skip || isNaN(x) || isNaN(y), + x: x, + y: y, + }; + + point.pivot(); + }, + + /** + * @protected + */ + setHoverStyle: function(point) { + var model = point._model; + var options = point._options; + point.$previousStyle = { + backgroundColor: model.backgroundColor, + borderColor: model.borderColor, + borderWidth: model.borderWidth, + radius: model.radius + }; + model.backgroundColor = helpers.valueOrDefault(options.hoverBackgroundColor, helpers.getHoverColor(options.backgroundColor)); + model.borderColor = helpers.valueOrDefault(options.hoverBorderColor, helpers.getHoverColor(options.borderColor)); + model.borderWidth = helpers.valueOrDefault(options.hoverBorderWidth, options.borderWidth); + model.radius = options.radius + options.hoverRadius; + }, + + /** + * @private + */ + _resolveElementOptions: function(point, index) { + var me = this; + var chart = me.chart; + var datasets = chart.data.datasets; + var dataset = datasets[me.index]; + var custom = point.custom || {}; + var options = chart.options.elements.point; + var resolve = helpers.options.resolve; + var data = dataset.data[index]; + var values = {}; + var i, ilen, key; + + // Scriptable options + var context = { + chart: chart, + dataIndex: index, + dataset: dataset, + datasetIndex: me.index + }; + + var keys = [ + 'backgroundColor', + 'borderColor', + 'borderWidth', + 'hoverBackgroundColor', + 'hoverBorderColor', + 'hoverBorderWidth', + 'hoverRadius', + 'hitRadius', + 'pointStyle', + 'rotation' + ]; + + for (i = 0, ilen = keys.length; i < ilen; ++i) { + key = keys[i]; + values[key] = resolve([ + custom[key], + dataset[key], + options[key] + ], context, index); + } + + // Custom radius resolution + values.radius = resolve([ + custom.radius, + data ? data.r : undefined, + dataset.radius, + options.radius + ], context, index); + return values; + } + }); +}; + +},{"26":26,"41":41,"46":46}],17:[function(require,module,exports){ +'use strict'; + +var defaults = require(26); +var elements = require(41); +var helpers = require(46); + +defaults._set('doughnut', { + animation: { + // Boolean - Whether we animate the rotation of the Doughnut + animateRotate: true, + // Boolean - Whether we animate scaling the Doughnut from the centre + animateScale: false + }, + hover: { + mode: 'single' + }, + legendCallback: function(chart) { + var text = []; + text.push('

'); + return text.join(''); + }, + legend: { + labels: { + generateLabels: function(chart) { + var data = chart.data; + if (data.labels.length && data.datasets.length) { + return data.labels.map(function(label, i) { + var meta = chart.getDatasetMeta(0); + var ds = data.datasets[0]; + var arc = meta.data[i]; + var custom = arc && arc.custom || {}; + var valueAtIndexOrDefault = helpers.valueAtIndexOrDefault; + var arcOpts = chart.options.elements.arc; + var fill = custom.backgroundColor ? custom.backgroundColor : valueAtIndexOrDefault(ds.backgroundColor, i, arcOpts.backgroundColor); + var stroke = custom.borderColor ? custom.borderColor : valueAtIndexOrDefault(ds.borderColor, i, arcOpts.borderColor); + var bw = custom.borderWidth ? custom.borderWidth : valueAtIndexOrDefault(ds.borderWidth, i, arcOpts.borderWidth); + + return { + text: label, + fillStyle: fill, + strokeStyle: stroke, + lineWidth: bw, + hidden: isNaN(ds.data[i]) || meta.data[i].hidden, + + // Extra data used for toggling the correct item + index: i + }; + }); + } + return []; + } + }, + + onClick: function(e, legendItem) { + var index = legendItem.index; + var chart = this.chart; + var i, ilen, meta; + + for (i = 0, ilen = (chart.data.datasets || []).length; i < ilen; ++i) { + meta = chart.getDatasetMeta(i); + // toggle visibility of index if exists + if (meta.data[index]) { + meta.data[index].hidden = !meta.data[index].hidden; + } + } + + chart.update(); + } + }, + + // The percentage of the chart that we cut out of the middle. + cutoutPercentage: 50, + + // The rotation of the chart, where the first data arc begins. + rotation: Math.PI * -0.5, + + // The total circumference of the chart. + circumference: Math.PI * 2.0, + + // Need to override these to give a nice default + tooltips: { + callbacks: { + title: function() { + return ''; + }, + label: function(tooltipItem, data) { + var dataLabel = data.labels[tooltipItem.index]; + var value = ': ' + data.datasets[tooltipItem.datasetIndex].data[tooltipItem.index]; + + if (helpers.isArray(dataLabel)) { + // show value on first line of multiline label + // need to clone because we are changing the value + dataLabel = dataLabel.slice(); + dataLabel[0] += value; + } else { + dataLabel += value; + } + + return dataLabel; + } + } + } +}); + +defaults._set('pie', helpers.clone(defaults.doughnut)); +defaults._set('pie', { + cutoutPercentage: 0 +}); + +module.exports = function(Chart) { + + Chart.controllers.doughnut = Chart.controllers.pie = Chart.DatasetController.extend({ + + dataElementType: elements.Arc, + + linkScales: helpers.noop, + + // Get index of the dataset in relation to the visible datasets. This allows determining the inner and outer radius correctly + getRingIndex: function(datasetIndex) { + var ringIndex = 0; + + for (var j = 0; j < datasetIndex; ++j) { + if (this.chart.isDatasetVisible(j)) { + ++ringIndex; + } + } + + return ringIndex; + }, + + update: function(reset) { + var me = this; + var chart = me.chart; + var chartArea = chart.chartArea; + var opts = chart.options; + var arcOpts = opts.elements.arc; + var availableWidth = chartArea.right - chartArea.left - arcOpts.borderWidth; + var availableHeight = chartArea.bottom - chartArea.top - arcOpts.borderWidth; + var minSize = Math.min(availableWidth, availableHeight); + var offset = {x: 0, y: 0}; + var meta = me.getMeta(); + var cutoutPercentage = opts.cutoutPercentage; + var circumference = opts.circumference; + + // If the chart's circumference isn't a full circle, calculate minSize as a ratio of the width/height of the arc + if (circumference < Math.PI * 2.0) { + var startAngle = opts.rotation % (Math.PI * 2.0); + startAngle += Math.PI * 2.0 * (startAngle >= Math.PI ? -1 : startAngle < -Math.PI ? 1 : 0); + var endAngle = startAngle + circumference; + var start = {x: Math.cos(startAngle), y: Math.sin(startAngle)}; + var end = {x: Math.cos(endAngle), y: Math.sin(endAngle)}; + var contains0 = (startAngle <= 0 && endAngle >= 0) || (startAngle <= Math.PI * 2.0 && Math.PI * 2.0 <= endAngle); + var contains90 = (startAngle <= Math.PI * 0.5 && Math.PI * 0.5 <= endAngle) || (startAngle <= Math.PI * 2.5 && Math.PI * 2.5 <= endAngle); + var contains180 = (startAngle <= -Math.PI && -Math.PI <= endAngle) || (startAngle <= Math.PI && Math.PI <= endAngle); + var contains270 = (startAngle <= -Math.PI * 0.5 && -Math.PI * 0.5 <= endAngle) || (startAngle <= Math.PI * 1.5 && Math.PI * 1.5 <= endAngle); + var cutout = cutoutPercentage / 100.0; + var min = {x: contains180 ? -1 : Math.min(start.x * (start.x < 0 ? 1 : cutout), end.x * (end.x < 0 ? 1 : cutout)), y: contains270 ? -1 : Math.min(start.y * (start.y < 0 ? 1 : cutout), end.y * (end.y < 0 ? 1 : cutout))}; + var max = {x: contains0 ? 1 : Math.max(start.x * (start.x > 0 ? 1 : cutout), end.x * (end.x > 0 ? 1 : cutout)), y: contains90 ? 1 : Math.max(start.y * (start.y > 0 ? 1 : cutout), end.y * (end.y > 0 ? 1 : cutout))}; + var size = {width: (max.x - min.x) * 0.5, height: (max.y - min.y) * 0.5}; + minSize = Math.min(availableWidth / size.width, availableHeight / size.height); + offset = {x: (max.x + min.x) * -0.5, y: (max.y + min.y) * -0.5}; + } + + chart.borderWidth = me.getMaxBorderWidth(meta.data); + chart.outerRadius = Math.max((minSize - chart.borderWidth) / 2, 0); + chart.innerRadius = Math.max(cutoutPercentage ? (chart.outerRadius / 100) * (cutoutPercentage) : 0, 0); + chart.radiusLength = (chart.outerRadius - chart.innerRadius) / chart.getVisibleDatasetCount(); + chart.offsetX = offset.x * chart.outerRadius; + chart.offsetY = offset.y * chart.outerRadius; + + meta.total = me.calculateTotal(); + + me.outerRadius = chart.outerRadius - (chart.radiusLength * me.getRingIndex(me.index)); + me.innerRadius = Math.max(me.outerRadius - chart.radiusLength, 0); + + helpers.each(meta.data, function(arc, index) { + me.updateElement(arc, index, reset); + }); + }, + + updateElement: function(arc, index, reset) { + var me = this; + var chart = me.chart; + var chartArea = chart.chartArea; + var opts = chart.options; + var animationOpts = opts.animation; + var centerX = (chartArea.left + chartArea.right) / 2; + var centerY = (chartArea.top + chartArea.bottom) / 2; + var startAngle = opts.rotation; // non reset case handled later + var endAngle = opts.rotation; // non reset case handled later + var dataset = me.getDataset(); + var circumference = reset && animationOpts.animateRotate ? 0 : arc.hidden ? 0 : me.calculateCircumference(dataset.data[index]) * (opts.circumference / (2.0 * Math.PI)); + var innerRadius = reset && animationOpts.animateScale ? 0 : me.innerRadius; + var outerRadius = reset && animationOpts.animateScale ? 0 : me.outerRadius; + var valueAtIndexOrDefault = helpers.valueAtIndexOrDefault; + + helpers.extend(arc, { + // Utility + _datasetIndex: me.index, + _index: index, + + // Desired view properties + _model: { + x: centerX + chart.offsetX, + y: centerY + chart.offsetY, + startAngle: startAngle, + endAngle: endAngle, + circumference: circumference, + outerRadius: outerRadius, + innerRadius: innerRadius, + label: valueAtIndexOrDefault(dataset.label, index, chart.data.labels[index]) + } + }); + + var model = arc._model; + + // Resets the visual styles + var custom = arc.custom || {}; + var valueOrDefault = helpers.valueAtIndexOrDefault; + var elementOpts = this.chart.options.elements.arc; + model.backgroundColor = custom.backgroundColor ? custom.backgroundColor : valueOrDefault(dataset.backgroundColor, index, elementOpts.backgroundColor); + model.borderColor = custom.borderColor ? custom.borderColor : valueOrDefault(dataset.borderColor, index, elementOpts.borderColor); + model.borderWidth = custom.borderWidth ? custom.borderWidth : valueOrDefault(dataset.borderWidth, index, elementOpts.borderWidth); + + // Set correct angles if not resetting + if (!reset || !animationOpts.animateRotate) { + if (index === 0) { + model.startAngle = opts.rotation; + } else { + model.startAngle = me.getMeta().data[index - 1]._model.endAngle; + } + + model.endAngle = model.startAngle + model.circumference; + } + + arc.pivot(); + }, + + calculateTotal: function() { + var dataset = this.getDataset(); + var meta = this.getMeta(); + var total = 0; + var value; + + helpers.each(meta.data, function(element, index) { + value = dataset.data[index]; + if (!isNaN(value) && !element.hidden) { + total += Math.abs(value); + } + }); + + /* if (total === 0) { + total = NaN; + }*/ + + return total; + }, + + calculateCircumference: function(value) { + var total = this.getMeta().total; + if (total > 0 && !isNaN(value)) { + return (Math.PI * 2.0) * (Math.abs(value) / total); + } + return 0; + }, + + // gets the max border or hover width to properly scale pie charts + getMaxBorderWidth: function(arcs) { + var max = 0; + var index = this.index; + var length = arcs.length; + var borderWidth; + var hoverWidth; + + for (var i = 0; i < length; i++) { + borderWidth = arcs[i]._model ? arcs[i]._model.borderWidth : 0; + hoverWidth = arcs[i]._chart ? arcs[i]._chart.config.data.datasets[index].hoverBorderWidth : 0; + + max = borderWidth > max ? borderWidth : max; + max = hoverWidth > max ? hoverWidth : max; + } + return max; + } + }); +}; + +},{"26":26,"41":41,"46":46}],18:[function(require,module,exports){ +'use strict'; + +var defaults = require(26); +var elements = require(41); +var helpers = require(46); + +defaults._set('line', { + showLines: true, + spanGaps: false, + + hover: { + mode: 'label' + }, + + scales: { + xAxes: [{ + type: 'category', + id: 'x-axis-0' + }], + yAxes: [{ + type: 'linear', + id: 'y-axis-0' + }] + } +}); + +module.exports = function(Chart) { + + function lineEnabled(dataset, options) { + return helpers.valueOrDefault(dataset.showLine, options.showLines); + } + + Chart.controllers.line = Chart.DatasetController.extend({ + + datasetElementType: elements.Line, + + dataElementType: elements.Point, + + update: function(reset) { + var me = this; + var meta = me.getMeta(); + var line = meta.dataset; + var points = meta.data || []; + var options = me.chart.options; + var lineElementOptions = options.elements.line; + var scale = me.getScaleForId(meta.yAxisID); + var i, ilen, custom; + var dataset = me.getDataset(); + var showLine = lineEnabled(dataset, options); + + // Update Line + if (showLine) { + custom = line.custom || {}; + + // Compatibility: If the properties are defined with only the old name, use those values + if ((dataset.tension !== undefined) && (dataset.lineTension === undefined)) { + dataset.lineTension = dataset.tension; + } + + // Utility + line._scale = scale; + line._datasetIndex = me.index; + // Data + line._children = points; + // Model + line._model = { + // Appearance + // The default behavior of lines is to break at null values, according + // to https://github.com/chartjs/Chart.js/issues/2435#issuecomment-216718158 + // This option gives lines the ability to span gaps + spanGaps: dataset.spanGaps ? dataset.spanGaps : options.spanGaps, + tension: custom.tension ? custom.tension : helpers.valueOrDefault(dataset.lineTension, lineElementOptions.tension), + backgroundColor: custom.backgroundColor ? custom.backgroundColor : (dataset.backgroundColor || lineElementOptions.backgroundColor), + borderWidth: custom.borderWidth ? custom.borderWidth : (dataset.borderWidth || lineElementOptions.borderWidth), + borderColor: custom.borderColor ? custom.borderColor : (dataset.borderColor || lineElementOptions.borderColor), + borderCapStyle: custom.borderCapStyle ? custom.borderCapStyle : (dataset.borderCapStyle || lineElementOptions.borderCapStyle), + borderDash: custom.borderDash ? custom.borderDash : (dataset.borderDash || lineElementOptions.borderDash), + borderDashOffset: custom.borderDashOffset ? custom.borderDashOffset : (dataset.borderDashOffset || lineElementOptions.borderDashOffset), + borderJoinStyle: custom.borderJoinStyle ? custom.borderJoinStyle : (dataset.borderJoinStyle || lineElementOptions.borderJoinStyle), + fill: custom.fill ? custom.fill : (dataset.fill !== undefined ? dataset.fill : lineElementOptions.fill), + steppedLine: custom.steppedLine ? custom.steppedLine : helpers.valueOrDefault(dataset.steppedLine, lineElementOptions.stepped), + cubicInterpolationMode: custom.cubicInterpolationMode ? custom.cubicInterpolationMode : helpers.valueOrDefault(dataset.cubicInterpolationMode, lineElementOptions.cubicInterpolationMode), + }; + + line.pivot(); + } + + // Update Points + for (i = 0, ilen = points.length; i < ilen; ++i) { + me.updateElement(points[i], i, reset); + } + + if (showLine && line._model.tension !== 0) { + me.updateBezierControlPoints(); + } + + // Now pivot the point for animation + for (i = 0, ilen = points.length; i < ilen; ++i) { + points[i].pivot(); + } + }, + + getPointBackgroundColor: function(point, index) { + var backgroundColor = this.chart.options.elements.point.backgroundColor; + var dataset = this.getDataset(); + var custom = point.custom || {}; + + if (custom.backgroundColor) { + backgroundColor = custom.backgroundColor; + } else if (dataset.pointBackgroundColor) { + backgroundColor = helpers.valueAtIndexOrDefault(dataset.pointBackgroundColor, index, backgroundColor); + } else if (dataset.backgroundColor) { + backgroundColor = dataset.backgroundColor; + } + + return backgroundColor; + }, + + getPointBorderColor: function(point, index) { + var borderColor = this.chart.options.elements.point.borderColor; + var dataset = this.getDataset(); + var custom = point.custom || {}; + + if (custom.borderColor) { + borderColor = custom.borderColor; + } else if (dataset.pointBorderColor) { + borderColor = helpers.valueAtIndexOrDefault(dataset.pointBorderColor, index, borderColor); + } else if (dataset.borderColor) { + borderColor = dataset.borderColor; + } + + return borderColor; + }, + + getPointBorderWidth: function(point, index) { + var borderWidth = this.chart.options.elements.point.borderWidth; + var dataset = this.getDataset(); + var custom = point.custom || {}; + + if (!isNaN(custom.borderWidth)) { + borderWidth = custom.borderWidth; + } else if (!isNaN(dataset.pointBorderWidth) || helpers.isArray(dataset.pointBorderWidth)) { + borderWidth = helpers.valueAtIndexOrDefault(dataset.pointBorderWidth, index, borderWidth); + } else if (!isNaN(dataset.borderWidth)) { + borderWidth = dataset.borderWidth; + } + + return borderWidth; + }, + + getPointRotation: function(point, index) { + var pointRotation = this.chart.options.elements.point.rotation; + var dataset = this.getDataset(); + var custom = point.custom || {}; + + if (!isNaN(custom.rotation)) { + pointRotation = custom.rotation; + } else if (!isNaN(dataset.pointRotation) || helpers.isArray(dataset.pointRotation)) { + pointRotation = helpers.valueAtIndexOrDefault(dataset.pointRotation, index, pointRotation); + } + return pointRotation; + }, + + updateElement: function(point, index, reset) { + var me = this; + var meta = me.getMeta(); + var custom = point.custom || {}; + var dataset = me.getDataset(); + var datasetIndex = me.index; + var value = dataset.data[index]; + var yScale = me.getScaleForId(meta.yAxisID); + var xScale = me.getScaleForId(meta.xAxisID); + var pointOptions = me.chart.options.elements.point; + var x, y; + + // Compatibility: If the properties are defined with only the old name, use those values + if ((dataset.radius !== undefined) && (dataset.pointRadius === undefined)) { + dataset.pointRadius = dataset.radius; + } + if ((dataset.hitRadius !== undefined) && (dataset.pointHitRadius === undefined)) { + dataset.pointHitRadius = dataset.hitRadius; + } + + x = xScale.getPixelForValue(typeof value === 'object' ? value : NaN, index, datasetIndex); + y = reset ? yScale.getBasePixel() : me.calculatePointY(value, index, datasetIndex); + + // Utility + point._xScale = xScale; + point._yScale = yScale; + point._datasetIndex = datasetIndex; + point._index = index; + + // Desired view properties + point._model = { + x: x, + y: y, + skip: custom.skip || isNaN(x) || isNaN(y), + // Appearance + radius: custom.radius || helpers.valueAtIndexOrDefault(dataset.pointRadius, index, pointOptions.radius), + pointStyle: custom.pointStyle || helpers.valueAtIndexOrDefault(dataset.pointStyle, index, pointOptions.pointStyle), + rotation: me.getPointRotation(point, index), + backgroundColor: me.getPointBackgroundColor(point, index), + borderColor: me.getPointBorderColor(point, index), + borderWidth: me.getPointBorderWidth(point, index), + tension: meta.dataset._model ? meta.dataset._model.tension : 0, + steppedLine: meta.dataset._model ? meta.dataset._model.steppedLine : false, + // Tooltip + hitRadius: custom.hitRadius || helpers.valueAtIndexOrDefault(dataset.pointHitRadius, index, pointOptions.hitRadius) + }; + }, + + calculatePointY: function(value, index, datasetIndex) { + var me = this; + var chart = me.chart; + var meta = me.getMeta(); + var yScale = me.getScaleForId(meta.yAxisID); + var sumPos = 0; + var sumNeg = 0; + var i, ds, dsMeta; + + if (yScale.options.stacked) { + for (i = 0; i < datasetIndex; i++) { + ds = chart.data.datasets[i]; + dsMeta = chart.getDatasetMeta(i); + if (dsMeta.type === 'line' && dsMeta.yAxisID === yScale.id && chart.isDatasetVisible(i)) { + var stackedRightValue = Number(yScale.getRightValue(ds.data[index])); + if (stackedRightValue < 0) { + sumNeg += stackedRightValue || 0; + } else { + sumPos += stackedRightValue || 0; + } + } + } + + var rightValue = Number(yScale.getRightValue(value)); + if (rightValue < 0) { + return yScale.getPixelForValue(sumNeg + rightValue); + } + return yScale.getPixelForValue(sumPos + rightValue); + } + + return yScale.getPixelForValue(value); + }, + + updateBezierControlPoints: function() { + var me = this; + var meta = me.getMeta(); + var area = me.chart.chartArea; + var points = (meta.data || []); + var i, ilen, point, model, controlPoints; + + // Only consider points that are drawn in case the spanGaps option is used + if (meta.dataset._model.spanGaps) { + points = points.filter(function(pt) { + return !pt._model.skip; + }); + } + + function capControlPoint(pt, min, max) { + return Math.max(Math.min(pt, max), min); + } + + if (meta.dataset._model.cubicInterpolationMode === 'monotone') { + helpers.splineCurveMonotone(points); + } else { + for (i = 0, ilen = points.length; i < ilen; ++i) { + point = points[i]; + model = point._model; + controlPoints = helpers.splineCurve( + helpers.previousItem(points, i)._model, + model, + helpers.nextItem(points, i)._model, + meta.dataset._model.tension + ); + model.controlPointPreviousX = controlPoints.previous.x; + model.controlPointPreviousY = controlPoints.previous.y; + model.controlPointNextX = controlPoints.next.x; + model.controlPointNextY = controlPoints.next.y; + } + } + + if (me.chart.options.elements.line.capBezierPoints) { + for (i = 0, ilen = points.length; i < ilen; ++i) { + model = points[i]._model; + model.controlPointPreviousX = capControlPoint(model.controlPointPreviousX, area.left, area.right); + model.controlPointPreviousY = capControlPoint(model.controlPointPreviousY, area.top, area.bottom); + model.controlPointNextX = capControlPoint(model.controlPointNextX, area.left, area.right); + model.controlPointNextY = capControlPoint(model.controlPointNextY, area.top, area.bottom); + } + } + }, + + draw: function() { + var me = this; + var chart = me.chart; + var meta = me.getMeta(); + var points = meta.data || []; + var area = chart.chartArea; + var ilen = points.length; + var halfBorderWidth; + var i = 0; + + if (lineEnabled(me.getDataset(), chart.options)) { + halfBorderWidth = (meta.dataset._model.borderWidth || 0) / 2; + + helpers.canvas.clipArea(chart.ctx, { + left: area.left, + right: area.right, + top: area.top - halfBorderWidth, + bottom: area.bottom + halfBorderWidth + }); + + meta.dataset.draw(); + + helpers.canvas.unclipArea(chart.ctx); + } + + // Draw the points + for (; i < ilen; ++i) { + points[i].draw(area); + } + }, + + setHoverStyle: function(element) { + // Point + var dataset = this.chart.data.datasets[element._datasetIndex]; + var index = element._index; + var custom = element.custom || {}; + var model = element._model; + + element.$previousStyle = { + backgroundColor: model.backgroundColor, + borderColor: model.borderColor, + borderWidth: model.borderWidth, + radius: model.radius + }; + + model.backgroundColor = custom.hoverBackgroundColor || helpers.valueAtIndexOrDefault(dataset.pointHoverBackgroundColor, index, helpers.getHoverColor(model.backgroundColor)); + model.borderColor = custom.hoverBorderColor || helpers.valueAtIndexOrDefault(dataset.pointHoverBorderColor, index, helpers.getHoverColor(model.borderColor)); + model.borderWidth = custom.hoverBorderWidth || helpers.valueAtIndexOrDefault(dataset.pointHoverBorderWidth, index, model.borderWidth); + model.radius = custom.hoverRadius || helpers.valueAtIndexOrDefault(dataset.pointHoverRadius, index, this.chart.options.elements.point.hoverRadius); + }, + }); +}; + +},{"26":26,"41":41,"46":46}],19:[function(require,module,exports){ +'use strict'; + +var defaults = require(26); +var elements = require(41); +var helpers = require(46); + +defaults._set('polarArea', { + scale: { + type: 'radialLinear', + angleLines: { + display: false + }, + gridLines: { + circular: true + }, + pointLabels: { + display: false + }, + ticks: { + beginAtZero: true + } + }, + + // Boolean - Whether to animate the rotation of the chart + animation: { + animateRotate: true, + animateScale: true + }, + + startAngle: -0.5 * Math.PI, + legendCallback: function(chart) { + var text = []; + text.push('
    '); + + var data = chart.data; + var datasets = data.datasets; + var labels = data.labels; + + if (datasets.length) { + for (var i = 0; i < datasets[0].data.length; ++i) { + text.push('
  • '); + if (labels[i]) { + text.push(labels[i]); + } + text.push('
  • '); + } + } + + text.push('
'); + return text.join(''); + }, + legend: { + labels: { + generateLabels: function(chart) { + var data = chart.data; + if (data.labels.length && data.datasets.length) { + return data.labels.map(function(label, i) { + var meta = chart.getDatasetMeta(0); + var ds = data.datasets[0]; + var arc = meta.data[i]; + var custom = arc.custom || {}; + var valueAtIndexOrDefault = helpers.valueAtIndexOrDefault; + var arcOpts = chart.options.elements.arc; + var fill = custom.backgroundColor ? custom.backgroundColor : valueAtIndexOrDefault(ds.backgroundColor, i, arcOpts.backgroundColor); + var stroke = custom.borderColor ? custom.borderColor : valueAtIndexOrDefault(ds.borderColor, i, arcOpts.borderColor); + var bw = custom.borderWidth ? custom.borderWidth : valueAtIndexOrDefault(ds.borderWidth, i, arcOpts.borderWidth); + + return { + text: label, + fillStyle: fill, + strokeStyle: stroke, + lineWidth: bw, + hidden: isNaN(ds.data[i]) || meta.data[i].hidden, + + // Extra data used for toggling the correct item + index: i + }; + }); + } + return []; + } + }, + + onClick: function(e, legendItem) { + var index = legendItem.index; + var chart = this.chart; + var i, ilen, meta; + + for (i = 0, ilen = (chart.data.datasets || []).length; i < ilen; ++i) { + meta = chart.getDatasetMeta(i); + meta.data[index].hidden = !meta.data[index].hidden; + } + + chart.update(); + } + }, + + // Need to override these to give a nice default + tooltips: { + callbacks: { + title: function() { + return ''; + }, + label: function(item, data) { + return data.labels[item.index] + ': ' + item.yLabel; + } + } + } +}); + +module.exports = function(Chart) { + + Chart.controllers.polarArea = Chart.DatasetController.extend({ + + dataElementType: elements.Arc, + + linkScales: helpers.noop, + + update: function(reset) { + var me = this; + var dataset = me.getDataset(); + var meta = me.getMeta(); + var start = me.chart.options.startAngle || 0; + var starts = me._starts = []; + var angles = me._angles = []; + var i, ilen, angle; + + me._updateRadius(); + + meta.count = me.countVisibleElements(); + + for (i = 0, ilen = dataset.data.length; i < ilen; i++) { + starts[i] = start; + angle = me._computeAngle(i); + angles[i] = angle; + start += angle; + } + + helpers.each(meta.data, function(arc, index) { + me.updateElement(arc, index, reset); + }); + }, + + /** + * @private + */ + _updateRadius: function() { + var me = this; + var chart = me.chart; + var chartArea = chart.chartArea; + var opts = chart.options; + var arcOpts = opts.elements.arc; + var minSize = Math.min(chartArea.right - chartArea.left, chartArea.bottom - chartArea.top); + + chart.outerRadius = Math.max((minSize - arcOpts.borderWidth / 2) / 2, 0); + chart.innerRadius = Math.max(opts.cutoutPercentage ? (chart.outerRadius / 100) * (opts.cutoutPercentage) : 1, 0); + chart.radiusLength = (chart.outerRadius - chart.innerRadius) / chart.getVisibleDatasetCount(); + + me.outerRadius = chart.outerRadius - (chart.radiusLength * me.index); + me.innerRadius = me.outerRadius - chart.radiusLength; + }, + + updateElement: function(arc, index, reset) { + var me = this; + var chart = me.chart; + var dataset = me.getDataset(); + var opts = chart.options; + var animationOpts = opts.animation; + var scale = chart.scale; + var labels = chart.data.labels; + + var centerX = scale.xCenter; + var centerY = scale.yCenter; + + // var negHalfPI = -0.5 * Math.PI; + var datasetStartAngle = opts.startAngle; + var distance = arc.hidden ? 0 : scale.getDistanceFromCenterForValue(dataset.data[index]); + var startAngle = me._starts[index]; + var endAngle = startAngle + (arc.hidden ? 0 : me._angles[index]); + + var resetRadius = animationOpts.animateScale ? 0 : scale.getDistanceFromCenterForValue(dataset.data[index]); + + helpers.extend(arc, { + // Utility + _datasetIndex: me.index, + _index: index, + _scale: scale, + + // Desired view properties + _model: { + x: centerX, + y: centerY, + innerRadius: 0, + outerRadius: reset ? resetRadius : distance, + startAngle: reset && animationOpts.animateRotate ? datasetStartAngle : startAngle, + endAngle: reset && animationOpts.animateRotate ? datasetStartAngle : endAngle, + label: helpers.valueAtIndexOrDefault(labels, index, labels[index]) + } + }); + + // Apply border and fill style + var elementOpts = this.chart.options.elements.arc; + var custom = arc.custom || {}; + var valueOrDefault = helpers.valueAtIndexOrDefault; + var model = arc._model; + + model.backgroundColor = custom.backgroundColor ? custom.backgroundColor : valueOrDefault(dataset.backgroundColor, index, elementOpts.backgroundColor); + model.borderColor = custom.borderColor ? custom.borderColor : valueOrDefault(dataset.borderColor, index, elementOpts.borderColor); + model.borderWidth = custom.borderWidth ? custom.borderWidth : valueOrDefault(dataset.borderWidth, index, elementOpts.borderWidth); + + arc.pivot(); + }, + + countVisibleElements: function() { + var dataset = this.getDataset(); + var meta = this.getMeta(); + var count = 0; + + helpers.each(meta.data, function(element, index) { + if (!isNaN(dataset.data[index]) && !element.hidden) { + count++; + } + }); + + return count; + }, + + /** + * @private + */ + _computeAngle: function(index) { + var me = this; + var count = this.getMeta().count; + var dataset = me.getDataset(); + var meta = me.getMeta(); + + if (isNaN(dataset.data[index]) || meta.data[index].hidden) { + return 0; + } + + // Scriptable options + var context = { + chart: me.chart, + dataIndex: index, + dataset: dataset, + datasetIndex: me.index + }; + + return helpers.options.resolve([ + me.chart.options.elements.arc.angle, + (2 * Math.PI) / count + ], context, index); + } + }); +}; + +},{"26":26,"41":41,"46":46}],20:[function(require,module,exports){ +'use strict'; + +var defaults = require(26); +var elements = require(41); +var helpers = require(46); + +defaults._set('radar', { + scale: { + type: 'radialLinear' + }, + elements: { + line: { + tension: 0 // no bezier in radar + } + } +}); + +module.exports = function(Chart) { + + Chart.controllers.radar = Chart.DatasetController.extend({ + + datasetElementType: elements.Line, + + dataElementType: elements.Point, + + linkScales: helpers.noop, + + update: function(reset) { + var me = this; + var meta = me.getMeta(); + var line = meta.dataset; + var points = meta.data; + var custom = line.custom || {}; + var dataset = me.getDataset(); + var lineElementOptions = me.chart.options.elements.line; + var scale = me.chart.scale; + + // Compatibility: If the properties are defined with only the old name, use those values + if ((dataset.tension !== undefined) && (dataset.lineTension === undefined)) { + dataset.lineTension = dataset.tension; + } + + helpers.extend(meta.dataset, { + // Utility + _datasetIndex: me.index, + _scale: scale, + // Data + _children: points, + _loop: true, + // Model + _model: { + // Appearance + tension: custom.tension ? custom.tension : helpers.valueOrDefault(dataset.lineTension, lineElementOptions.tension), + backgroundColor: custom.backgroundColor ? custom.backgroundColor : (dataset.backgroundColor || lineElementOptions.backgroundColor), + borderWidth: custom.borderWidth ? custom.borderWidth : (dataset.borderWidth || lineElementOptions.borderWidth), + borderColor: custom.borderColor ? custom.borderColor : (dataset.borderColor || lineElementOptions.borderColor), + fill: custom.fill ? custom.fill : (dataset.fill !== undefined ? dataset.fill : lineElementOptions.fill), + borderCapStyle: custom.borderCapStyle ? custom.borderCapStyle : (dataset.borderCapStyle || lineElementOptions.borderCapStyle), + borderDash: custom.borderDash ? custom.borderDash : (dataset.borderDash || lineElementOptions.borderDash), + borderDashOffset: custom.borderDashOffset ? custom.borderDashOffset : (dataset.borderDashOffset || lineElementOptions.borderDashOffset), + borderJoinStyle: custom.borderJoinStyle ? custom.borderJoinStyle : (dataset.borderJoinStyle || lineElementOptions.borderJoinStyle), + } + }); + + meta.dataset.pivot(); + + // Update Points + helpers.each(points, function(point, index) { + me.updateElement(point, index, reset); + }, me); + + // Update bezier control points + me.updateBezierControlPoints(); + }, + updateElement: function(point, index, reset) { + var me = this; + var custom = point.custom || {}; + var dataset = me.getDataset(); + var scale = me.chart.scale; + var pointElementOptions = me.chart.options.elements.point; + var pointPosition = scale.getPointPositionForValue(index, dataset.data[index]); + + // Compatibility: If the properties are defined with only the old name, use those values + if ((dataset.radius !== undefined) && (dataset.pointRadius === undefined)) { + dataset.pointRadius = dataset.radius; + } + if ((dataset.hitRadius !== undefined) && (dataset.pointHitRadius === undefined)) { + dataset.pointHitRadius = dataset.hitRadius; + } + + helpers.extend(point, { + // Utility + _datasetIndex: me.index, + _index: index, + _scale: scale, + + // Desired view properties + _model: { + x: reset ? scale.xCenter : pointPosition.x, // value not used in dataset scale, but we want a consistent API between scales + y: reset ? scale.yCenter : pointPosition.y, + + // Appearance + tension: custom.tension ? custom.tension : helpers.valueOrDefault(dataset.lineTension, me.chart.options.elements.line.tension), + radius: custom.radius ? custom.radius : helpers.valueAtIndexOrDefault(dataset.pointRadius, index, pointElementOptions.radius), + backgroundColor: custom.backgroundColor ? custom.backgroundColor : helpers.valueAtIndexOrDefault(dataset.pointBackgroundColor, index, pointElementOptions.backgroundColor), + borderColor: custom.borderColor ? custom.borderColor : helpers.valueAtIndexOrDefault(dataset.pointBorderColor, index, pointElementOptions.borderColor), + borderWidth: custom.borderWidth ? custom.borderWidth : helpers.valueAtIndexOrDefault(dataset.pointBorderWidth, index, pointElementOptions.borderWidth), + pointStyle: custom.pointStyle ? custom.pointStyle : helpers.valueAtIndexOrDefault(dataset.pointStyle, index, pointElementOptions.pointStyle), + rotation: custom.rotation ? custom.rotation : helpers.valueAtIndexOrDefault(dataset.pointRotation, index, pointElementOptions.rotation), + + // Tooltip + hitRadius: custom.hitRadius ? custom.hitRadius : helpers.valueAtIndexOrDefault(dataset.pointHitRadius, index, pointElementOptions.hitRadius) + } + }); + + point._model.skip = custom.skip ? custom.skip : (isNaN(point._model.x) || isNaN(point._model.y)); + }, + updateBezierControlPoints: function() { + var chartArea = this.chart.chartArea; + var meta = this.getMeta(); + + helpers.each(meta.data, function(point, index) { + var model = point._model; + var controlPoints = helpers.splineCurve( + helpers.previousItem(meta.data, index, true)._model, + model, + helpers.nextItem(meta.data, index, true)._model, + model.tension + ); + + // Prevent the bezier going outside of the bounds of the graph + model.controlPointPreviousX = Math.max(Math.min(controlPoints.previous.x, chartArea.right), chartArea.left); + model.controlPointPreviousY = Math.max(Math.min(controlPoints.previous.y, chartArea.bottom), chartArea.top); + + model.controlPointNextX = Math.max(Math.min(controlPoints.next.x, chartArea.right), chartArea.left); + model.controlPointNextY = Math.max(Math.min(controlPoints.next.y, chartArea.bottom), chartArea.top); + + // Now pivot the point for animation + point.pivot(); + }); + }, + + setHoverStyle: function(point) { + // Point + var dataset = this.chart.data.datasets[point._datasetIndex]; + var custom = point.custom || {}; + var index = point._index; + var model = point._model; + + point.$previousStyle = { + backgroundColor: model.backgroundColor, + borderColor: model.borderColor, + borderWidth: model.borderWidth, + radius: model.radius + }; + + model.radius = custom.hoverRadius ? custom.hoverRadius : helpers.valueAtIndexOrDefault(dataset.pointHoverRadius, index, this.chart.options.elements.point.hoverRadius); + model.backgroundColor = custom.hoverBackgroundColor ? custom.hoverBackgroundColor : helpers.valueAtIndexOrDefault(dataset.pointHoverBackgroundColor, index, helpers.getHoverColor(model.backgroundColor)); + model.borderColor = custom.hoverBorderColor ? custom.hoverBorderColor : helpers.valueAtIndexOrDefault(dataset.pointHoverBorderColor, index, helpers.getHoverColor(model.borderColor)); + model.borderWidth = custom.hoverBorderWidth ? custom.hoverBorderWidth : helpers.valueAtIndexOrDefault(dataset.pointHoverBorderWidth, index, model.borderWidth); + }, + }); +}; + +},{"26":26,"41":41,"46":46}],21:[function(require,module,exports){ +'use strict'; + +var defaults = require(26); + +defaults._set('scatter', { + hover: { + mode: 'single' + }, + + scales: { + xAxes: [{ + id: 'x-axis-1', // need an ID so datasets can reference the scale + type: 'linear', // scatter should not use a category axis + position: 'bottom' + }], + yAxes: [{ + id: 'y-axis-1', + type: 'linear', + position: 'left' + }] + }, + + showLines: false, + + tooltips: { + callbacks: { + title: function() { + return ''; // doesn't make sense for scatter since data are formatted as a point + }, + label: function(item) { + return '(' + item.xLabel + ', ' + item.yLabel + ')'; + } + } + } +}); + +module.exports = function(Chart) { + + // Scatter charts use line controllers + Chart.controllers.scatter = Chart.controllers.line; + +}; + +},{"26":26}],22:[function(require,module,exports){ +'use strict'; + +var Element = require(27); + +var exports = module.exports = Element.extend({ + chart: null, // the animation associated chart instance + currentStep: 0, // the current animation step + numSteps: 60, // default number of steps + easing: '', // the easing to use for this animation + render: null, // render function used by the animation service + + onAnimationProgress: null, // user specified callback to fire on each step of the animation + onAnimationComplete: null, // user specified callback to fire when the animation finishes +}); + +// DEPRECATIONS + +/** + * Provided for backward compatibility, use Chart.Animation instead + * @prop Chart.Animation#animationObject + * @deprecated since version 2.6.0 + * @todo remove at version 3 + */ +Object.defineProperty(exports.prototype, 'animationObject', { + get: function() { + return this; + } +}); + +/** + * Provided for backward compatibility, use Chart.Animation#chart instead + * @prop Chart.Animation#chartInstance + * @deprecated since version 2.6.0 + * @todo remove at version 3 + */ +Object.defineProperty(exports.prototype, 'chartInstance', { + get: function() { + return this.chart; + }, + set: function(value) { + this.chart = value; + } +}); + +},{"27":27}],23:[function(require,module,exports){ +/* global window: false */ +'use strict'; + +var defaults = require(26); +var helpers = require(46); + +defaults._set('global', { + animation: { + duration: 1000, + easing: 'easeOutQuart', + onProgress: helpers.noop, + onComplete: helpers.noop + } +}); + +module.exports = { + frameDuration: 17, + animations: [], + dropFrames: 0, + request: null, + + /** + * @param {Chart} chart - The chart to animate. + * @param {Chart.Animation} animation - The animation that we will animate. + * @param {Number} duration - The animation duration in ms. + * @param {Boolean} lazy - if true, the chart is not marked as animating to enable more responsive interactions + */ + addAnimation: function(chart, animation, duration, lazy) { + var animations = this.animations; + var i, ilen; + + animation.chart = chart; + + if (!lazy) { + chart.animating = true; + } + + for (i = 0, ilen = animations.length; i < ilen; ++i) { + if (animations[i].chart === chart) { + animations[i] = animation; + return; + } + } + + animations.push(animation); + + // If there are no animations queued, manually kickstart a digest, for lack of a better word + if (animations.length === 1) { + this.requestAnimationFrame(); + } + }, + + cancelAnimation: function(chart) { + var index = helpers.findIndex(this.animations, function(animation) { + return animation.chart === chart; + }); + + if (index !== -1) { + this.animations.splice(index, 1); + chart.animating = false; + } + }, + + requestAnimationFrame: function() { + var me = this; + if (me.request === null) { + // Skip animation frame requests until the active one is executed. + // This can happen when processing mouse events, e.g. 'mousemove' + // and 'mouseout' events will trigger multiple renders. + me.request = helpers.requestAnimFrame.call(window, function() { + me.request = null; + me.startDigest(); + }); + } + }, + + /** + * @private + */ + startDigest: function() { + var me = this; + var startTime = Date.now(); + var framesToDrop = 0; + + if (me.dropFrames > 1) { + framesToDrop = Math.floor(me.dropFrames); + me.dropFrames = me.dropFrames % 1; + } + + me.advance(1 + framesToDrop); + + var endTime = Date.now(); + + me.dropFrames += (endTime - startTime) / me.frameDuration; + + // Do we have more stuff to animate? + if (me.animations.length > 0) { + me.requestAnimationFrame(); + } + }, + + /** + * @private + */ + advance: function(count) { + var animations = this.animations; + var animation, chart; + var i = 0; + + while (i < animations.length) { + animation = animations[i]; + chart = animation.chart; + + animation.currentStep = (animation.currentStep || 0) + count; + animation.currentStep = Math.min(animation.currentStep, animation.numSteps); + + helpers.callback(animation.render, [chart, animation], chart); + helpers.callback(animation.onAnimationProgress, [animation], chart); + + if (animation.currentStep >= animation.numSteps) { + helpers.callback(animation.onAnimationComplete, [animation], chart); + chart.animating = false; + animations.splice(i, 1); + } else { + ++i; + } + } + } +}; + +},{"26":26,"46":46}],24:[function(require,module,exports){ +'use strict'; + +var Animation = require(22); +var animations = require(23); +var defaults = require(26); +var helpers = require(46); +var Interaction = require(29); +var layouts = require(31); +var platform = require(49); +var plugins = require(32); +var scaleService = require(34); +var Tooltip = require(36); + +module.exports = function(Chart) { + + // Create a dictionary of chart types, to allow for extension of existing types + Chart.types = {}; + + // Store a reference to each instance - allowing us to globally resize chart instances on window resize. + // Destroy method on the chart will remove the instance of the chart from this reference. + Chart.instances = {}; + + // Controllers available for dataset visualization eg. bar, line, slice, etc. + Chart.controllers = {}; + + /** + * Initializes the given config with global and chart default values. + */ + function initConfig(config) { + config = config || {}; + + // Do NOT use configMerge() for the data object because this method merges arrays + // and so would change references to labels and datasets, preventing data updates. + var data = config.data = config.data || {}; + data.datasets = data.datasets || []; + data.labels = data.labels || []; + + config.options = helpers.configMerge( + defaults.global, + defaults[config.type], + config.options || {}); + + return config; + } + + /** + * Updates the config of the chart + * @param chart {Chart} chart to update the options for + */ + function updateConfig(chart) { + var newOptions = chart.options; + + helpers.each(chart.scales, function(scale) { + layouts.removeBox(chart, scale); + }); + + newOptions = helpers.configMerge( + Chart.defaults.global, + Chart.defaults[chart.config.type], + newOptions); + + chart.options = chart.config.options = newOptions; + chart.ensureScalesHaveIDs(); + chart.buildOrUpdateScales(); + // Tooltip + chart.tooltip._options = newOptions.tooltips; + chart.tooltip.initialize(); + } + + function positionIsHorizontal(position) { + return position === 'top' || position === 'bottom'; + } + + helpers.extend(Chart.prototype, /** @lends Chart */ { + /** + * @private + */ + construct: function(item, config) { + var me = this; + + config = initConfig(config); + + var context = platform.acquireContext(item, config); + var canvas = context && context.canvas; + var height = canvas && canvas.height; + var width = canvas && canvas.width; + + me.id = helpers.uid(); + me.ctx = context; + me.canvas = canvas; + me.config = config; + me.width = width; + me.height = height; + me.aspectRatio = height ? width / height : null; + me.options = config.options; + me._bufferedRender = false; + + /** + * Provided for backward compatibility, Chart and Chart.Controller have been merged, + * the "instance" still need to be defined since it might be called from plugins. + * @prop Chart#chart + * @deprecated since version 2.6.0 + * @todo remove at version 3 + * @private + */ + me.chart = me; + me.controller = me; // chart.chart.controller #inception + + // Add the chart instance to the global namespace + Chart.instances[me.id] = me; + + // Define alias to the config data: `chart.data === chart.config.data` + Object.defineProperty(me, 'data', { + get: function() { + return me.config.data; + }, + set: function(value) { + me.config.data = value; + } + }); + + if (!context || !canvas) { + // The given item is not a compatible context2d element, let's return before finalizing + // the chart initialization but after setting basic chart / controller properties that + // can help to figure out that the chart is not valid (e.g chart.canvas !== null); + // https://github.com/chartjs/Chart.js/issues/2807 + console.error("Failed to create chart: can't acquire context from the given item"); + return; + } + + me.initialize(); + me.update(); + }, + + /** + * @private + */ + initialize: function() { + var me = this; + + // Before init plugin notification + plugins.notify(me, 'beforeInit'); + + helpers.retinaScale(me, me.options.devicePixelRatio); + + me.bindEvents(); + + if (me.options.responsive) { + // Initial resize before chart draws (must be silent to preserve initial animations). + me.resize(true); + } + + // Make sure scales have IDs and are built before we build any controllers. + me.ensureScalesHaveIDs(); + me.buildOrUpdateScales(); + me.initToolTip(); + + // After init plugin notification + plugins.notify(me, 'afterInit'); + + return me; + }, + + clear: function() { + helpers.canvas.clear(this); + return this; + }, + + stop: function() { + // Stops any current animation loop occurring + animations.cancelAnimation(this); + return this; + }, + + resize: function(silent) { + var me = this; + var options = me.options; + var canvas = me.canvas; + var aspectRatio = (options.maintainAspectRatio && me.aspectRatio) || null; + + // the canvas render width and height will be casted to integers so make sure that + // the canvas display style uses the same integer values to avoid blurring effect. + + // Set to 0 instead of canvas.size because the size defaults to 300x150 if the element is collapsed + var newWidth = Math.max(0, Math.floor(helpers.getMaximumWidth(canvas))); + var newHeight = Math.max(0, Math.floor(aspectRatio ? newWidth / aspectRatio : helpers.getMaximumHeight(canvas))); + + if (me.width === newWidth && me.height === newHeight) { + return; + } + + canvas.width = me.width = newWidth; + canvas.height = me.height = newHeight; + canvas.style.width = newWidth + 'px'; + canvas.style.height = newHeight + 'px'; + + helpers.retinaScale(me, options.devicePixelRatio); + + if (!silent) { + // Notify any plugins about the resize + var newSize = {width: newWidth, height: newHeight}; + plugins.notify(me, 'resize', [newSize]); + + // Notify of resize + if (me.options.onResize) { + me.options.onResize(me, newSize); + } + + me.stop(); + me.update({ + duration: me.options.responsiveAnimationDuration + }); + } + }, + + ensureScalesHaveIDs: function() { + var options = this.options; + var scalesOptions = options.scales || {}; + var scaleOptions = options.scale; + + helpers.each(scalesOptions.xAxes, function(xAxisOptions, index) { + xAxisOptions.id = xAxisOptions.id || ('x-axis-' + index); + }); + + helpers.each(scalesOptions.yAxes, function(yAxisOptions, index) { + yAxisOptions.id = yAxisOptions.id || ('y-axis-' + index); + }); + + if (scaleOptions) { + scaleOptions.id = scaleOptions.id || 'scale'; + } + }, + + /** + * Builds a map of scale ID to scale object for future lookup. + */ + buildOrUpdateScales: function() { + var me = this; + var options = me.options; + var scales = me.scales || {}; + var items = []; + var updated = Object.keys(scales).reduce(function(obj, id) { + obj[id] = false; + return obj; + }, {}); + + if (options.scales) { + items = items.concat( + (options.scales.xAxes || []).map(function(xAxisOptions) { + return {options: xAxisOptions, dtype: 'category', dposition: 'bottom'}; + }), + (options.scales.yAxes || []).map(function(yAxisOptions) { + return {options: yAxisOptions, dtype: 'linear', dposition: 'left'}; + }) + ); + } + + if (options.scale) { + items.push({ + options: options.scale, + dtype: 'radialLinear', + isDefault: true, + dposition: 'chartArea' + }); + } + + helpers.each(items, function(item) { + var scaleOptions = item.options; + var id = scaleOptions.id; + var scaleType = helpers.valueOrDefault(scaleOptions.type, item.dtype); + + if (positionIsHorizontal(scaleOptions.position) !== positionIsHorizontal(item.dposition)) { + scaleOptions.position = item.dposition; + } + + updated[id] = true; + var scale = null; + if (id in scales && scales[id].type === scaleType) { + scale = scales[id]; + scale.options = scaleOptions; + scale.ctx = me.ctx; + scale.chart = me; + } else { + var scaleClass = scaleService.getScaleConstructor(scaleType); + if (!scaleClass) { + return; + } + scale = new scaleClass({ + id: id, + type: scaleType, + options: scaleOptions, + ctx: me.ctx, + chart: me + }); + scales[scale.id] = scale; + } + + scale.mergeTicksOptions(); + + // TODO(SB): I think we should be able to remove this custom case (options.scale) + // and consider it as a regular scale part of the "scales"" map only! This would + // make the logic easier and remove some useless? custom code. + if (item.isDefault) { + me.scale = scale; + } + }); + // clear up discarded scales + helpers.each(updated, function(hasUpdated, id) { + if (!hasUpdated) { + delete scales[id]; + } + }); + + me.scales = scales; + + scaleService.addScalesToLayout(this); + }, + + buildOrUpdateControllers: function() { + var me = this; + var types = []; + var newControllers = []; + + helpers.each(me.data.datasets, function(dataset, datasetIndex) { + var meta = me.getDatasetMeta(datasetIndex); + var type = dataset.type || me.config.type; + + if (meta.type && meta.type !== type) { + me.destroyDatasetMeta(datasetIndex); + meta = me.getDatasetMeta(datasetIndex); + } + meta.type = type; + + types.push(meta.type); + + if (meta.controller) { + meta.controller.updateIndex(datasetIndex); + meta.controller.linkScales(); + } else { + var ControllerClass = Chart.controllers[meta.type]; + if (ControllerClass === undefined) { + throw new Error('"' + meta.type + '" is not a chart type.'); + } + + meta.controller = new ControllerClass(me, datasetIndex); + newControllers.push(meta.controller); + } + }, me); + + return newControllers; + }, + + /** + * Reset the elements of all datasets + * @private + */ + resetElements: function() { + var me = this; + helpers.each(me.data.datasets, function(dataset, datasetIndex) { + me.getDatasetMeta(datasetIndex).controller.reset(); + }, me); + }, + + /** + * Resets the chart back to it's state before the initial animation + */ + reset: function() { + this.resetElements(); + this.tooltip.initialize(); + }, + + update: function(config) { + var me = this; + + if (!config || typeof config !== 'object') { + // backwards compatibility + config = { + duration: config, + lazy: arguments[1] + }; + } + + updateConfig(me); + + // plugins options references might have change, let's invalidate the cache + // https://github.com/chartjs/Chart.js/issues/5111#issuecomment-355934167 + plugins._invalidate(me); + + if (plugins.notify(me, 'beforeUpdate') === false) { + return; + } + + // In case the entire data object changed + me.tooltip._data = me.data; + + // Make sure dataset controllers are updated and new controllers are reset + var newControllers = me.buildOrUpdateControllers(); + + // Make sure all dataset controllers have correct meta data counts + helpers.each(me.data.datasets, function(dataset, datasetIndex) { + me.getDatasetMeta(datasetIndex).controller.buildOrUpdateElements(); + }, me); + + me.updateLayout(); + + // Can only reset the new controllers after the scales have been updated + if (me.options.animation && me.options.animation.duration) { + helpers.each(newControllers, function(controller) { + controller.reset(); + }); + } + + me.updateDatasets(); + + // Need to reset tooltip in case it is displayed with elements that are removed + // after update. + me.tooltip.initialize(); + + // Last active contains items that were previously in the tooltip. + // When we reset the tooltip, we need to clear it + me.lastActive = []; + + // Do this before render so that any plugins that need final scale updates can use it + plugins.notify(me, 'afterUpdate'); + + if (me._bufferedRender) { + me._bufferedRequest = { + duration: config.duration, + easing: config.easing, + lazy: config.lazy + }; + } else { + me.render(config); + } + }, + + /** + * Updates the chart layout unless a plugin returns `false` to the `beforeLayout` + * hook, in which case, plugins will not be called on `afterLayout`. + * @private + */ + updateLayout: function() { + var me = this; + + if (plugins.notify(me, 'beforeLayout') === false) { + return; + } + + layouts.update(this, this.width, this.height); + + /** + * Provided for backward compatibility, use `afterLayout` instead. + * @method IPlugin#afterScaleUpdate + * @deprecated since version 2.5.0 + * @todo remove at version 3 + * @private + */ + plugins.notify(me, 'afterScaleUpdate'); + plugins.notify(me, 'afterLayout'); + }, + + /** + * Updates all datasets unless a plugin returns `false` to the `beforeDatasetsUpdate` + * hook, in which case, plugins will not be called on `afterDatasetsUpdate`. + * @private + */ + updateDatasets: function() { + var me = this; + + if (plugins.notify(me, 'beforeDatasetsUpdate') === false) { + return; + } + + for (var i = 0, ilen = me.data.datasets.length; i < ilen; ++i) { + me.updateDataset(i); + } + + plugins.notify(me, 'afterDatasetsUpdate'); + }, + + /** + * Updates dataset at index unless a plugin returns `false` to the `beforeDatasetUpdate` + * hook, in which case, plugins will not be called on `afterDatasetUpdate`. + * @private + */ + updateDataset: function(index) { + var me = this; + var meta = me.getDatasetMeta(index); + var args = { + meta: meta, + index: index + }; + + if (plugins.notify(me, 'beforeDatasetUpdate', [args]) === false) { + return; + } + + meta.controller.update(); + + plugins.notify(me, 'afterDatasetUpdate', [args]); + }, + + render: function(config) { + var me = this; + + if (!config || typeof config !== 'object') { + // backwards compatibility + config = { + duration: config, + lazy: arguments[1] + }; + } + + var duration = config.duration; + var lazy = config.lazy; + + if (plugins.notify(me, 'beforeRender') === false) { + return; + } + + var animationOptions = me.options.animation; + var onComplete = function(animation) { + plugins.notify(me, 'afterRender'); + helpers.callback(animationOptions && animationOptions.onComplete, [animation], me); + }; + + if (animationOptions && ((typeof duration !== 'undefined' && duration !== 0) || (typeof duration === 'undefined' && animationOptions.duration !== 0))) { + var animation = new Animation({ + numSteps: (duration || animationOptions.duration) / 16.66, // 60 fps + easing: config.easing || animationOptions.easing, + + render: function(chart, animationObject) { + var easingFunction = helpers.easing.effects[animationObject.easing]; + var currentStep = animationObject.currentStep; + var stepDecimal = currentStep / animationObject.numSteps; + + chart.draw(easingFunction(stepDecimal), stepDecimal, currentStep); + }, + + onAnimationProgress: animationOptions.onProgress, + onAnimationComplete: onComplete + }); + + animations.addAnimation(me, animation, duration, lazy); + } else { + me.draw(); + + // See https://github.com/chartjs/Chart.js/issues/3781 + onComplete(new Animation({numSteps: 0, chart: me})); + } + + return me; + }, + + draw: function(easingValue) { + var me = this; + + me.clear(); + + if (helpers.isNullOrUndef(easingValue)) { + easingValue = 1; + } + + me.transition(easingValue); + + if (me.width <= 0 || me.height <= 0) { + return; + } + + if (plugins.notify(me, 'beforeDraw', [easingValue]) === false) { + return; + } + + // Draw all the scales + helpers.each(me.boxes, function(box) { + box.draw(me.chartArea); + }, me); + + if (me.scale) { + me.scale.draw(); + } + + me.drawDatasets(easingValue); + me._drawTooltip(easingValue); + + plugins.notify(me, 'afterDraw', [easingValue]); + }, + + /** + * @private + */ + transition: function(easingValue) { + var me = this; + + for (var i = 0, ilen = (me.data.datasets || []).length; i < ilen; ++i) { + if (me.isDatasetVisible(i)) { + me.getDatasetMeta(i).controller.transition(easingValue); + } + } + + me.tooltip.transition(easingValue); + }, + + /** + * Draws all datasets unless a plugin returns `false` to the `beforeDatasetsDraw` + * hook, in which case, plugins will not be called on `afterDatasetsDraw`. + * @private + */ + drawDatasets: function(easingValue) { + var me = this; + + if (plugins.notify(me, 'beforeDatasetsDraw', [easingValue]) === false) { + return; + } + + // Draw datasets reversed to support proper line stacking + for (var i = (me.data.datasets || []).length - 1; i >= 0; --i) { + if (me.isDatasetVisible(i)) { + me.drawDataset(i, easingValue); + } + } + + plugins.notify(me, 'afterDatasetsDraw', [easingValue]); + }, + + /** + * Draws dataset at index unless a plugin returns `false` to the `beforeDatasetDraw` + * hook, in which case, plugins will not be called on `afterDatasetDraw`. + * @private + */ + drawDataset: function(index, easingValue) { + var me = this; + var meta = me.getDatasetMeta(index); + var args = { + meta: meta, + index: index, + easingValue: easingValue + }; + + if (plugins.notify(me, 'beforeDatasetDraw', [args]) === false) { + return; + } + + meta.controller.draw(easingValue); + + plugins.notify(me, 'afterDatasetDraw', [args]); + }, + + /** + * Draws tooltip unless a plugin returns `false` to the `beforeTooltipDraw` + * hook, in which case, plugins will not be called on `afterTooltipDraw`. + * @private + */ + _drawTooltip: function(easingValue) { + var me = this; + var tooltip = me.tooltip; + var args = { + tooltip: tooltip, + easingValue: easingValue + }; + + if (plugins.notify(me, 'beforeTooltipDraw', [args]) === false) { + return; + } + + tooltip.draw(); + + plugins.notify(me, 'afterTooltipDraw', [args]); + }, + + // Get the single element that was clicked on + // @return : An object containing the dataset index and element index of the matching element. Also contains the rectangle that was draw + getElementAtEvent: function(e) { + return Interaction.modes.single(this, e); + }, + + getElementsAtEvent: function(e) { + return Interaction.modes.label(this, e, {intersect: true}); + }, + + getElementsAtXAxis: function(e) { + return Interaction.modes['x-axis'](this, e, {intersect: true}); + }, + + getElementsAtEventForMode: function(e, mode, options) { + var method = Interaction.modes[mode]; + if (typeof method === 'function') { + return method(this, e, options); + } + + return []; + }, + + getDatasetAtEvent: function(e) { + return Interaction.modes.dataset(this, e, {intersect: true}); + }, + + getDatasetMeta: function(datasetIndex) { + var me = this; + var dataset = me.data.datasets[datasetIndex]; + if (!dataset._meta) { + dataset._meta = {}; + } + + var meta = dataset._meta[me.id]; + if (!meta) { + meta = dataset._meta[me.id] = { + type: null, + data: [], + dataset: null, + controller: null, + hidden: null, // See isDatasetVisible() comment + xAxisID: null, + yAxisID: null + }; + } + + return meta; + }, + + getVisibleDatasetCount: function() { + var count = 0; + for (var i = 0, ilen = this.data.datasets.length; i < ilen; ++i) { + if (this.isDatasetVisible(i)) { + count++; + } + } + return count; + }, + + isDatasetVisible: function(datasetIndex) { + var meta = this.getDatasetMeta(datasetIndex); + + // meta.hidden is a per chart dataset hidden flag override with 3 states: if true or false, + // the dataset.hidden value is ignored, else if null, the dataset hidden state is returned. + return typeof meta.hidden === 'boolean' ? !meta.hidden : !this.data.datasets[datasetIndex].hidden; + }, + + generateLegend: function() { + return this.options.legendCallback(this); + }, + + /** + * @private + */ + destroyDatasetMeta: function(datasetIndex) { + var id = this.id; + var dataset = this.data.datasets[datasetIndex]; + var meta = dataset._meta && dataset._meta[id]; + + if (meta) { + meta.controller.destroy(); + delete dataset._meta[id]; + } + }, + + destroy: function() { + var me = this; + var canvas = me.canvas; + var i, ilen; + + me.stop(); + + // dataset controllers need to cleanup associated data + for (i = 0, ilen = me.data.datasets.length; i < ilen; ++i) { + me.destroyDatasetMeta(i); + } + + if (canvas) { + me.unbindEvents(); + helpers.canvas.clear(me); + platform.releaseContext(me.ctx); + me.canvas = null; + me.ctx = null; + } + + plugins.notify(me, 'destroy'); + + delete Chart.instances[me.id]; + }, + + toBase64Image: function() { + return this.canvas.toDataURL.apply(this.canvas, arguments); + }, + + initToolTip: function() { + var me = this; + me.tooltip = new Tooltip({ + _chart: me, + _chartInstance: me, // deprecated, backward compatibility + _data: me.data, + _options: me.options.tooltips + }, me); + }, + + /** + * @private + */ + bindEvents: function() { + var me = this; + var listeners = me._listeners = {}; + var listener = function() { + me.eventHandler.apply(me, arguments); + }; + + helpers.each(me.options.events, function(type) { + platform.addEventListener(me, type, listener); + listeners[type] = listener; + }); + + // Elements used to detect size change should not be injected for non responsive charts. + // See https://github.com/chartjs/Chart.js/issues/2210 + if (me.options.responsive) { + listener = function() { + me.resize(); + }; + + platform.addEventListener(me, 'resize', listener); + listeners.resize = listener; + } + }, + + /** + * @private + */ + unbindEvents: function() { + var me = this; + var listeners = me._listeners; + if (!listeners) { + return; + } + + delete me._listeners; + helpers.each(listeners, function(listener, type) { + platform.removeEventListener(me, type, listener); + }); + }, + + updateHoverStyle: function(elements, mode, enabled) { + var method = enabled ? 'setHoverStyle' : 'removeHoverStyle'; + var element, i, ilen; + + for (i = 0, ilen = elements.length; i < ilen; ++i) { + element = elements[i]; + if (element) { + this.getDatasetMeta(element._datasetIndex).controller[method](element); + } + } + }, + + /** + * @private + */ + eventHandler: function(e) { + var me = this; + var tooltip = me.tooltip; + + if (plugins.notify(me, 'beforeEvent', [e]) === false) { + return; + } + + // Buffer any update calls so that renders do not occur + me._bufferedRender = true; + me._bufferedRequest = null; + + var changed = me.handleEvent(e); + // for smooth tooltip animations issue #4989 + // the tooltip should be the source of change + // Animation check workaround: + // tooltip._start will be null when tooltip isn't animating + if (tooltip) { + changed = tooltip._start + ? tooltip.handleEvent(e) + : changed | tooltip.handleEvent(e); + } + + plugins.notify(me, 'afterEvent', [e]); + + var bufferedRequest = me._bufferedRequest; + if (bufferedRequest) { + // If we have an update that was triggered, we need to do a normal render + me.render(bufferedRequest); + } else if (changed && !me.animating) { + // If entering, leaving, or changing elements, animate the change via pivot + me.stop(); + + // We only need to render at this point. Updating will cause scales to be + // recomputed generating flicker & using more memory than necessary. + me.render({ + duration: me.options.hover.animationDuration, + lazy: true + }); + } + + me._bufferedRender = false; + me._bufferedRequest = null; + + return me; + }, + + /** + * Handle an event + * @private + * @param {IEvent} event the event to handle + * @return {Boolean} true if the chart needs to re-render + */ + handleEvent: function(e) { + var me = this; + var options = me.options || {}; + var hoverOptions = options.hover; + var changed = false; + + me.lastActive = me.lastActive || []; + + // Find Active Elements for hover and tooltips + if (e.type === 'mouseout') { + me.active = []; + } else { + me.active = me.getElementsAtEventForMode(e, hoverOptions.mode, hoverOptions); + } + + // Invoke onHover hook + // Need to call with native event here to not break backwards compatibility + helpers.callback(options.onHover || options.hover.onHover, [e.native, me.active], me); + + if (e.type === 'mouseup' || e.type === 'click') { + if (options.onClick) { + // Use e.native here for backwards compatibility + options.onClick.call(me, e.native, me.active); + } + } + + // Remove styling for last active (even if it may still be active) + if (me.lastActive.length) { + me.updateHoverStyle(me.lastActive, hoverOptions.mode, false); + } + + // Built in hover styling + if (me.active.length && hoverOptions.mode) { + me.updateHoverStyle(me.active, hoverOptions.mode, true); + } + + changed = !helpers.arrayEquals(me.active, me.lastActive); + + // Remember Last Actives + me.lastActive = me.active; + + return changed; + } + }); + + /** + * Provided for backward compatibility, use Chart instead. + * @class Chart.Controller + * @deprecated since version 2.6.0 + * @todo remove at version 3 + * @private + */ + Chart.Controller = Chart; +}; + +},{"22":22,"23":23,"26":26,"29":29,"31":31,"32":32,"34":34,"36":36,"46":46,"49":49}],25:[function(require,module,exports){ +'use strict'; + +var helpers = require(46); + +module.exports = function(Chart) { + + var arrayEvents = ['push', 'pop', 'shift', 'splice', 'unshift']; + + /** + * Hooks the array methods that add or remove values ('push', pop', 'shift', 'splice', + * 'unshift') and notify the listener AFTER the array has been altered. Listeners are + * called on the 'onData*' callbacks (e.g. onDataPush, etc.) with same arguments. + */ + function listenArrayEvents(array, listener) { + if (array._chartjs) { + array._chartjs.listeners.push(listener); + return; + } + + Object.defineProperty(array, '_chartjs', { + configurable: true, + enumerable: false, + value: { + listeners: [listener] + } + }); + + arrayEvents.forEach(function(key) { + var method = 'onData' + key.charAt(0).toUpperCase() + key.slice(1); + var base = array[key]; + + Object.defineProperty(array, key, { + configurable: true, + enumerable: false, + value: function() { + var args = Array.prototype.slice.call(arguments); + var res = base.apply(this, args); + + helpers.each(array._chartjs.listeners, function(object) { + if (typeof object[method] === 'function') { + object[method].apply(object, args); + } + }); + + return res; + } + }); + }); + } + + /** + * Removes the given array event listener and cleanup extra attached properties (such as + * the _chartjs stub and overridden methods) if array doesn't have any more listeners. + */ + function unlistenArrayEvents(array, listener) { + var stub = array._chartjs; + if (!stub) { + return; + } + + var listeners = stub.listeners; + var index = listeners.indexOf(listener); + if (index !== -1) { + listeners.splice(index, 1); + } + + if (listeners.length > 0) { + return; + } + + arrayEvents.forEach(function(key) { + delete array[key]; + }); + + delete array._chartjs; + } + + // Base class for all dataset controllers (line, bar, etc) + Chart.DatasetController = function(chart, datasetIndex) { + this.initialize(chart, datasetIndex); + }; + + helpers.extend(Chart.DatasetController.prototype, { + + /** + * Element type used to generate a meta dataset (e.g. Chart.element.Line). + * @type {Chart.core.element} + */ + datasetElementType: null, + + /** + * Element type used to generate a meta data (e.g. Chart.element.Point). + * @type {Chart.core.element} + */ + dataElementType: null, + + initialize: function(chart, datasetIndex) { + var me = this; + me.chart = chart; + me.index = datasetIndex; + me.linkScales(); + me.addElements(); + }, + + updateIndex: function(datasetIndex) { + this.index = datasetIndex; + }, + + linkScales: function() { + var me = this; + var meta = me.getMeta(); + var dataset = me.getDataset(); + + if (meta.xAxisID === null || !(meta.xAxisID in me.chart.scales)) { + meta.xAxisID = dataset.xAxisID || me.chart.options.scales.xAxes[0].id; + } + if (meta.yAxisID === null || !(meta.yAxisID in me.chart.scales)) { + meta.yAxisID = dataset.yAxisID || me.chart.options.scales.yAxes[0].id; + } + }, + + getDataset: function() { + return this.chart.data.datasets[this.index]; + }, + + getMeta: function() { + return this.chart.getDatasetMeta(this.index); + }, + + getScaleForId: function(scaleID) { + return this.chart.scales[scaleID]; + }, + + reset: function() { + this.update(true); + }, + + /** + * @private + */ + destroy: function() { + if (this._data) { + unlistenArrayEvents(this._data, this); + } + }, + + createMetaDataset: function() { + var me = this; + var type = me.datasetElementType; + return type && new type({ + _chart: me.chart, + _datasetIndex: me.index + }); + }, + + createMetaData: function(index) { + var me = this; + var type = me.dataElementType; + return type && new type({ + _chart: me.chart, + _datasetIndex: me.index, + _index: index + }); + }, + + addElements: function() { + var me = this; + var meta = me.getMeta(); + var data = me.getDataset().data || []; + var metaData = meta.data; + var i, ilen; + + for (i = 0, ilen = data.length; i < ilen; ++i) { + metaData[i] = metaData[i] || me.createMetaData(i); + } + + meta.dataset = meta.dataset || me.createMetaDataset(); + }, + + addElementAndReset: function(index) { + var element = this.createMetaData(index); + this.getMeta().data.splice(index, 0, element); + this.updateElement(element, index, true); + }, + + buildOrUpdateElements: function() { + var me = this; + var dataset = me.getDataset(); + var data = dataset.data || (dataset.data = []); + + // In order to correctly handle data addition/deletion animation (an thus simulate + // real-time charts), we need to monitor these data modifications and synchronize + // the internal meta data accordingly. + if (me._data !== data) { + if (me._data) { + // This case happens when the user replaced the data array instance. + unlistenArrayEvents(me._data, me); + } + + listenArrayEvents(data, me); + me._data = data; + } + + // Re-sync meta data in case the user replaced the data array or if we missed + // any updates and so make sure that we handle number of datapoints changing. + me.resyncElements(); + }, + + update: helpers.noop, + + transition: function(easingValue) { + var meta = this.getMeta(); + var elements = meta.data || []; + var ilen = elements.length; + var i = 0; + + for (; i < ilen; ++i) { + elements[i].transition(easingValue); + } + + if (meta.dataset) { + meta.dataset.transition(easingValue); + } + }, + + draw: function() { + var meta = this.getMeta(); + var elements = meta.data || []; + var ilen = elements.length; + var i = 0; + + if (meta.dataset) { + meta.dataset.draw(); + } + + for (; i < ilen; ++i) { + elements[i].draw(); + } + }, + + removeHoverStyle: function(element) { + helpers.merge(element._model, element.$previousStyle || {}); + delete element.$previousStyle; + }, + + setHoverStyle: function(element) { + var dataset = this.chart.data.datasets[element._datasetIndex]; + var index = element._index; + var custom = element.custom || {}; + var valueOrDefault = helpers.valueAtIndexOrDefault; + var getHoverColor = helpers.getHoverColor; + var model = element._model; + + element.$previousStyle = { + backgroundColor: model.backgroundColor, + borderColor: model.borderColor, + borderWidth: model.borderWidth + }; + + model.backgroundColor = custom.hoverBackgroundColor ? custom.hoverBackgroundColor : valueOrDefault(dataset.hoverBackgroundColor, index, getHoverColor(model.backgroundColor)); + model.borderColor = custom.hoverBorderColor ? custom.hoverBorderColor : valueOrDefault(dataset.hoverBorderColor, index, getHoverColor(model.borderColor)); + model.borderWidth = custom.hoverBorderWidth ? custom.hoverBorderWidth : valueOrDefault(dataset.hoverBorderWidth, index, model.borderWidth); + }, + + /** + * @private + */ + resyncElements: function() { + var me = this; + var meta = me.getMeta(); + var data = me.getDataset().data; + var numMeta = meta.data.length; + var numData = data.length; + + if (numData < numMeta) { + meta.data.splice(numData, numMeta - numData); + } else if (numData > numMeta) { + me.insertElements(numMeta, numData - numMeta); + } + }, + + /** + * @private + */ + insertElements: function(start, count) { + for (var i = 0; i < count; ++i) { + this.addElementAndReset(start + i); + } + }, + + /** + * @private + */ + onDataPush: function() { + this.insertElements(this.getDataset().data.length - 1, arguments.length); + }, + + /** + * @private + */ + onDataPop: function() { + this.getMeta().data.pop(); + }, + + /** + * @private + */ + onDataShift: function() { + this.getMeta().data.shift(); + }, + + /** + * @private + */ + onDataSplice: function(start, count) { + this.getMeta().data.splice(start, count); + this.insertElements(start, arguments.length - 2); + }, + + /** + * @private + */ + onDataUnshift: function() { + this.insertElements(0, arguments.length); + } + }); + + Chart.DatasetController.extend = helpers.inherits; +}; + +},{"46":46}],26:[function(require,module,exports){ +'use strict'; + +var helpers = require(46); + +module.exports = { + /** + * @private + */ + _set: function(scope, values) { + return helpers.merge(this[scope] || (this[scope] = {}), values); + } +}; + +},{"46":46}],27:[function(require,module,exports){ +'use strict'; + +var color = require(3); +var helpers = require(46); + +function interpolate(start, view, model, ease) { + var keys = Object.keys(model); + var i, ilen, key, actual, origin, target, type, c0, c1; + + for (i = 0, ilen = keys.length; i < ilen; ++i) { + key = keys[i]; + + target = model[key]; + + // if a value is added to the model after pivot() has been called, the view + // doesn't contain it, so let's initialize the view to the target value. + if (!view.hasOwnProperty(key)) { + view[key] = target; + } + + actual = view[key]; + + if (actual === target || key[0] === '_') { + continue; + } + + if (!start.hasOwnProperty(key)) { + start[key] = actual; + } + + origin = start[key]; + + type = typeof target; + + if (type === typeof origin) { + if (type === 'string') { + c0 = color(origin); + if (c0.valid) { + c1 = color(target); + if (c1.valid) { + view[key] = c1.mix(c0, ease).rgbString(); + continue; + } + } + } else if (type === 'number' && isFinite(origin) && isFinite(target)) { + view[key] = origin + (target - origin) * ease; + continue; + } + } + + view[key] = target; + } +} + +var Element = function(configuration) { + helpers.extend(this, configuration); + this.initialize.apply(this, arguments); +}; + +helpers.extend(Element.prototype, { + + initialize: function() { + this.hidden = false; + }, + + pivot: function() { + var me = this; + if (!me._view) { + me._view = helpers.clone(me._model); + } + me._start = {}; + return me; + }, + + transition: function(ease) { + var me = this; + var model = me._model; + var start = me._start; + var view = me._view; + + // No animation -> No Transition + if (!model || ease === 1) { + me._view = model; + me._start = null; + return me; + } + + if (!view) { + view = me._view = {}; + } + + if (!start) { + start = me._start = {}; + } + + interpolate(start, view, model, ease); + + return me; + }, + + tooltipPosition: function() { + return { + x: this._model.x, + y: this._model.y + }; + }, + + hasValue: function() { + return helpers.isNumber(this._model.x) && helpers.isNumber(this._model.y); + } +}); + +Element.extend = helpers.inherits; + +module.exports = Element; + +},{"3":3,"46":46}],28:[function(require,module,exports){ +/* global window: false */ +/* global document: false */ +'use strict'; + +var color = require(3); +var defaults = require(26); +var helpers = require(46); +var scaleService = require(34); + +module.exports = function() { + + // -- Basic js utility methods + + helpers.configMerge = function(/* objects ... */) { + return helpers.merge(helpers.clone(arguments[0]), [].slice.call(arguments, 1), { + merger: function(key, target, source, options) { + var tval = target[key] || {}; + var sval = source[key]; + + if (key === 'scales') { + // scale config merging is complex. Add our own function here for that + target[key] = helpers.scaleMerge(tval, sval); + } else if (key === 'scale') { + // used in polar area & radar charts since there is only one scale + target[key] = helpers.merge(tval, [scaleService.getScaleDefaults(sval.type), sval]); + } else { + helpers._merger(key, target, source, options); + } + } + }); + }; + + helpers.scaleMerge = function(/* objects ... */) { + return helpers.merge(helpers.clone(arguments[0]), [].slice.call(arguments, 1), { + merger: function(key, target, source, options) { + if (key === 'xAxes' || key === 'yAxes') { + var slen = source[key].length; + var i, type, scale; + + if (!target[key]) { + target[key] = []; + } + + for (i = 0; i < slen; ++i) { + scale = source[key][i]; + type = helpers.valueOrDefault(scale.type, key === 'xAxes' ? 'category' : 'linear'); + + if (i >= target[key].length) { + target[key].push({}); + } + + if (!target[key][i].type || (scale.type && scale.type !== target[key][i].type)) { + // new/untyped scale or type changed: let's apply the new defaults + // then merge source scale to correctly overwrite the defaults. + helpers.merge(target[key][i], [scaleService.getScaleDefaults(type), scale]); + } else { + // scales type are the same + helpers.merge(target[key][i], scale); + } + } + } else { + helpers._merger(key, target, source, options); + } + } + }); + }; + + helpers.where = function(collection, filterCallback) { + if (helpers.isArray(collection) && Array.prototype.filter) { + return collection.filter(filterCallback); + } + var filtered = []; + + helpers.each(collection, function(item) { + if (filterCallback(item)) { + filtered.push(item); + } + }); + + return filtered; + }; + helpers.findIndex = Array.prototype.findIndex ? + function(array, callback, scope) { + return array.findIndex(callback, scope); + } : + function(array, callback, scope) { + scope = scope === undefined ? array : scope; + for (var i = 0, ilen = array.length; i < ilen; ++i) { + if (callback.call(scope, array[i], i, array)) { + return i; + } + } + return -1; + }; + helpers.findNextWhere = function(arrayToSearch, filterCallback, startIndex) { + // Default to start of the array + if (helpers.isNullOrUndef(startIndex)) { + startIndex = -1; + } + for (var i = startIndex + 1; i < arrayToSearch.length; i++) { + var currentItem = arrayToSearch[i]; + if (filterCallback(currentItem)) { + return currentItem; + } + } + }; + helpers.findPreviousWhere = function(arrayToSearch, filterCallback, startIndex) { + // Default to end of the array + if (helpers.isNullOrUndef(startIndex)) { + startIndex = arrayToSearch.length; + } + for (var i = startIndex - 1; i >= 0; i--) { + var currentItem = arrayToSearch[i]; + if (filterCallback(currentItem)) { + return currentItem; + } + } + }; + + // -- Math methods + helpers.isNumber = function(n) { + return !isNaN(parseFloat(n)) && isFinite(n); + }; + helpers.almostEquals = function(x, y, epsilon) { + return Math.abs(x - y) < epsilon; + }; + helpers.almostWhole = function(x, epsilon) { + var rounded = Math.round(x); + return (((rounded - epsilon) < x) && ((rounded + epsilon) > x)); + }; + helpers.max = function(array) { + return array.reduce(function(max, value) { + if (!isNaN(value)) { + return Math.max(max, value); + } + return max; + }, Number.NEGATIVE_INFINITY); + }; + helpers.min = function(array) { + return array.reduce(function(min, value) { + if (!isNaN(value)) { + return Math.min(min, value); + } + return min; + }, Number.POSITIVE_INFINITY); + }; + helpers.sign = Math.sign ? + function(x) { + return Math.sign(x); + } : + function(x) { + x = +x; // convert to a number + if (x === 0 || isNaN(x)) { + return x; + } + return x > 0 ? 1 : -1; + }; + helpers.log10 = Math.log10 ? + function(x) { + return Math.log10(x); + } : + function(x) { + var exponent = Math.log(x) * Math.LOG10E; // Math.LOG10E = 1 / Math.LN10. + // Check for whole powers of 10, + // which due to floating point rounding error should be corrected. + var powerOf10 = Math.round(exponent); + var isPowerOf10 = x === Math.pow(10, powerOf10); + + return isPowerOf10 ? powerOf10 : exponent; + }; + helpers.toRadians = function(degrees) { + return degrees * (Math.PI / 180); + }; + helpers.toDegrees = function(radians) { + return radians * (180 / Math.PI); + }; + // Gets the angle from vertical upright to the point about a centre. + helpers.getAngleFromPoint = function(centrePoint, anglePoint) { + var distanceFromXCenter = anglePoint.x - centrePoint.x; + var distanceFromYCenter = anglePoint.y - centrePoint.y; + var radialDistanceFromCenter = Math.sqrt(distanceFromXCenter * distanceFromXCenter + distanceFromYCenter * distanceFromYCenter); + + var angle = Math.atan2(distanceFromYCenter, distanceFromXCenter); + + if (angle < (-0.5 * Math.PI)) { + angle += 2.0 * Math.PI; // make sure the returned angle is in the range of (-PI/2, 3PI/2] + } + + return { + angle: angle, + distance: radialDistanceFromCenter + }; + }; + helpers.distanceBetweenPoints = function(pt1, pt2) { + return Math.sqrt(Math.pow(pt2.x - pt1.x, 2) + Math.pow(pt2.y - pt1.y, 2)); + }; + helpers.aliasPixel = function(pixelWidth) { + return (pixelWidth % 2 === 0) ? 0 : 0.5; + }; + helpers.splineCurve = function(firstPoint, middlePoint, afterPoint, t) { + // Props to Rob Spencer at scaled innovation for his post on splining between points + // http://scaledinnovation.com/analytics/splines/aboutSplines.html + + // This function must also respect "skipped" points + + var previous = firstPoint.skip ? middlePoint : firstPoint; + var current = middlePoint; + var next = afterPoint.skip ? middlePoint : afterPoint; + + var d01 = Math.sqrt(Math.pow(current.x - previous.x, 2) + Math.pow(current.y - previous.y, 2)); + var d12 = Math.sqrt(Math.pow(next.x - current.x, 2) + Math.pow(next.y - current.y, 2)); + + var s01 = d01 / (d01 + d12); + var s12 = d12 / (d01 + d12); + + // If all points are the same, s01 & s02 will be inf + s01 = isNaN(s01) ? 0 : s01; + s12 = isNaN(s12) ? 0 : s12; + + var fa = t * s01; // scaling factor for triangle Ta + var fb = t * s12; + + return { + previous: { + x: current.x - fa * (next.x - previous.x), + y: current.y - fa * (next.y - previous.y) + }, + next: { + x: current.x + fb * (next.x - previous.x), + y: current.y + fb * (next.y - previous.y) + } + }; + }; + helpers.EPSILON = Number.EPSILON || 1e-14; + helpers.splineCurveMonotone = function(points) { + // This function calculates BĂŠzier control points in a similar way than |splineCurve|, + // but preserves monotonicity of the provided data and ensures no local extremums are added + // between the dataset discrete points due to the interpolation. + // See : https://en.wikipedia.org/wiki/Monotone_cubic_interpolation + + var pointsWithTangents = (points || []).map(function(point) { + return { + model: point._model, + deltaK: 0, + mK: 0 + }; + }); + + // Calculate slopes (deltaK) and initialize tangents (mK) + var pointsLen = pointsWithTangents.length; + var i, pointBefore, pointCurrent, pointAfter; + for (i = 0; i < pointsLen; ++i) { + pointCurrent = pointsWithTangents[i]; + if (pointCurrent.model.skip) { + continue; + } + + pointBefore = i > 0 ? pointsWithTangents[i - 1] : null; + pointAfter = i < pointsLen - 1 ? pointsWithTangents[i + 1] : null; + if (pointAfter && !pointAfter.model.skip) { + var slopeDeltaX = (pointAfter.model.x - pointCurrent.model.x); + + // In the case of two points that appear at the same x pixel, slopeDeltaX is 0 + pointCurrent.deltaK = slopeDeltaX !== 0 ? (pointAfter.model.y - pointCurrent.model.y) / slopeDeltaX : 0; + } + + if (!pointBefore || pointBefore.model.skip) { + pointCurrent.mK = pointCurrent.deltaK; + } else if (!pointAfter || pointAfter.model.skip) { + pointCurrent.mK = pointBefore.deltaK; + } else if (this.sign(pointBefore.deltaK) !== this.sign(pointCurrent.deltaK)) { + pointCurrent.mK = 0; + } else { + pointCurrent.mK = (pointBefore.deltaK + pointCurrent.deltaK) / 2; + } + } + + // Adjust tangents to ensure monotonic properties + var alphaK, betaK, tauK, squaredMagnitude; + for (i = 0; i < pointsLen - 1; ++i) { + pointCurrent = pointsWithTangents[i]; + pointAfter = pointsWithTangents[i + 1]; + if (pointCurrent.model.skip || pointAfter.model.skip) { + continue; + } + + if (helpers.almostEquals(pointCurrent.deltaK, 0, this.EPSILON)) { + pointCurrent.mK = pointAfter.mK = 0; + continue; + } + + alphaK = pointCurrent.mK / pointCurrent.deltaK; + betaK = pointAfter.mK / pointCurrent.deltaK; + squaredMagnitude = Math.pow(alphaK, 2) + Math.pow(betaK, 2); + if (squaredMagnitude <= 9) { + continue; + } + + tauK = 3 / Math.sqrt(squaredMagnitude); + pointCurrent.mK = alphaK * tauK * pointCurrent.deltaK; + pointAfter.mK = betaK * tauK * pointCurrent.deltaK; + } + + // Compute control points + var deltaX; + for (i = 0; i < pointsLen; ++i) { + pointCurrent = pointsWithTangents[i]; + if (pointCurrent.model.skip) { + continue; + } + + pointBefore = i > 0 ? pointsWithTangents[i - 1] : null; + pointAfter = i < pointsLen - 1 ? pointsWithTangents[i + 1] : null; + if (pointBefore && !pointBefore.model.skip) { + deltaX = (pointCurrent.model.x - pointBefore.model.x) / 3; + pointCurrent.model.controlPointPreviousX = pointCurrent.model.x - deltaX; + pointCurrent.model.controlPointPreviousY = pointCurrent.model.y - deltaX * pointCurrent.mK; + } + if (pointAfter && !pointAfter.model.skip) { + deltaX = (pointAfter.model.x - pointCurrent.model.x) / 3; + pointCurrent.model.controlPointNextX = pointCurrent.model.x + deltaX; + pointCurrent.model.controlPointNextY = pointCurrent.model.y + deltaX * pointCurrent.mK; + } + } + }; + helpers.nextItem = function(collection, index, loop) { + if (loop) { + return index >= collection.length - 1 ? collection[0] : collection[index + 1]; + } + return index >= collection.length - 1 ? collection[collection.length - 1] : collection[index + 1]; + }; + helpers.previousItem = function(collection, index, loop) { + if (loop) { + return index <= 0 ? collection[collection.length - 1] : collection[index - 1]; + } + return index <= 0 ? collection[0] : collection[index - 1]; + }; + // Implementation of the nice number algorithm used in determining where axis labels will go + helpers.niceNum = function(range, round) { + var exponent = Math.floor(helpers.log10(range)); + var fraction = range / Math.pow(10, exponent); + var niceFraction; + + if (round) { + if (fraction < 1.5) { + niceFraction = 1; + } else if (fraction < 3) { + niceFraction = 2; + } else if (fraction < 7) { + niceFraction = 5; + } else { + niceFraction = 10; + } + } else if (fraction <= 1.0) { + niceFraction = 1; + } else if (fraction <= 2) { + niceFraction = 2; + } else if (fraction <= 5) { + niceFraction = 5; + } else { + niceFraction = 10; + } + + return niceFraction * Math.pow(10, exponent); + }; + // Request animation polyfill - http://www.paulirish.com/2011/requestanimationframe-for-smart-animating/ + helpers.requestAnimFrame = (function() { + if (typeof window === 'undefined') { + return function(callback) { + callback(); + }; + } + return window.requestAnimationFrame || + window.webkitRequestAnimationFrame || + window.mozRequestAnimationFrame || + window.oRequestAnimationFrame || + window.msRequestAnimationFrame || + function(callback) { + return window.setTimeout(callback, 1000 / 60); + }; + }()); + // -- DOM methods + helpers.getRelativePosition = function(evt, chart) { + var mouseX, mouseY; + var e = evt.originalEvent || evt; + var canvas = evt.target || evt.srcElement; + var boundingRect = canvas.getBoundingClientRect(); + + var touches = e.touches; + if (touches && touches.length > 0) { + mouseX = touches[0].clientX; + mouseY = touches[0].clientY; + + } else { + mouseX = e.clientX; + mouseY = e.clientY; + } + + // Scale mouse coordinates into canvas coordinates + // by following the pattern laid out by 'jerryj' in the comments of + // http://www.html5canvastutorials.com/advanced/html5-canvas-mouse-coordinates/ + var paddingLeft = parseFloat(helpers.getStyle(canvas, 'padding-left')); + var paddingTop = parseFloat(helpers.getStyle(canvas, 'padding-top')); + var paddingRight = parseFloat(helpers.getStyle(canvas, 'padding-right')); + var paddingBottom = parseFloat(helpers.getStyle(canvas, 'padding-bottom')); + var width = boundingRect.right - boundingRect.left - paddingLeft - paddingRight; + var height = boundingRect.bottom - boundingRect.top - paddingTop - paddingBottom; + + // We divide by the current device pixel ratio, because the canvas is scaled up by that amount in each direction. However + // the backend model is in unscaled coordinates. Since we are going to deal with our model coordinates, we go back here + mouseX = Math.round((mouseX - boundingRect.left - paddingLeft) / (width) * canvas.width / chart.currentDevicePixelRatio); + mouseY = Math.round((mouseY - boundingRect.top - paddingTop) / (height) * canvas.height / chart.currentDevicePixelRatio); + + return { + x: mouseX, + y: mouseY + }; + + }; + + // Private helper function to convert max-width/max-height values that may be percentages into a number + function parseMaxStyle(styleValue, node, parentProperty) { + var valueInPixels; + if (typeof styleValue === 'string') { + valueInPixels = parseInt(styleValue, 10); + + if (styleValue.indexOf('%') !== -1) { + // percentage * size in dimension + valueInPixels = valueInPixels / 100 * node.parentNode[parentProperty]; + } + } else { + valueInPixels = styleValue; + } + + return valueInPixels; + } + + /** + * Returns if the given value contains an effective constraint. + * @private + */ + function isConstrainedValue(value) { + return value !== undefined && value !== null && value !== 'none'; + } + + // Private helper to get a constraint dimension + // @param domNode : the node to check the constraint on + // @param maxStyle : the style that defines the maximum for the direction we are using (maxWidth / maxHeight) + // @param percentageProperty : property of parent to use when calculating width as a percentage + // @see http://www.nathanaeljones.com/blog/2013/reading-max-width-cross-browser + function getConstraintDimension(domNode, maxStyle, percentageProperty) { + var view = document.defaultView; + var parentNode = helpers._getParentNode(domNode); + var constrainedNode = view.getComputedStyle(domNode)[maxStyle]; + var constrainedContainer = view.getComputedStyle(parentNode)[maxStyle]; + var hasCNode = isConstrainedValue(constrainedNode); + var hasCContainer = isConstrainedValue(constrainedContainer); + var infinity = Number.POSITIVE_INFINITY; + + if (hasCNode || hasCContainer) { + return Math.min( + hasCNode ? parseMaxStyle(constrainedNode, domNode, percentageProperty) : infinity, + hasCContainer ? parseMaxStyle(constrainedContainer, parentNode, percentageProperty) : infinity); + } + + return 'none'; + } + // returns Number or undefined if no constraint + helpers.getConstraintWidth = function(domNode) { + return getConstraintDimension(domNode, 'max-width', 'clientWidth'); + }; + // returns Number or undefined if no constraint + helpers.getConstraintHeight = function(domNode) { + return getConstraintDimension(domNode, 'max-height', 'clientHeight'); + }; + /** + * @private + */ + helpers._calculatePadding = function(container, padding, parentDimension) { + padding = helpers.getStyle(container, padding); + + return padding.indexOf('%') > -1 ? parentDimension / parseInt(padding, 10) : parseInt(padding, 10); + }; + /** + * @private + */ + helpers._getParentNode = function(domNode) { + var parent = domNode.parentNode; + if (parent && parent.host) { + parent = parent.host; + } + return parent; + }; + helpers.getMaximumWidth = function(domNode) { + var container = helpers._getParentNode(domNode); + if (!container) { + return domNode.clientWidth; + } + + var clientWidth = container.clientWidth; + var paddingLeft = helpers._calculatePadding(container, 'padding-left', clientWidth); + var paddingRight = helpers._calculatePadding(container, 'padding-right', clientWidth); + + var w = clientWidth - paddingLeft - paddingRight; + var cw = helpers.getConstraintWidth(domNode); + return isNaN(cw) ? w : Math.min(w, cw); + }; + helpers.getMaximumHeight = function(domNode) { + var container = helpers._getParentNode(domNode); + if (!container) { + return domNode.clientHeight; + } + + var clientHeight = container.clientHeight; + var paddingTop = helpers._calculatePadding(container, 'padding-top', clientHeight); + var paddingBottom = helpers._calculatePadding(container, 'padding-bottom', clientHeight); + + var h = clientHeight - paddingTop - paddingBottom; + var ch = helpers.getConstraintHeight(domNode); + return isNaN(ch) ? h : Math.min(h, ch); + }; + helpers.getStyle = function(el, property) { + return el.currentStyle ? + el.currentStyle[property] : + document.defaultView.getComputedStyle(el, null).getPropertyValue(property); + }; + helpers.retinaScale = function(chart, forceRatio) { + var pixelRatio = chart.currentDevicePixelRatio = forceRatio || (typeof window !== 'undefined' && window.devicePixelRatio) || 1; + if (pixelRatio === 1) { + return; + } + + var canvas = chart.canvas; + var height = chart.height; + var width = chart.width; + + canvas.height = height * pixelRatio; + canvas.width = width * pixelRatio; + chart.ctx.scale(pixelRatio, pixelRatio); + + // If no style has been set on the canvas, the render size is used as display size, + // making the chart visually bigger, so let's enforce it to the "correct" values. + // See https://github.com/chartjs/Chart.js/issues/3575 + if (!canvas.style.height && !canvas.style.width) { + canvas.style.height = height + 'px'; + canvas.style.width = width + 'px'; + } + }; + // -- Canvas methods + helpers.fontString = function(pixelSize, fontStyle, fontFamily) { + return fontStyle + ' ' + pixelSize + 'px ' + fontFamily; + }; + helpers.longestText = function(ctx, font, arrayOfThings, cache) { + cache = cache || {}; + var data = cache.data = cache.data || {}; + var gc = cache.garbageCollect = cache.garbageCollect || []; + + if (cache.font !== font) { + data = cache.data = {}; + gc = cache.garbageCollect = []; + cache.font = font; + } + + ctx.font = font; + var longest = 0; + helpers.each(arrayOfThings, function(thing) { + // Undefined strings and arrays should not be measured + if (thing !== undefined && thing !== null && helpers.isArray(thing) !== true) { + longest = helpers.measureText(ctx, data, gc, longest, thing); + } else if (helpers.isArray(thing)) { + // if it is an array lets measure each element + // to do maybe simplify this function a bit so we can do this more recursively? + helpers.each(thing, function(nestedThing) { + // Undefined strings and arrays should not be measured + if (nestedThing !== undefined && nestedThing !== null && !helpers.isArray(nestedThing)) { + longest = helpers.measureText(ctx, data, gc, longest, nestedThing); + } + }); + } + }); + + var gcLen = gc.length / 2; + if (gcLen > arrayOfThings.length) { + for (var i = 0; i < gcLen; i++) { + delete data[gc[i]]; + } + gc.splice(0, gcLen); + } + return longest; + }; + helpers.measureText = function(ctx, data, gc, longest, string) { + var textWidth = data[string]; + if (!textWidth) { + textWidth = data[string] = ctx.measureText(string).width; + gc.push(string); + } + if (textWidth > longest) { + longest = textWidth; + } + return longest; + }; + helpers.numberOfLabelLines = function(arrayOfThings) { + var numberOfLines = 1; + helpers.each(arrayOfThings, function(thing) { + if (helpers.isArray(thing)) { + if (thing.length > numberOfLines) { + numberOfLines = thing.length; + } + } + }); + return numberOfLines; + }; + + helpers.color = !color ? + function(value) { + console.error('Color.js not found!'); + return value; + } : + function(value) { + /* global CanvasGradient */ + if (value instanceof CanvasGradient) { + value = defaults.global.defaultColor; + } + + return color(value); + }; + + helpers.getHoverColor = function(colorValue) { + /* global CanvasPattern */ + return (colorValue instanceof CanvasPattern) ? + colorValue : + helpers.color(colorValue).saturate(0.5).darken(0.1).rgbString(); + }; +}; + +},{"26":26,"3":3,"34":34,"46":46}],29:[function(require,module,exports){ +'use strict'; + +var helpers = require(46); + +/** + * Helper function to get relative position for an event + * @param {Event|IEvent} event - The event to get the position for + * @param {Chart} chart - The chart + * @returns {Point} the event position + */ +function getRelativePosition(e, chart) { + if (e.native) { + return { + x: e.x, + y: e.y + }; + } + + return helpers.getRelativePosition(e, chart); +} + +/** + * Helper function to traverse all of the visible elements in the chart + * @param chart {chart} the chart + * @param handler {Function} the callback to execute for each visible item + */ +function parseVisibleItems(chart, handler) { + var datasets = chart.data.datasets; + var meta, i, j, ilen, jlen; + + for (i = 0, ilen = datasets.length; i < ilen; ++i) { + if (!chart.isDatasetVisible(i)) { + continue; + } + + meta = chart.getDatasetMeta(i); + for (j = 0, jlen = meta.data.length; j < jlen; ++j) { + var element = meta.data[j]; + if (!element._view.skip) { + handler(element); + } + } + } +} + +/** + * Helper function to get the items that intersect the event position + * @param items {ChartElement[]} elements to filter + * @param position {Point} the point to be nearest to + * @return {ChartElement[]} the nearest items + */ +function getIntersectItems(chart, position) { + var elements = []; + + parseVisibleItems(chart, function(element) { + if (element.inRange(position.x, position.y)) { + elements.push(element); + } + }); + + return elements; +} + +/** + * Helper function to get the items nearest to the event position considering all visible items in teh chart + * @param chart {Chart} the chart to look at elements from + * @param position {Point} the point to be nearest to + * @param intersect {Boolean} if true, only consider items that intersect the position + * @param distanceMetric {Function} function to provide the distance between points + * @return {ChartElement[]} the nearest items + */ +function getNearestItems(chart, position, intersect, distanceMetric) { + var minDistance = Number.POSITIVE_INFINITY; + var nearestItems = []; + + parseVisibleItems(chart, function(element) { + if (intersect && !element.inRange(position.x, position.y)) { + return; + } + + var center = element.getCenterPoint(); + var distance = distanceMetric(position, center); + + if (distance < minDistance) { + nearestItems = [element]; + minDistance = distance; + } else if (distance === minDistance) { + // Can have multiple items at the same distance in which case we sort by size + nearestItems.push(element); + } + }); + + return nearestItems; +} + +/** + * Get a distance metric function for two points based on the + * axis mode setting + * @param {String} axis the axis mode. x|y|xy + */ +function getDistanceMetricForAxis(axis) { + var useX = axis.indexOf('x') !== -1; + var useY = axis.indexOf('y') !== -1; + + return function(pt1, pt2) { + var deltaX = useX ? Math.abs(pt1.x - pt2.x) : 0; + var deltaY = useY ? Math.abs(pt1.y - pt2.y) : 0; + return Math.sqrt(Math.pow(deltaX, 2) + Math.pow(deltaY, 2)); + }; +} + +function indexMode(chart, e, options) { + var position = getRelativePosition(e, chart); + // Default axis for index mode is 'x' to match old behaviour + options.axis = options.axis || 'x'; + var distanceMetric = getDistanceMetricForAxis(options.axis); + var items = options.intersect ? getIntersectItems(chart, position) : getNearestItems(chart, position, false, distanceMetric); + var elements = []; + + if (!items.length) { + return []; + } + + chart.data.datasets.forEach(function(dataset, datasetIndex) { + if (chart.isDatasetVisible(datasetIndex)) { + var meta = chart.getDatasetMeta(datasetIndex); + var element = meta.data[items[0]._index]; + + // don't count items that are skipped (null data) + if (element && !element._view.skip) { + elements.push(element); + } + } + }); + + return elements; +} + +/** + * @interface IInteractionOptions + */ +/** + * If true, only consider items that intersect the point + * @name IInterfaceOptions#boolean + * @type Boolean + */ + +/** + * Contains interaction related functions + * @namespace Chart.Interaction + */ +module.exports = { + // Helper function for different modes + modes: { + single: function(chart, e) { + var position = getRelativePosition(e, chart); + var elements = []; + + parseVisibleItems(chart, function(element) { + if (element.inRange(position.x, position.y)) { + elements.push(element); + return elements; + } + }); + + return elements.slice(0, 1); + }, + + /** + * @function Chart.Interaction.modes.label + * @deprecated since version 2.4.0 + * @todo remove at version 3 + * @private + */ + label: indexMode, + + /** + * Returns items at the same index. If the options.intersect parameter is true, we only return items if we intersect something + * If the options.intersect mode is false, we find the nearest item and return the items at the same index as that item + * @function Chart.Interaction.modes.index + * @since v2.4.0 + * @param chart {chart} the chart we are returning items from + * @param e {Event} the event we are find things at + * @param options {IInteractionOptions} options to use during interaction + * @return {Chart.Element[]} Array of elements that are under the point. If none are found, an empty array is returned + */ + index: indexMode, + + /** + * Returns items in the same dataset. If the options.intersect parameter is true, we only return items if we intersect something + * If the options.intersect is false, we find the nearest item and return the items in that dataset + * @function Chart.Interaction.modes.dataset + * @param chart {chart} the chart we are returning items from + * @param e {Event} the event we are find things at + * @param options {IInteractionOptions} options to use during interaction + * @return {Chart.Element[]} Array of elements that are under the point. If none are found, an empty array is returned + */ + dataset: function(chart, e, options) { + var position = getRelativePosition(e, chart); + options.axis = options.axis || 'xy'; + var distanceMetric = getDistanceMetricForAxis(options.axis); + var items = options.intersect ? getIntersectItems(chart, position) : getNearestItems(chart, position, false, distanceMetric); + + if (items.length > 0) { + items = chart.getDatasetMeta(items[0]._datasetIndex).data; + } + + return items; + }, + + /** + * @function Chart.Interaction.modes.x-axis + * @deprecated since version 2.4.0. Use index mode and intersect == true + * @todo remove at version 3 + * @private + */ + 'x-axis': function(chart, e) { + return indexMode(chart, e, {intersect: false}); + }, + + /** + * Point mode returns all elements that hit test based on the event position + * of the event + * @function Chart.Interaction.modes.intersect + * @param chart {chart} the chart we are returning items from + * @param e {Event} the event we are find things at + * @return {Chart.Element[]} Array of elements that are under the point. If none are found, an empty array is returned + */ + point: function(chart, e) { + var position = getRelativePosition(e, chart); + return getIntersectItems(chart, position); + }, + + /** + * nearest mode returns the element closest to the point + * @function Chart.Interaction.modes.intersect + * @param chart {chart} the chart we are returning items from + * @param e {Event} the event we are find things at + * @param options {IInteractionOptions} options to use + * @return {Chart.Element[]} Array of elements that are under the point. If none are found, an empty array is returned + */ + nearest: function(chart, e, options) { + var position = getRelativePosition(e, chart); + options.axis = options.axis || 'xy'; + var distanceMetric = getDistanceMetricForAxis(options.axis); + var nearestItems = getNearestItems(chart, position, options.intersect, distanceMetric); + + // We have multiple items at the same distance from the event. Now sort by smallest + if (nearestItems.length > 1) { + nearestItems.sort(function(a, b) { + var sizeA = a.getArea(); + var sizeB = b.getArea(); + var ret = sizeA - sizeB; + + if (ret === 0) { + // if equal sort by dataset index + ret = a._datasetIndex - b._datasetIndex; + } + + return ret; + }); + } + + // Return only 1 item + return nearestItems.slice(0, 1); + }, + + /** + * x mode returns the elements that hit-test at the current x coordinate + * @function Chart.Interaction.modes.x + * @param chart {chart} the chart we are returning items from + * @param e {Event} the event we are find things at + * @param options {IInteractionOptions} options to use + * @return {Chart.Element[]} Array of elements that are under the point. If none are found, an empty array is returned + */ + x: function(chart, e, options) { + var position = getRelativePosition(e, chart); + var items = []; + var intersectsItem = false; + + parseVisibleItems(chart, function(element) { + if (element.inXRange(position.x)) { + items.push(element); + } + + if (element.inRange(position.x, position.y)) { + intersectsItem = true; + } + }); + + // If we want to trigger on an intersect and we don't have any items + // that intersect the position, return nothing + if (options.intersect && !intersectsItem) { + items = []; + } + return items; + }, + + /** + * y mode returns the elements that hit-test at the current y coordinate + * @function Chart.Interaction.modes.y + * @param chart {chart} the chart we are returning items from + * @param e {Event} the event we are find things at + * @param options {IInteractionOptions} options to use + * @return {Chart.Element[]} Array of elements that are under the point. If none are found, an empty array is returned + */ + y: function(chart, e, options) { + var position = getRelativePosition(e, chart); + var items = []; + var intersectsItem = false; + + parseVisibleItems(chart, function(element) { + if (element.inYRange(position.y)) { + items.push(element); + } + + if (element.inRange(position.x, position.y)) { + intersectsItem = true; + } + }); + + // If we want to trigger on an intersect and we don't have any items + // that intersect the position, return nothing + if (options.intersect && !intersectsItem) { + items = []; + } + return items; + } + } +}; + +},{"46":46}],30:[function(require,module,exports){ +'use strict'; + +var defaults = require(26); + +defaults._set('global', { + responsive: true, + responsiveAnimationDuration: 0, + maintainAspectRatio: true, + events: ['mousemove', 'mouseout', 'click', 'touchstart', 'touchmove'], + hover: { + onHover: null, + mode: 'nearest', + intersect: true, + animationDuration: 400 + }, + onClick: null, + defaultColor: 'rgba(0,0,0,0.1)', + defaultFontColor: '#666', + defaultFontFamily: "'Helvetica Neue', 'Helvetica', 'Arial', sans-serif", + defaultFontSize: 12, + defaultFontStyle: 'normal', + showLines: true, + + // Element defaults defined in element extensions + elements: {}, + + // Layout options such as padding + layout: { + padding: { + top: 0, + right: 0, + bottom: 0, + left: 0 + } + } +}); + +module.exports = function() { + + // Occupy the global variable of Chart, and create a simple base class + var Chart = function(item, config) { + this.construct(item, config); + return this; + }; + + Chart.Chart = Chart; + + return Chart; +}; + +},{"26":26}],31:[function(require,module,exports){ +'use strict'; + +var helpers = require(46); + +function filterByPosition(array, position) { + return helpers.where(array, function(v) { + return v.position === position; + }); +} + +function sortByWeight(array, reverse) { + array.forEach(function(v, i) { + v._tmpIndex_ = i; + return v; + }); + array.sort(function(a, b) { + var v0 = reverse ? b : a; + var v1 = reverse ? a : b; + return v0.weight === v1.weight ? + v0._tmpIndex_ - v1._tmpIndex_ : + v0.weight - v1.weight; + }); + array.forEach(function(v) { + delete v._tmpIndex_; + }); +} + +/** + * @interface ILayoutItem + * @prop {String} position - The position of the item in the chart layout. Possible values are + * 'left', 'top', 'right', 'bottom', and 'chartArea' + * @prop {Number} weight - The weight used to sort the item. Higher weights are further away from the chart area + * @prop {Boolean} fullWidth - if true, and the item is horizontal, then push vertical boxes down + * @prop {Function} isHorizontal - returns true if the layout item is horizontal (ie. top or bottom) + * @prop {Function} update - Takes two parameters: width and height. Returns size of item + * @prop {Function} getPadding - Returns an object with padding on the edges + * @prop {Number} width - Width of item. Must be valid after update() + * @prop {Number} height - Height of item. Must be valid after update() + * @prop {Number} left - Left edge of the item. Set by layout system and cannot be used in update + * @prop {Number} top - Top edge of the item. Set by layout system and cannot be used in update + * @prop {Number} right - Right edge of the item. Set by layout system and cannot be used in update + * @prop {Number} bottom - Bottom edge of the item. Set by layout system and cannot be used in update + */ + +// The layout service is very self explanatory. It's responsible for the layout within a chart. +// Scales, Legends and Plugins all rely on the layout service and can easily register to be placed anywhere they need +// It is this service's responsibility of carrying out that layout. +module.exports = { + defaults: {}, + + /** + * Register a box to a chart. + * A box is simply a reference to an object that requires layout. eg. Scales, Legend, Title. + * @param {Chart} chart - the chart to use + * @param {ILayoutItem} item - the item to add to be layed out + */ + addBox: function(chart, item) { + if (!chart.boxes) { + chart.boxes = []; + } + + // initialize item with default values + item.fullWidth = item.fullWidth || false; + item.position = item.position || 'top'; + item.weight = item.weight || 0; + + chart.boxes.push(item); + }, + + /** + * Remove a layoutItem from a chart + * @param {Chart} chart - the chart to remove the box from + * @param {Object} layoutItem - the item to remove from the layout + */ + removeBox: function(chart, layoutItem) { + var index = chart.boxes ? chart.boxes.indexOf(layoutItem) : -1; + if (index !== -1) { + chart.boxes.splice(index, 1); + } + }, + + /** + * Sets (or updates) options on the given `item`. + * @param {Chart} chart - the chart in which the item lives (or will be added to) + * @param {Object} item - the item to configure with the given options + * @param {Object} options - the new item options. + */ + configure: function(chart, item, options) { + var props = ['fullWidth', 'position', 'weight']; + var ilen = props.length; + var i = 0; + var prop; + + for (; i < ilen; ++i) { + prop = props[i]; + if (options.hasOwnProperty(prop)) { + item[prop] = options[prop]; + } + } + }, + + /** + * Fits boxes of the given chart into the given size by having each box measure itself + * then running a fitting algorithm + * @param {Chart} chart - the chart + * @param {Number} width - the width to fit into + * @param {Number} height - the height to fit into + */ + update: function(chart, width, height) { + if (!chart) { + return; + } + + var layoutOptions = chart.options.layout || {}; + var padding = helpers.options.toPadding(layoutOptions.padding); + var leftPadding = padding.left; + var rightPadding = padding.right; + var topPadding = padding.top; + var bottomPadding = padding.bottom; + + var leftBoxes = filterByPosition(chart.boxes, 'left'); + var rightBoxes = filterByPosition(chart.boxes, 'right'); + var topBoxes = filterByPosition(chart.boxes, 'top'); + var bottomBoxes = filterByPosition(chart.boxes, 'bottom'); + var chartAreaBoxes = filterByPosition(chart.boxes, 'chartArea'); + + // Sort boxes by weight. A higher weight is further away from the chart area + sortByWeight(leftBoxes, true); + sortByWeight(rightBoxes, false); + sortByWeight(topBoxes, true); + sortByWeight(bottomBoxes, false); + + // Essentially we now have any number of boxes on each of the 4 sides. + // Our canvas looks like the following. + // The areas L1 and L2 are the left axes. R1 is the right axis, T1 is the top axis and + // B1 is the bottom axis + // There are also 4 quadrant-like locations (left to right instead of clockwise) reserved for chart overlays + // These locations are single-box locations only, when trying to register a chartArea location that is already taken, + // an error will be thrown. + // + // |----------------------------------------------------| + // | T1 (Full Width) | + // |----------------------------------------------------| + // | | | T2 | | + // | |----|-------------------------------------|----| + // | | | C1 | | C2 | | + // | | |----| |----| | + // | | | | | + // | L1 | L2 | ChartArea (C0) | R1 | + // | | | | | + // | | |----| |----| | + // | | | C3 | | C4 | | + // | |----|-------------------------------------|----| + // | | | B1 | | + // |----------------------------------------------------| + // | B2 (Full Width) | + // |----------------------------------------------------| + // + // What we do to find the best sizing, we do the following + // 1. Determine the minimum size of the chart area. + // 2. Split the remaining width equally between each vertical axis + // 3. Split the remaining height equally between each horizontal axis + // 4. Give each layout the maximum size it can be. The layout will return it's minimum size + // 5. Adjust the sizes of each axis based on it's minimum reported size. + // 6. Refit each axis + // 7. Position each axis in the final location + // 8. Tell the chart the final location of the chart area + // 9. Tell any axes that overlay the chart area the positions of the chart area + + // Step 1 + var chartWidth = width - leftPadding - rightPadding; + var chartHeight = height - topPadding - bottomPadding; + var chartAreaWidth = chartWidth / 2; // min 50% + var chartAreaHeight = chartHeight / 2; // min 50% + + // Step 2 + var verticalBoxWidth = (width - chartAreaWidth) / (leftBoxes.length + rightBoxes.length); + + // Step 3 + var horizontalBoxHeight = (height - chartAreaHeight) / (topBoxes.length + bottomBoxes.length); + + // Step 4 + var maxChartAreaWidth = chartWidth; + var maxChartAreaHeight = chartHeight; + var minBoxSizes = []; + + function getMinimumBoxSize(box) { + var minSize; + var isHorizontal = box.isHorizontal(); + + if (isHorizontal) { + minSize = box.update(box.fullWidth ? chartWidth : maxChartAreaWidth, horizontalBoxHeight); + maxChartAreaHeight -= minSize.height; + } else { + minSize = box.update(verticalBoxWidth, maxChartAreaHeight); + maxChartAreaWidth -= minSize.width; + } + + minBoxSizes.push({ + horizontal: isHorizontal, + minSize: minSize, + box: box, + }); + } + + helpers.each(leftBoxes.concat(rightBoxes, topBoxes, bottomBoxes), getMinimumBoxSize); + + // If a horizontal box has padding, we move the left boxes over to avoid ugly charts (see issue #2478) + var maxHorizontalLeftPadding = 0; + var maxHorizontalRightPadding = 0; + var maxVerticalTopPadding = 0; + var maxVerticalBottomPadding = 0; + + helpers.each(topBoxes.concat(bottomBoxes), function(horizontalBox) { + if (horizontalBox.getPadding) { + var boxPadding = horizontalBox.getPadding(); + maxHorizontalLeftPadding = Math.max(maxHorizontalLeftPadding, boxPadding.left); + maxHorizontalRightPadding = Math.max(maxHorizontalRightPadding, boxPadding.right); + } + }); + + helpers.each(leftBoxes.concat(rightBoxes), function(verticalBox) { + if (verticalBox.getPadding) { + var boxPadding = verticalBox.getPadding(); + maxVerticalTopPadding = Math.max(maxVerticalTopPadding, boxPadding.top); + maxVerticalBottomPadding = Math.max(maxVerticalBottomPadding, boxPadding.bottom); + } + }); + + // At this point, maxChartAreaHeight and maxChartAreaWidth are the size the chart area could + // be if the axes are drawn at their minimum sizes. + // Steps 5 & 6 + var totalLeftBoxesWidth = leftPadding; + var totalRightBoxesWidth = rightPadding; + var totalTopBoxesHeight = topPadding; + var totalBottomBoxesHeight = bottomPadding; + + // Function to fit a box + function fitBox(box) { + var minBoxSize = helpers.findNextWhere(minBoxSizes, function(minBox) { + return minBox.box === box; + }); + + if (minBoxSize) { + if (box.isHorizontal()) { + var scaleMargin = { + left: Math.max(totalLeftBoxesWidth, maxHorizontalLeftPadding), + right: Math.max(totalRightBoxesWidth, maxHorizontalRightPadding), + top: 0, + bottom: 0 + }; + + // Don't use min size here because of label rotation. When the labels are rotated, their rotation highly depends + // on the margin. Sometimes they need to increase in size slightly + box.update(box.fullWidth ? chartWidth : maxChartAreaWidth, chartHeight / 2, scaleMargin); + } else { + box.update(minBoxSize.minSize.width, maxChartAreaHeight); + } + } + } + + // Update, and calculate the left and right margins for the horizontal boxes + helpers.each(leftBoxes.concat(rightBoxes), fitBox); + + helpers.each(leftBoxes, function(box) { + totalLeftBoxesWidth += box.width; + }); + + helpers.each(rightBoxes, function(box) { + totalRightBoxesWidth += box.width; + }); + + // Set the Left and Right margins for the horizontal boxes + helpers.each(topBoxes.concat(bottomBoxes), fitBox); + + // Figure out how much margin is on the top and bottom of the vertical boxes + helpers.each(topBoxes, function(box) { + totalTopBoxesHeight += box.height; + }); + + helpers.each(bottomBoxes, function(box) { + totalBottomBoxesHeight += box.height; + }); + + function finalFitVerticalBox(box) { + var minBoxSize = helpers.findNextWhere(minBoxSizes, function(minSize) { + return minSize.box === box; + }); + + var scaleMargin = { + left: 0, + right: 0, + top: totalTopBoxesHeight, + bottom: totalBottomBoxesHeight + }; + + if (minBoxSize) { + box.update(minBoxSize.minSize.width, maxChartAreaHeight, scaleMargin); + } + } + + // Let the left layout know the final margin + helpers.each(leftBoxes.concat(rightBoxes), finalFitVerticalBox); + + // Recalculate because the size of each layout might have changed slightly due to the margins (label rotation for instance) + totalLeftBoxesWidth = leftPadding; + totalRightBoxesWidth = rightPadding; + totalTopBoxesHeight = topPadding; + totalBottomBoxesHeight = bottomPadding; + + helpers.each(leftBoxes, function(box) { + totalLeftBoxesWidth += box.width; + }); + + helpers.each(rightBoxes, function(box) { + totalRightBoxesWidth += box.width; + }); + + helpers.each(topBoxes, function(box) { + totalTopBoxesHeight += box.height; + }); + helpers.each(bottomBoxes, function(box) { + totalBottomBoxesHeight += box.height; + }); + + // We may be adding some padding to account for rotated x axis labels + var leftPaddingAddition = Math.max(maxHorizontalLeftPadding - totalLeftBoxesWidth, 0); + totalLeftBoxesWidth += leftPaddingAddition; + totalRightBoxesWidth += Math.max(maxHorizontalRightPadding - totalRightBoxesWidth, 0); + + var topPaddingAddition = Math.max(maxVerticalTopPadding - totalTopBoxesHeight, 0); + totalTopBoxesHeight += topPaddingAddition; + totalBottomBoxesHeight += Math.max(maxVerticalBottomPadding - totalBottomBoxesHeight, 0); + + // Figure out if our chart area changed. This would occur if the dataset layout label rotation + // changed due to the application of the margins in step 6. Since we can only get bigger, this is safe to do + // without calling `fit` again + var newMaxChartAreaHeight = height - totalTopBoxesHeight - totalBottomBoxesHeight; + var newMaxChartAreaWidth = width - totalLeftBoxesWidth - totalRightBoxesWidth; + + if (newMaxChartAreaWidth !== maxChartAreaWidth || newMaxChartAreaHeight !== maxChartAreaHeight) { + helpers.each(leftBoxes, function(box) { + box.height = newMaxChartAreaHeight; + }); + + helpers.each(rightBoxes, function(box) { + box.height = newMaxChartAreaHeight; + }); + + helpers.each(topBoxes, function(box) { + if (!box.fullWidth) { + box.width = newMaxChartAreaWidth; + } + }); + + helpers.each(bottomBoxes, function(box) { + if (!box.fullWidth) { + box.width = newMaxChartAreaWidth; + } + }); + + maxChartAreaHeight = newMaxChartAreaHeight; + maxChartAreaWidth = newMaxChartAreaWidth; + } + + // Step 7 - Position the boxes + var left = leftPadding + leftPaddingAddition; + var top = topPadding + topPaddingAddition; + + function placeBox(box) { + if (box.isHorizontal()) { + box.left = box.fullWidth ? leftPadding : totalLeftBoxesWidth; + box.right = box.fullWidth ? width - rightPadding : totalLeftBoxesWidth + maxChartAreaWidth; + box.top = top; + box.bottom = top + box.height; + + // Move to next point + top = box.bottom; + + } else { + + box.left = left; + box.right = left + box.width; + box.top = totalTopBoxesHeight; + box.bottom = totalTopBoxesHeight + maxChartAreaHeight; + + // Move to next point + left = box.right; + } + } + + helpers.each(leftBoxes.concat(topBoxes), placeBox); + + // Account for chart width and height + left += maxChartAreaWidth; + top += maxChartAreaHeight; + + helpers.each(rightBoxes, placeBox); + helpers.each(bottomBoxes, placeBox); + + // Step 8 + chart.chartArea = { + left: totalLeftBoxesWidth, + top: totalTopBoxesHeight, + right: totalLeftBoxesWidth + maxChartAreaWidth, + bottom: totalTopBoxesHeight + maxChartAreaHeight + }; + + // Step 9 + helpers.each(chartAreaBoxes, function(box) { + box.left = chart.chartArea.left; + box.top = chart.chartArea.top; + box.right = chart.chartArea.right; + box.bottom = chart.chartArea.bottom; + + box.update(maxChartAreaWidth, maxChartAreaHeight); + }); + } +}; + +},{"46":46}],32:[function(require,module,exports){ +'use strict'; + +var defaults = require(26); +var helpers = require(46); + +defaults._set('global', { + plugins: {} +}); + +/** + * The plugin service singleton + * @namespace Chart.plugins + * @since 2.1.0 + */ +module.exports = { + /** + * Globally registered plugins. + * @private + */ + _plugins: [], + + /** + * This identifier is used to invalidate the descriptors cache attached to each chart + * when a global plugin is registered or unregistered. In this case, the cache ID is + * incremented and descriptors are regenerated during following API calls. + * @private + */ + _cacheId: 0, + + /** + * Registers the given plugin(s) if not already registered. + * @param {Array|Object} plugins plugin instance(s). + */ + register: function(plugins) { + var p = this._plugins; + ([]).concat(plugins).forEach(function(plugin) { + if (p.indexOf(plugin) === -1) { + p.push(plugin); + } + }); + + this._cacheId++; + }, + + /** + * Unregisters the given plugin(s) only if registered. + * @param {Array|Object} plugins plugin instance(s). + */ + unregister: function(plugins) { + var p = this._plugins; + ([]).concat(plugins).forEach(function(plugin) { + var idx = p.indexOf(plugin); + if (idx !== -1) { + p.splice(idx, 1); + } + }); + + this._cacheId++; + }, + + /** + * Remove all registered plugins. + * @since 2.1.5 + */ + clear: function() { + this._plugins = []; + this._cacheId++; + }, + + /** + * Returns the number of registered plugins? + * @returns {Number} + * @since 2.1.5 + */ + count: function() { + return this._plugins.length; + }, + + /** + * Returns all registered plugin instances. + * @returns {Array} array of plugin objects. + * @since 2.1.5 + */ + getAll: function() { + return this._plugins; + }, + + /** + * Calls enabled plugins for `chart` on the specified hook and with the given args. + * This method immediately returns as soon as a plugin explicitly returns false. The + * returned value can be used, for instance, to interrupt the current action. + * @param {Object} chart - The chart instance for which plugins should be called. + * @param {String} hook - The name of the plugin method to call (e.g. 'beforeUpdate'). + * @param {Array} [args] - Extra arguments to apply to the hook call. + * @returns {Boolean} false if any of the plugins return false, else returns true. + */ + notify: function(chart, hook, args) { + var descriptors = this.descriptors(chart); + var ilen = descriptors.length; + var i, descriptor, plugin, params, method; + + for (i = 0; i < ilen; ++i) { + descriptor = descriptors[i]; + plugin = descriptor.plugin; + method = plugin[hook]; + if (typeof method === 'function') { + params = [chart].concat(args || []); + params.push(descriptor.options); + if (method.apply(plugin, params) === false) { + return false; + } + } + } + + return true; + }, + + /** + * Returns descriptors of enabled plugins for the given chart. + * @returns {Array} [{ plugin, options }] + * @private + */ + descriptors: function(chart) { + var cache = chart.$plugins || (chart.$plugins = {}); + if (cache.id === this._cacheId) { + return cache.descriptors; + } + + var plugins = []; + var descriptors = []; + var config = (chart && chart.config) || {}; + var options = (config.options && config.options.plugins) || {}; + + this._plugins.concat(config.plugins || []).forEach(function(plugin) { + var idx = plugins.indexOf(plugin); + if (idx !== -1) { + return; + } + + var id = plugin.id; + var opts = options[id]; + if (opts === false) { + return; + } + + if (opts === true) { + opts = helpers.clone(defaults.global.plugins[id]); + } + + plugins.push(plugin); + descriptors.push({ + plugin: plugin, + options: opts || {} + }); + }); + + cache.descriptors = descriptors; + cache.id = this._cacheId; + return descriptors; + }, + + /** + * Invalidates cache for the given chart: descriptors hold a reference on plugin option, + * but in some cases, this reference can be changed by the user when updating options. + * https://github.com/chartjs/Chart.js/issues/5111#issuecomment-355934167 + * @private + */ + _invalidate: function(chart) { + delete chart.$plugins; + } +}; + +/** + * Plugin extension hooks. + * @interface IPlugin + * @since 2.1.0 + */ +/** + * @method IPlugin#beforeInit + * @desc Called before initializing `chart`. + * @param {Chart.Controller} chart - The chart instance. + * @param {Object} options - The plugin options. + */ +/** + * @method IPlugin#afterInit + * @desc Called after `chart` has been initialized and before the first update. + * @param {Chart.Controller} chart - The chart instance. + * @param {Object} options - The plugin options. + */ +/** + * @method IPlugin#beforeUpdate + * @desc Called before updating `chart`. If any plugin returns `false`, the update + * is cancelled (and thus subsequent render(s)) until another `update` is triggered. + * @param {Chart.Controller} chart - The chart instance. + * @param {Object} options - The plugin options. + * @returns {Boolean} `false` to cancel the chart update. + */ +/** + * @method IPlugin#afterUpdate + * @desc Called after `chart` has been updated and before rendering. Note that this + * hook will not be called if the chart update has been previously cancelled. + * @param {Chart.Controller} chart - The chart instance. + * @param {Object} options - The plugin options. + */ +/** + * @method IPlugin#beforeDatasetsUpdate + * @desc Called before updating the `chart` datasets. If any plugin returns `false`, + * the datasets update is cancelled until another `update` is triggered. + * @param {Chart.Controller} chart - The chart instance. + * @param {Object} options - The plugin options. + * @returns {Boolean} false to cancel the datasets update. + * @since version 2.1.5 +*/ +/** + * @method IPlugin#afterDatasetsUpdate + * @desc Called after the `chart` datasets have been updated. Note that this hook + * will not be called if the datasets update has been previously cancelled. + * @param {Chart.Controller} chart - The chart instance. + * @param {Object} options - The plugin options. + * @since version 2.1.5 + */ +/** + * @method IPlugin#beforeDatasetUpdate + * @desc Called before updating the `chart` dataset at the given `args.index`. If any plugin + * returns `false`, the datasets update is cancelled until another `update` is triggered. + * @param {Chart} chart - The chart instance. + * @param {Object} args - The call arguments. + * @param {Number} args.index - The dataset index. + * @param {Object} args.meta - The dataset metadata. + * @param {Object} options - The plugin options. + * @returns {Boolean} `false` to cancel the chart datasets drawing. + */ +/** + * @method IPlugin#afterDatasetUpdate + * @desc Called after the `chart` datasets at the given `args.index` has been updated. Note + * that this hook will not be called if the datasets update has been previously cancelled. + * @param {Chart} chart - The chart instance. + * @param {Object} args - The call arguments. + * @param {Number} args.index - The dataset index. + * @param {Object} args.meta - The dataset metadata. + * @param {Object} options - The plugin options. + */ +/** + * @method IPlugin#beforeLayout + * @desc Called before laying out `chart`. If any plugin returns `false`, + * the layout update is cancelled until another `update` is triggered. + * @param {Chart.Controller} chart - The chart instance. + * @param {Object} options - The plugin options. + * @returns {Boolean} `false` to cancel the chart layout. + */ +/** + * @method IPlugin#afterLayout + * @desc Called after the `chart` has been layed out. Note that this hook will not + * be called if the layout update has been previously cancelled. + * @param {Chart.Controller} chart - The chart instance. + * @param {Object} options - The plugin options. + */ +/** + * @method IPlugin#beforeRender + * @desc Called before rendering `chart`. If any plugin returns `false`, + * the rendering is cancelled until another `render` is triggered. + * @param {Chart.Controller} chart - The chart instance. + * @param {Object} options - The plugin options. + * @returns {Boolean} `false` to cancel the chart rendering. + */ +/** + * @method IPlugin#afterRender + * @desc Called after the `chart` has been fully rendered (and animation completed). Note + * that this hook will not be called if the rendering has been previously cancelled. + * @param {Chart.Controller} chart - The chart instance. + * @param {Object} options - The plugin options. + */ +/** + * @method IPlugin#beforeDraw + * @desc Called before drawing `chart` at every animation frame specified by the given + * easing value. If any plugin returns `false`, the frame drawing is cancelled until + * another `render` is triggered. + * @param {Chart.Controller} chart - The chart instance. + * @param {Number} easingValue - The current animation value, between 0.0 and 1.0. + * @param {Object} options - The plugin options. + * @returns {Boolean} `false` to cancel the chart drawing. + */ +/** + * @method IPlugin#afterDraw + * @desc Called after the `chart` has been drawn for the specific easing value. Note + * that this hook will not be called if the drawing has been previously cancelled. + * @param {Chart.Controller} chart - The chart instance. + * @param {Number} easingValue - The current animation value, between 0.0 and 1.0. + * @param {Object} options - The plugin options. + */ +/** + * @method IPlugin#beforeDatasetsDraw + * @desc Called before drawing the `chart` datasets. If any plugin returns `false`, + * the datasets drawing is cancelled until another `render` is triggered. + * @param {Chart.Controller} chart - The chart instance. + * @param {Number} easingValue - The current animation value, between 0.0 and 1.0. + * @param {Object} options - The plugin options. + * @returns {Boolean} `false` to cancel the chart datasets drawing. + */ +/** + * @method IPlugin#afterDatasetsDraw + * @desc Called after the `chart` datasets have been drawn. Note that this hook + * will not be called if the datasets drawing has been previously cancelled. + * @param {Chart.Controller} chart - The chart instance. + * @param {Number} easingValue - The current animation value, between 0.0 and 1.0. + * @param {Object} options - The plugin options. + */ +/** + * @method IPlugin#beforeDatasetDraw + * @desc Called before drawing the `chart` dataset at the given `args.index` (datasets + * are drawn in the reverse order). If any plugin returns `false`, the datasets drawing + * is cancelled until another `render` is triggered. + * @param {Chart} chart - The chart instance. + * @param {Object} args - The call arguments. + * @param {Number} args.index - The dataset index. + * @param {Object} args.meta - The dataset metadata. + * @param {Number} args.easingValue - The current animation value, between 0.0 and 1.0. + * @param {Object} options - The plugin options. + * @returns {Boolean} `false` to cancel the chart datasets drawing. + */ +/** + * @method IPlugin#afterDatasetDraw + * @desc Called after the `chart` datasets at the given `args.index` have been drawn + * (datasets are drawn in the reverse order). Note that this hook will not be called + * if the datasets drawing has been previously cancelled. + * @param {Chart} chart - The chart instance. + * @param {Object} args - The call arguments. + * @param {Number} args.index - The dataset index. + * @param {Object} args.meta - The dataset metadata. + * @param {Number} args.easingValue - The current animation value, between 0.0 and 1.0. + * @param {Object} options - The plugin options. + */ +/** + * @method IPlugin#beforeTooltipDraw + * @desc Called before drawing the `tooltip`. If any plugin returns `false`, + * the tooltip drawing is cancelled until another `render` is triggered. + * @param {Chart} chart - The chart instance. + * @param {Object} args - The call arguments. + * @param {Object} args.tooltip - The tooltip. + * @param {Number} args.easingValue - The current animation value, between 0.0 and 1.0. + * @param {Object} options - The plugin options. + * @returns {Boolean} `false` to cancel the chart tooltip drawing. + */ +/** + * @method IPlugin#afterTooltipDraw + * @desc Called after drawing the `tooltip`. Note that this hook will not + * be called if the tooltip drawing has been previously cancelled. + * @param {Chart} chart - The chart instance. + * @param {Object} args - The call arguments. + * @param {Object} args.tooltip - The tooltip. + * @param {Number} args.easingValue - The current animation value, between 0.0 and 1.0. + * @param {Object} options - The plugin options. + */ +/** + * @method IPlugin#beforeEvent + * @desc Called before processing the specified `event`. If any plugin returns `false`, + * the event will be discarded. + * @param {Chart.Controller} chart - The chart instance. + * @param {IEvent} event - The event object. + * @param {Object} options - The plugin options. + */ +/** + * @method IPlugin#afterEvent + * @desc Called after the `event` has been consumed. Note that this hook + * will not be called if the `event` has been previously discarded. + * @param {Chart.Controller} chart - The chart instance. + * @param {IEvent} event - The event object. + * @param {Object} options - The plugin options. + */ +/** + * @method IPlugin#resize + * @desc Called after the chart as been resized. + * @param {Chart.Controller} chart - The chart instance. + * @param {Number} size - The new canvas display size (eq. canvas.style width & height). + * @param {Object} options - The plugin options. + */ +/** + * @method IPlugin#destroy + * @desc Called after the chart as been destroyed. + * @param {Chart.Controller} chart - The chart instance. + * @param {Object} options - The plugin options. + */ + +},{"26":26,"46":46}],33:[function(require,module,exports){ +'use strict'; + +var defaults = require(26); +var Element = require(27); +var helpers = require(46); +var Ticks = require(35); + +defaults._set('scale', { + display: true, + position: 'left', + offset: false, + + // grid line settings + gridLines: { + display: true, + color: 'rgba(0, 0, 0, 0.1)', + lineWidth: 1, + drawBorder: true, + drawOnChartArea: true, + drawTicks: true, + tickMarkLength: 10, + zeroLineWidth: 1, + zeroLineColor: 'rgba(0,0,0,0.25)', + zeroLineBorderDash: [], + zeroLineBorderDashOffset: 0.0, + offsetGridLines: false, + borderDash: [], + borderDashOffset: 0.0 + }, + + // scale label + scaleLabel: { + // display property + display: false, + + // actual label + labelString: '', + + // line height + lineHeight: 1.2, + + // top/bottom padding + padding: { + top: 4, + bottom: 4 + } + }, + + // label settings + ticks: { + beginAtZero: false, + minRotation: 0, + maxRotation: 50, + mirror: false, + padding: 0, + reverse: false, + display: true, + autoSkip: true, + autoSkipPadding: 0, + labelOffset: 0, + // We pass through arrays to be rendered as multiline labels, we convert Others to strings here. + callback: Ticks.formatters.values, + minor: {}, + major: {} + } +}); + +function labelsFromTicks(ticks) { + var labels = []; + var i, ilen; + + for (i = 0, ilen = ticks.length; i < ilen; ++i) { + labels.push(ticks[i].label); + } + + return labels; +} + +function getLineValue(scale, index, offsetGridLines) { + var lineValue = scale.getPixelForTick(index); + + if (offsetGridLines) { + if (index === 0) { + lineValue -= (scale.getPixelForTick(1) - lineValue) / 2; + } else { + lineValue -= (lineValue - scale.getPixelForTick(index - 1)) / 2; + } + } + return lineValue; +} + +function computeTextSize(context, tick, font) { + return helpers.isArray(tick) ? + helpers.longestText(context, font, tick) : + context.measureText(tick).width; +} + +function parseFontOptions(options) { + var valueOrDefault = helpers.valueOrDefault; + var globalDefaults = defaults.global; + var size = valueOrDefault(options.fontSize, globalDefaults.defaultFontSize); + var style = valueOrDefault(options.fontStyle, globalDefaults.defaultFontStyle); + var family = valueOrDefault(options.fontFamily, globalDefaults.defaultFontFamily); + + return { + size: size, + style: style, + family: family, + font: helpers.fontString(size, style, family) + }; +} + +function parseLineHeight(options) { + return helpers.options.toLineHeight( + helpers.valueOrDefault(options.lineHeight, 1.2), + helpers.valueOrDefault(options.fontSize, defaults.global.defaultFontSize)); +} + +module.exports = Element.extend({ + /** + * Get the padding needed for the scale + * @method getPadding + * @private + * @returns {Padding} the necessary padding + */ + getPadding: function() { + var me = this; + return { + left: me.paddingLeft || 0, + top: me.paddingTop || 0, + right: me.paddingRight || 0, + bottom: me.paddingBottom || 0 + }; + }, + + /** + * Returns the scale tick objects ({label, major}) + * @since 2.7 + */ + getTicks: function() { + return this._ticks; + }, + + // These methods are ordered by lifecyle. Utilities then follow. + // Any function defined here is inherited by all scale types. + // Any function can be extended by the scale type + + mergeTicksOptions: function() { + var ticks = this.options.ticks; + if (ticks.minor === false) { + ticks.minor = { + display: false + }; + } + if (ticks.major === false) { + ticks.major = { + display: false + }; + } + for (var key in ticks) { + if (key !== 'major' && key !== 'minor') { + if (typeof ticks.minor[key] === 'undefined') { + ticks.minor[key] = ticks[key]; + } + if (typeof ticks.major[key] === 'undefined') { + ticks.major[key] = ticks[key]; + } + } + } + }, + beforeUpdate: function() { + helpers.callback(this.options.beforeUpdate, [this]); + }, + + update: function(maxWidth, maxHeight, margins) { + var me = this; + var i, ilen, labels, label, ticks, tick; + + // Update Lifecycle - Probably don't want to ever extend or overwrite this function ;) + me.beforeUpdate(); + + // Absorb the master measurements + me.maxWidth = maxWidth; + me.maxHeight = maxHeight; + me.margins = helpers.extend({ + left: 0, + right: 0, + top: 0, + bottom: 0 + }, margins); + me.longestTextCache = me.longestTextCache || {}; + + // Dimensions + me.beforeSetDimensions(); + me.setDimensions(); + me.afterSetDimensions(); + + // Data min/max + me.beforeDataLimits(); + me.determineDataLimits(); + me.afterDataLimits(); + + // Ticks - `this.ticks` is now DEPRECATED! + // Internal ticks are now stored as objects in the PRIVATE `this._ticks` member + // and must not be accessed directly from outside this class. `this.ticks` being + // around for long time and not marked as private, we can't change its structure + // without unexpected breaking changes. If you need to access the scale ticks, + // use scale.getTicks() instead. + + me.beforeBuildTicks(); + + // New implementations should return an array of objects but for BACKWARD COMPAT, + // we still support no return (`this.ticks` internally set by calling this method). + ticks = me.buildTicks() || []; + + me.afterBuildTicks(); + + me.beforeTickToLabelConversion(); + + // New implementations should return the formatted tick labels but for BACKWARD + // COMPAT, we still support no return (`this.ticks` internally changed by calling + // this method and supposed to contain only string values). + labels = me.convertTicksToLabels(ticks) || me.ticks; + + me.afterTickToLabelConversion(); + + me.ticks = labels; // BACKWARD COMPATIBILITY + + // IMPORTANT: from this point, we consider that `this.ticks` will NEVER change! + + // BACKWARD COMPAT: synchronize `_ticks` with labels (so potentially `this.ticks`) + for (i = 0, ilen = labels.length; i < ilen; ++i) { + label = labels[i]; + tick = ticks[i]; + if (!tick) { + ticks.push(tick = { + label: label, + major: false + }); + } else { + tick.label = label; + } + } + + me._ticks = ticks; + + // Tick Rotation + me.beforeCalculateTickRotation(); + me.calculateTickRotation(); + me.afterCalculateTickRotation(); + // Fit + me.beforeFit(); + me.fit(); + me.afterFit(); + // + me.afterUpdate(); + + return me.minSize; + + }, + afterUpdate: function() { + helpers.callback(this.options.afterUpdate, [this]); + }, + + // + + beforeSetDimensions: function() { + helpers.callback(this.options.beforeSetDimensions, [this]); + }, + setDimensions: function() { + var me = this; + // Set the unconstrained dimension before label rotation + if (me.isHorizontal()) { + // Reset position before calculating rotation + me.width = me.maxWidth; + me.left = 0; + me.right = me.width; + } else { + me.height = me.maxHeight; + + // Reset position before calculating rotation + me.top = 0; + me.bottom = me.height; + } + + // Reset padding + me.paddingLeft = 0; + me.paddingTop = 0; + me.paddingRight = 0; + me.paddingBottom = 0; + }, + afterSetDimensions: function() { + helpers.callback(this.options.afterSetDimensions, [this]); + }, + + // Data limits + beforeDataLimits: function() { + helpers.callback(this.options.beforeDataLimits, [this]); + }, + determineDataLimits: helpers.noop, + afterDataLimits: function() { + helpers.callback(this.options.afterDataLimits, [this]); + }, + + // + beforeBuildTicks: function() { + helpers.callback(this.options.beforeBuildTicks, [this]); + }, + buildTicks: helpers.noop, + afterBuildTicks: function() { + helpers.callback(this.options.afterBuildTicks, [this]); + }, + + beforeTickToLabelConversion: function() { + helpers.callback(this.options.beforeTickToLabelConversion, [this]); + }, + convertTicksToLabels: function() { + var me = this; + // Convert ticks to strings + var tickOpts = me.options.ticks; + me.ticks = me.ticks.map(tickOpts.userCallback || tickOpts.callback, this); + }, + afterTickToLabelConversion: function() { + helpers.callback(this.options.afterTickToLabelConversion, [this]); + }, + + // + + beforeCalculateTickRotation: function() { + helpers.callback(this.options.beforeCalculateTickRotation, [this]); + }, + calculateTickRotation: function() { + var me = this; + var context = me.ctx; + var tickOpts = me.options.ticks; + var labels = labelsFromTicks(me._ticks); + + // Get the width of each grid by calculating the difference + // between x offsets between 0 and 1. + var tickFont = parseFontOptions(tickOpts); + context.font = tickFont.font; + + var labelRotation = tickOpts.minRotation || 0; + + if (labels.length && me.options.display && me.isHorizontal()) { + var originalLabelWidth = helpers.longestText(context, tickFont.font, labels, me.longestTextCache); + var labelWidth = originalLabelWidth; + var cosRotation, sinRotation; + + // Allow 3 pixels x2 padding either side for label readability + var tickWidth = me.getPixelForTick(1) - me.getPixelForTick(0) - 6; + + // Max label rotation can be set or default to 90 - also act as a loop counter + while (labelWidth > tickWidth && labelRotation < tickOpts.maxRotation) { + var angleRadians = helpers.toRadians(labelRotation); + cosRotation = Math.cos(angleRadians); + sinRotation = Math.sin(angleRadians); + + if (sinRotation * originalLabelWidth > me.maxHeight) { + // go back one step + labelRotation--; + break; + } + + labelRotation++; + labelWidth = cosRotation * originalLabelWidth; + } + } + + me.labelRotation = labelRotation; + }, + afterCalculateTickRotation: function() { + helpers.callback(this.options.afterCalculateTickRotation, [this]); + }, + + // + + beforeFit: function() { + helpers.callback(this.options.beforeFit, [this]); + }, + fit: function() { + var me = this; + // Reset + var minSize = me.minSize = { + width: 0, + height: 0 + }; + + var labels = labelsFromTicks(me._ticks); + + var opts = me.options; + var tickOpts = opts.ticks; + var scaleLabelOpts = opts.scaleLabel; + var gridLineOpts = opts.gridLines; + var display = opts.display; + var isHorizontal = me.isHorizontal(); + + var tickFont = parseFontOptions(tickOpts); + var tickMarkLength = opts.gridLines.tickMarkLength; + + // Width + if (isHorizontal) { + // subtract the margins to line up with the chartArea if we are a full width scale + minSize.width = me.isFullWidth() ? me.maxWidth - me.margins.left - me.margins.right : me.maxWidth; + } else { + minSize.width = display && gridLineOpts.drawTicks ? tickMarkLength : 0; + } + + // height + if (isHorizontal) { + minSize.height = display && gridLineOpts.drawTicks ? tickMarkLength : 0; + } else { + minSize.height = me.maxHeight; // fill all the height + } + + // Are we showing a title for the scale? + if (scaleLabelOpts.display && display) { + var scaleLabelLineHeight = parseLineHeight(scaleLabelOpts); + var scaleLabelPadding = helpers.options.toPadding(scaleLabelOpts.padding); + var deltaHeight = scaleLabelLineHeight + scaleLabelPadding.height; + + if (isHorizontal) { + minSize.height += deltaHeight; + } else { + minSize.width += deltaHeight; + } + } + + // Don't bother fitting the ticks if we are not showing them + if (tickOpts.display && display) { + var largestTextWidth = helpers.longestText(me.ctx, tickFont.font, labels, me.longestTextCache); + var tallestLabelHeightInLines = helpers.numberOfLabelLines(labels); + var lineSpace = tickFont.size * 0.5; + var tickPadding = me.options.ticks.padding; + + if (isHorizontal) { + // A horizontal axis is more constrained by the height. + me.longestLabelWidth = largestTextWidth; + + var angleRadians = helpers.toRadians(me.labelRotation); + var cosRotation = Math.cos(angleRadians); + var sinRotation = Math.sin(angleRadians); + + // TODO - improve this calculation + var labelHeight = (sinRotation * largestTextWidth) + + (tickFont.size * tallestLabelHeightInLines) + + (lineSpace * (tallestLabelHeightInLines - 1)) + + lineSpace; // padding + + minSize.height = Math.min(me.maxHeight, minSize.height + labelHeight + tickPadding); + + me.ctx.font = tickFont.font; + var firstLabelWidth = computeTextSize(me.ctx, labels[0], tickFont.font); + var lastLabelWidth = computeTextSize(me.ctx, labels[labels.length - 1], tickFont.font); + + // Ensure that our ticks are always inside the canvas. When rotated, ticks are right aligned + // which means that the right padding is dominated by the font height + if (me.labelRotation !== 0) { + me.paddingLeft = opts.position === 'bottom' ? (cosRotation * firstLabelWidth) + 3 : (cosRotation * lineSpace) + 3; // add 3 px to move away from canvas edges + me.paddingRight = opts.position === 'bottom' ? (cosRotation * lineSpace) + 3 : (cosRotation * lastLabelWidth) + 3; + } else { + me.paddingLeft = firstLabelWidth / 2 + 3; // add 3 px to move away from canvas edges + me.paddingRight = lastLabelWidth / 2 + 3; + } + } else { + // A vertical axis is more constrained by the width. Labels are the + // dominant factor here, so get that length first and account for padding + if (tickOpts.mirror) { + largestTextWidth = 0; + } else { + // use lineSpace for consistency with horizontal axis + // tickPadding is not implemented for horizontal + largestTextWidth += tickPadding + lineSpace; + } + + minSize.width = Math.min(me.maxWidth, minSize.width + largestTextWidth); + + me.paddingTop = tickFont.size / 2; + me.paddingBottom = tickFont.size / 2; + } + } + + me.handleMargins(); + + me.width = minSize.width; + me.height = minSize.height; + }, + + /** + * Handle margins and padding interactions + * @private + */ + handleMargins: function() { + var me = this; + if (me.margins) { + me.paddingLeft = Math.max(me.paddingLeft - me.margins.left, 0); + me.paddingTop = Math.max(me.paddingTop - me.margins.top, 0); + me.paddingRight = Math.max(me.paddingRight - me.margins.right, 0); + me.paddingBottom = Math.max(me.paddingBottom - me.margins.bottom, 0); + } + }, + + afterFit: function() { + helpers.callback(this.options.afterFit, [this]); + }, + + // Shared Methods + isHorizontal: function() { + return this.options.position === 'top' || this.options.position === 'bottom'; + }, + isFullWidth: function() { + return (this.options.fullWidth); + }, + + // Get the correct value. NaN bad inputs, If the value type is object get the x or y based on whether we are horizontal or not + getRightValue: function(rawValue) { + // Null and undefined values first + if (helpers.isNullOrUndef(rawValue)) { + return NaN; + } + // isNaN(object) returns true, so make sure NaN is checking for a number; Discard Infinite values + if (typeof rawValue === 'number' && !isFinite(rawValue)) { + return NaN; + } + // If it is in fact an object, dive in one more level + if (rawValue) { + if (this.isHorizontal()) { + if (rawValue.x !== undefined) { + return this.getRightValue(rawValue.x); + } + } else if (rawValue.y !== undefined) { + return this.getRightValue(rawValue.y); + } + } + + // Value is good, return it + return rawValue; + }, + + /** + * Used to get the value to display in the tooltip for the data at the given index + * @param index + * @param datasetIndex + */ + getLabelForIndex: helpers.noop, + + /** + * Returns the location of the given data point. Value can either be an index or a numerical value + * The coordinate (0, 0) is at the upper-left corner of the canvas + * @param value + * @param index + * @param datasetIndex + */ + getPixelForValue: helpers.noop, + + /** + * Used to get the data value from a given pixel. This is the inverse of getPixelForValue + * The coordinate (0, 0) is at the upper-left corner of the canvas + * @param pixel + */ + getValueForPixel: helpers.noop, + + /** + * Returns the location of the tick at the given index + * The coordinate (0, 0) is at the upper-left corner of the canvas + */ + getPixelForTick: function(index) { + var me = this; + var offset = me.options.offset; + if (me.isHorizontal()) { + var innerWidth = me.width - (me.paddingLeft + me.paddingRight); + var tickWidth = innerWidth / Math.max((me._ticks.length - (offset ? 0 : 1)), 1); + var pixel = (tickWidth * index) + me.paddingLeft; + + if (offset) { + pixel += tickWidth / 2; + } + + var finalVal = me.left + Math.round(pixel); + finalVal += me.isFullWidth() ? me.margins.left : 0; + return finalVal; + } + var innerHeight = me.height - (me.paddingTop + me.paddingBottom); + return me.top + (index * (innerHeight / (me._ticks.length - 1))); + }, + + /** + * Utility for getting the pixel location of a percentage of scale + * The coordinate (0, 0) is at the upper-left corner of the canvas + */ + getPixelForDecimal: function(decimal) { + var me = this; + if (me.isHorizontal()) { + var innerWidth = me.width - (me.paddingLeft + me.paddingRight); + var valueOffset = (innerWidth * decimal) + me.paddingLeft; + + var finalVal = me.left + Math.round(valueOffset); + finalVal += me.isFullWidth() ? me.margins.left : 0; + return finalVal; + } + return me.top + (decimal * me.height); + }, + + /** + * Returns the pixel for the minimum chart value + * The coordinate (0, 0) is at the upper-left corner of the canvas + */ + getBasePixel: function() { + return this.getPixelForValue(this.getBaseValue()); + }, + + getBaseValue: function() { + var me = this; + var min = me.min; + var max = me.max; + + return me.beginAtZero ? 0 : + min < 0 && max < 0 ? max : + min > 0 && max > 0 ? min : + 0; + }, + + /** + * Returns a subset of ticks to be plotted to avoid overlapping labels. + * @private + */ + _autoSkip: function(ticks) { + var skipRatio; + var me = this; + var isHorizontal = me.isHorizontal(); + var optionTicks = me.options.ticks.minor; + var tickCount = ticks.length; + var labelRotationRadians = helpers.toRadians(me.labelRotation); + var cosRotation = Math.cos(labelRotationRadians); + var longestRotatedLabel = me.longestLabelWidth * cosRotation; + var result = []; + var i, tick, shouldSkip; + + // figure out the maximum number of gridlines to show + var maxTicks; + if (optionTicks.maxTicksLimit) { + maxTicks = optionTicks.maxTicksLimit; + } + + if (isHorizontal) { + skipRatio = false; + + if ((longestRotatedLabel + optionTicks.autoSkipPadding) * tickCount > (me.width - (me.paddingLeft + me.paddingRight))) { + skipRatio = 1 + Math.floor(((longestRotatedLabel + optionTicks.autoSkipPadding) * tickCount) / (me.width - (me.paddingLeft + me.paddingRight))); + } + + // if they defined a max number of optionTicks, + // increase skipRatio until that number is met + if (maxTicks && tickCount > maxTicks) { + skipRatio = Math.max(skipRatio, Math.floor(tickCount / maxTicks)); + } + } + + for (i = 0; i < tickCount; i++) { + tick = ticks[i]; + + // Since we always show the last tick,we need may need to hide the last shown one before + shouldSkip = (skipRatio > 1 && i % skipRatio > 0) || (i % skipRatio === 0 && i + skipRatio >= tickCount); + if (shouldSkip && i !== tickCount - 1) { + // leave tick in place but make sure it's not displayed (#4635) + delete tick.label; + } + result.push(tick); + } + return result; + }, + + // Actually draw the scale on the canvas + // @param {rectangle} chartArea : the area of the chart to draw full grid lines on + draw: function(chartArea) { + var me = this; + var options = me.options; + if (!options.display) { + return; + } + + var context = me.ctx; + var globalDefaults = defaults.global; + var optionTicks = options.ticks.minor; + var optionMajorTicks = options.ticks.major || optionTicks; + var gridLines = options.gridLines; + var scaleLabel = options.scaleLabel; + + var isRotated = me.labelRotation !== 0; + var isHorizontal = me.isHorizontal(); + + var ticks = optionTicks.autoSkip ? me._autoSkip(me.getTicks()) : me.getTicks(); + var tickFontColor = helpers.valueOrDefault(optionTicks.fontColor, globalDefaults.defaultFontColor); + var tickFont = parseFontOptions(optionTicks); + var majorTickFontColor = helpers.valueOrDefault(optionMajorTicks.fontColor, globalDefaults.defaultFontColor); + var majorTickFont = parseFontOptions(optionMajorTicks); + + var tl = gridLines.drawTicks ? gridLines.tickMarkLength : 0; + + var scaleLabelFontColor = helpers.valueOrDefault(scaleLabel.fontColor, globalDefaults.defaultFontColor); + var scaleLabelFont = parseFontOptions(scaleLabel); + var scaleLabelPadding = helpers.options.toPadding(scaleLabel.padding); + var labelRotationRadians = helpers.toRadians(me.labelRotation); + + var itemsToDraw = []; + + var axisWidth = me.options.gridLines.lineWidth; + var xTickStart = options.position === 'right' ? me.left : me.right - axisWidth - tl; + var xTickEnd = options.position === 'right' ? me.left + tl : me.right; + var yTickStart = options.position === 'bottom' ? me.top + axisWidth : me.bottom - tl - axisWidth; + var yTickEnd = options.position === 'bottom' ? me.top + axisWidth + tl : me.bottom + axisWidth; + + helpers.each(ticks, function(tick, index) { + // autoskipper skipped this tick (#4635) + if (helpers.isNullOrUndef(tick.label)) { + return; + } + + var label = tick.label; + var lineWidth, lineColor, borderDash, borderDashOffset; + if (index === me.zeroLineIndex && options.offset === gridLines.offsetGridLines) { + // Draw the first index specially + lineWidth = gridLines.zeroLineWidth; + lineColor = gridLines.zeroLineColor; + borderDash = gridLines.zeroLineBorderDash; + borderDashOffset = gridLines.zeroLineBorderDashOffset; + } else { + lineWidth = helpers.valueAtIndexOrDefault(gridLines.lineWidth, index); + lineColor = helpers.valueAtIndexOrDefault(gridLines.color, index); + borderDash = helpers.valueOrDefault(gridLines.borderDash, globalDefaults.borderDash); + borderDashOffset = helpers.valueOrDefault(gridLines.borderDashOffset, globalDefaults.borderDashOffset); + } + + // Common properties + var tx1, ty1, tx2, ty2, x1, y1, x2, y2, labelX, labelY; + var textAlign = 'middle'; + var textBaseline = 'middle'; + var tickPadding = optionTicks.padding; + + if (isHorizontal) { + var labelYOffset = tl + tickPadding; + + if (options.position === 'bottom') { + // bottom + textBaseline = !isRotated ? 'top' : 'middle'; + textAlign = !isRotated ? 'center' : 'right'; + labelY = me.top + labelYOffset; + } else { + // top + textBaseline = !isRotated ? 'bottom' : 'middle'; + textAlign = !isRotated ? 'center' : 'left'; + labelY = me.bottom - labelYOffset; + } + + var xLineValue = getLineValue(me, index, gridLines.offsetGridLines && ticks.length > 1); + if (xLineValue < me.left) { + lineColor = 'rgba(0,0,0,0)'; + } + xLineValue += helpers.aliasPixel(lineWidth); + + labelX = me.getPixelForTick(index) + optionTicks.labelOffset; // x values for optionTicks (need to consider offsetLabel option) + + tx1 = tx2 = x1 = x2 = xLineValue; + ty1 = yTickStart; + ty2 = yTickEnd; + y1 = chartArea.top; + y2 = chartArea.bottom + axisWidth; + } else { + var isLeft = options.position === 'left'; + var labelXOffset; + + if (optionTicks.mirror) { + textAlign = isLeft ? 'left' : 'right'; + labelXOffset = tickPadding; + } else { + textAlign = isLeft ? 'right' : 'left'; + labelXOffset = tl + tickPadding; + } + + labelX = isLeft ? me.right - labelXOffset : me.left + labelXOffset; + + var yLineValue = getLineValue(me, index, gridLines.offsetGridLines && ticks.length > 1); + if (yLineValue < me.top) { + lineColor = 'rgba(0,0,0,0)'; + } + yLineValue += helpers.aliasPixel(lineWidth); + + labelY = me.getPixelForTick(index) + optionTicks.labelOffset; + + tx1 = xTickStart; + tx2 = xTickEnd; + x1 = chartArea.left; + x2 = chartArea.right + axisWidth; + ty1 = ty2 = y1 = y2 = yLineValue; + } + + itemsToDraw.push({ + tx1: tx1, + ty1: ty1, + tx2: tx2, + ty2: ty2, + x1: x1, + y1: y1, + x2: x2, + y2: y2, + labelX: labelX, + labelY: labelY, + glWidth: lineWidth, + glColor: lineColor, + glBorderDash: borderDash, + glBorderDashOffset: borderDashOffset, + rotation: -1 * labelRotationRadians, + label: label, + major: tick.major, + textBaseline: textBaseline, + textAlign: textAlign + }); + }); + + // Draw all of the tick labels, tick marks, and grid lines at the correct places + helpers.each(itemsToDraw, function(itemToDraw) { + if (gridLines.display) { + context.save(); + context.lineWidth = itemToDraw.glWidth; + context.strokeStyle = itemToDraw.glColor; + if (context.setLineDash) { + context.setLineDash(itemToDraw.glBorderDash); + context.lineDashOffset = itemToDraw.glBorderDashOffset; + } + + context.beginPath(); + + if (gridLines.drawTicks) { + context.moveTo(itemToDraw.tx1, itemToDraw.ty1); + context.lineTo(itemToDraw.tx2, itemToDraw.ty2); + } + + if (gridLines.drawOnChartArea) { + context.moveTo(itemToDraw.x1, itemToDraw.y1); + context.lineTo(itemToDraw.x2, itemToDraw.y2); + } + + context.stroke(); + context.restore(); + } + + if (optionTicks.display) { + // Make sure we draw text in the correct color and font + context.save(); + context.translate(itemToDraw.labelX, itemToDraw.labelY); + context.rotate(itemToDraw.rotation); + context.font = itemToDraw.major ? majorTickFont.font : tickFont.font; + context.fillStyle = itemToDraw.major ? majorTickFontColor : tickFontColor; + context.textBaseline = itemToDraw.textBaseline; + context.textAlign = itemToDraw.textAlign; + + var label = itemToDraw.label; + if (helpers.isArray(label)) { + var lineCount = label.length; + var lineHeight = tickFont.size * 1.5; + var y = me.isHorizontal() ? 0 : -lineHeight * (lineCount - 1) / 2; + + for (var i = 0; i < lineCount; ++i) { + // We just make sure the multiline element is a string here.. + context.fillText('' + label[i], 0, y); + // apply same lineSpacing as calculated @ L#320 + y += lineHeight; + } + } else { + context.fillText(label, 0, 0); + } + context.restore(); + } + }); + + if (scaleLabel.display) { + // Draw the scale label + var scaleLabelX; + var scaleLabelY; + var rotation = 0; + var halfLineHeight = parseLineHeight(scaleLabel) / 2; + + if (isHorizontal) { + scaleLabelX = me.left + ((me.right - me.left) / 2); // midpoint of the width + scaleLabelY = options.position === 'bottom' + ? me.bottom - halfLineHeight - scaleLabelPadding.bottom + : me.top + halfLineHeight + scaleLabelPadding.top; + } else { + var isLeft = options.position === 'left'; + scaleLabelX = isLeft + ? me.left + halfLineHeight + scaleLabelPadding.top + : me.right - halfLineHeight - scaleLabelPadding.top; + scaleLabelY = me.top + ((me.bottom - me.top) / 2); + rotation = isLeft ? -0.5 * Math.PI : 0.5 * Math.PI; + } + + context.save(); + context.translate(scaleLabelX, scaleLabelY); + context.rotate(rotation); + context.textAlign = 'center'; + context.textBaseline = 'middle'; + context.fillStyle = scaleLabelFontColor; // render in correct colour + context.font = scaleLabelFont.font; + context.fillText(scaleLabel.labelString, 0, 0); + context.restore(); + } + + if (gridLines.drawBorder) { + // Draw the line at the edge of the axis + context.lineWidth = helpers.valueAtIndexOrDefault(gridLines.lineWidth, 0); + context.strokeStyle = helpers.valueAtIndexOrDefault(gridLines.color, 0); + var x1 = me.left; + var x2 = me.right + axisWidth; + var y1 = me.top; + var y2 = me.bottom + axisWidth; + + var aliasPixel = helpers.aliasPixel(context.lineWidth); + if (isHorizontal) { + y1 = y2 = options.position === 'top' ? me.bottom : me.top; + y1 += aliasPixel; + y2 += aliasPixel; + } else { + x1 = x2 = options.position === 'left' ? me.right : me.left; + x1 += aliasPixel; + x2 += aliasPixel; + } + + context.beginPath(); + context.moveTo(x1, y1); + context.lineTo(x2, y2); + context.stroke(); + } + } +}); + +},{"26":26,"27":27,"35":35,"46":46}],34:[function(require,module,exports){ +'use strict'; + +var defaults = require(26); +var helpers = require(46); +var layouts = require(31); + +module.exports = { + // Scale registration object. Extensions can register new scale types (such as log or DB scales) and then + // use the new chart options to grab the correct scale + constructors: {}, + // Use a registration function so that we can move to an ES6 map when we no longer need to support + // old browsers + + // Scale config defaults + defaults: {}, + registerScaleType: function(type, scaleConstructor, scaleDefaults) { + this.constructors[type] = scaleConstructor; + this.defaults[type] = helpers.clone(scaleDefaults); + }, + getScaleConstructor: function(type) { + return this.constructors.hasOwnProperty(type) ? this.constructors[type] : undefined; + }, + getScaleDefaults: function(type) { + // Return the scale defaults merged with the global settings so that we always use the latest ones + return this.defaults.hasOwnProperty(type) ? helpers.merge({}, [defaults.scale, this.defaults[type]]) : {}; + }, + updateScaleDefaults: function(type, additions) { + var me = this; + if (me.defaults.hasOwnProperty(type)) { + me.defaults[type] = helpers.extend(me.defaults[type], additions); + } + }, + addScalesToLayout: function(chart) { + // Adds each scale to the chart.boxes array to be sized accordingly + helpers.each(chart.scales, function(scale) { + // Set ILayoutItem parameters for backwards compatibility + scale.fullWidth = scale.options.fullWidth; + scale.position = scale.options.position; + scale.weight = scale.options.weight; + layouts.addBox(chart, scale); + }); + } +}; + +},{"26":26,"31":31,"46":46}],35:[function(require,module,exports){ +'use strict'; + +var helpers = require(46); + +/** + * Namespace to hold static tick generation functions + * @namespace Chart.Ticks + */ +module.exports = { + /** + * Namespace to hold formatters for different types of ticks + * @namespace Chart.Ticks.formatters + */ + formatters: { + /** + * Formatter for value labels + * @method Chart.Ticks.formatters.values + * @param value the value to display + * @return {String|Array} the label to display + */ + values: function(value) { + return helpers.isArray(value) ? value : '' + value; + }, + + /** + * Formatter for linear numeric ticks + * @method Chart.Ticks.formatters.linear + * @param tickValue {Number} the value to be formatted + * @param index {Number} the position of the tickValue parameter in the ticks array + * @param ticks {Array} the list of ticks being converted + * @return {String} string representation of the tickValue parameter + */ + linear: function(tickValue, index, ticks) { + // If we have lots of ticks, don't use the ones + var delta = ticks.length > 3 ? ticks[2] - ticks[1] : ticks[1] - ticks[0]; + + // If we have a number like 2.5 as the delta, figure out how many decimal places we need + if (Math.abs(delta) > 1) { + if (tickValue !== Math.floor(tickValue)) { + // not an integer + delta = tickValue - Math.floor(tickValue); + } + } + + var logDelta = helpers.log10(Math.abs(delta)); + var tickString = ''; + + if (tickValue !== 0) { + var maxTick = Math.max(Math.abs(ticks[0]), Math.abs(ticks[ticks.length - 1])); + if (maxTick < 1e-4) { // all ticks are small numbers; use scientific notation + var logTick = helpers.log10(Math.abs(tickValue)); + tickString = tickValue.toExponential(Math.floor(logTick) - Math.floor(logDelta)); + } else { + var numDecimal = -1 * Math.floor(logDelta); + numDecimal = Math.max(Math.min(numDecimal, 20), 0); // toFixed has a max of 20 decimal places + tickString = tickValue.toFixed(numDecimal); + } + } else { + tickString = '0'; // never show decimal places for 0 + } + + return tickString; + }, + + logarithmic: function(tickValue, index, ticks) { + var remain = tickValue / (Math.pow(10, Math.floor(helpers.log10(tickValue)))); + + if (tickValue === 0) { + return '0'; + } else if (remain === 1 || remain === 2 || remain === 5 || index === 0 || index === ticks.length - 1) { + return tickValue.toExponential(); + } + return ''; + } + } +}; + +},{"46":46}],36:[function(require,module,exports){ +'use strict'; + +var defaults = require(26); +var Element = require(27); +var helpers = require(46); + +defaults._set('global', { + tooltips: { + enabled: true, + custom: null, + mode: 'nearest', + position: 'average', + intersect: true, + backgroundColor: 'rgba(0,0,0,0.8)', + titleFontStyle: 'bold', + titleSpacing: 2, + titleMarginBottom: 6, + titleFontColor: '#fff', + titleAlign: 'left', + bodySpacing: 2, + bodyFontColor: '#fff', + bodyAlign: 'left', + footerFontStyle: 'bold', + footerSpacing: 2, + footerMarginTop: 6, + footerFontColor: '#fff', + footerAlign: 'left', + yPadding: 6, + xPadding: 6, + caretPadding: 2, + caretSize: 5, + cornerRadius: 6, + multiKeyBackground: '#fff', + displayColors: true, + borderColor: 'rgba(0,0,0,0)', + borderWidth: 0, + callbacks: { + // Args are: (tooltipItems, data) + beforeTitle: helpers.noop, + title: function(tooltipItems, data) { + // Pick first xLabel for now + var title = ''; + var labels = data.labels; + var labelCount = labels ? labels.length : 0; + + if (tooltipItems.length > 0) { + var item = tooltipItems[0]; + + if (item.xLabel) { + title = item.xLabel; + } else if (labelCount > 0 && item.index < labelCount) { + title = labels[item.index]; + } + } + + return title; + }, + afterTitle: helpers.noop, + + // Args are: (tooltipItems, data) + beforeBody: helpers.noop, + + // Args are: (tooltipItem, data) + beforeLabel: helpers.noop, + label: function(tooltipItem, data) { + var label = data.datasets[tooltipItem.datasetIndex].label || ''; + + if (label) { + label += ': '; + } + label += tooltipItem.yLabel; + return label; + }, + labelColor: function(tooltipItem, chart) { + var meta = chart.getDatasetMeta(tooltipItem.datasetIndex); + var activeElement = meta.data[tooltipItem.index]; + var view = activeElement._view; + return { + borderColor: view.borderColor, + backgroundColor: view.backgroundColor + }; + }, + labelTextColor: function() { + return this._options.bodyFontColor; + }, + afterLabel: helpers.noop, + + // Args are: (tooltipItems, data) + afterBody: helpers.noop, + + // Args are: (tooltipItems, data) + beforeFooter: helpers.noop, + footer: helpers.noop, + afterFooter: helpers.noop + } + } +}); + +var positioners = { + /** + * Average mode places the tooltip at the average position of the elements shown + * @function Chart.Tooltip.positioners.average + * @param elements {ChartElement[]} the elements being displayed in the tooltip + * @returns {Point} tooltip position + */ + average: function(elements) { + if (!elements.length) { + return false; + } + + var i, len; + var x = 0; + var y = 0; + var count = 0; + + for (i = 0, len = elements.length; i < len; ++i) { + var el = elements[i]; + if (el && el.hasValue()) { + var pos = el.tooltipPosition(); + x += pos.x; + y += pos.y; + ++count; + } + } + + return { + x: Math.round(x / count), + y: Math.round(y / count) + }; + }, + + /** + * Gets the tooltip position nearest of the item nearest to the event position + * @function Chart.Tooltip.positioners.nearest + * @param elements {Chart.Element[]} the tooltip elements + * @param eventPosition {Point} the position of the event in canvas coordinates + * @returns {Point} the tooltip position + */ + nearest: function(elements, eventPosition) { + var x = eventPosition.x; + var y = eventPosition.y; + var minDistance = Number.POSITIVE_INFINITY; + var i, len, nearestElement; + + for (i = 0, len = elements.length; i < len; ++i) { + var el = elements[i]; + if (el && el.hasValue()) { + var center = el.getCenterPoint(); + var d = helpers.distanceBetweenPoints(eventPosition, center); + + if (d < minDistance) { + minDistance = d; + nearestElement = el; + } + } + } + + if (nearestElement) { + var tp = nearestElement.tooltipPosition(); + x = tp.x; + y = tp.y; + } + + return { + x: x, + y: y + }; + } +}; + +/** + * Helper method to merge the opacity into a color + */ +function mergeOpacity(colorString, opacity) { + var color = helpers.color(colorString); + return color.alpha(opacity * color.alpha()).rgbaString(); +} + +// Helper to push or concat based on if the 2nd parameter is an array or not +function pushOrConcat(base, toPush) { + if (toPush) { + if (helpers.isArray(toPush)) { + // base = base.concat(toPush); + Array.prototype.push.apply(base, toPush); + } else { + base.push(toPush); + } + } + + return base; +} + +/** + * Returns array of strings split by newline + * @param {String} value - The value to split by newline. + * @returns {Array} value if newline present - Returned from String split() method + * @function + */ +function splitNewlines(str) { + if ((typeof str === 'string' || str instanceof String) && str.indexOf('\n') > -1) { + return str.split('\n'); + } + return str; +} + + +// Private helper to create a tooltip item model +// @param element : the chart element (point, arc, bar) to create the tooltip item for +// @return : new tooltip item +function createTooltipItem(element) { + var xScale = element._xScale; + var yScale = element._yScale || element._scale; // handle radar || polarArea charts + var index = element._index; + var datasetIndex = element._datasetIndex; + + return { + xLabel: xScale ? xScale.getLabelForIndex(index, datasetIndex) : '', + yLabel: yScale ? yScale.getLabelForIndex(index, datasetIndex) : '', + index: index, + datasetIndex: datasetIndex, + x: element._model.x, + y: element._model.y + }; +} + +/** + * Helper to get the reset model for the tooltip + * @param tooltipOpts {Object} the tooltip options + */ +function getBaseModel(tooltipOpts) { + var globalDefaults = defaults.global; + var valueOrDefault = helpers.valueOrDefault; + + return { + // Positioning + xPadding: tooltipOpts.xPadding, + yPadding: tooltipOpts.yPadding, + xAlign: tooltipOpts.xAlign, + yAlign: tooltipOpts.yAlign, + + // Body + bodyFontColor: tooltipOpts.bodyFontColor, + _bodyFontFamily: valueOrDefault(tooltipOpts.bodyFontFamily, globalDefaults.defaultFontFamily), + _bodyFontStyle: valueOrDefault(tooltipOpts.bodyFontStyle, globalDefaults.defaultFontStyle), + _bodyAlign: tooltipOpts.bodyAlign, + bodyFontSize: valueOrDefault(tooltipOpts.bodyFontSize, globalDefaults.defaultFontSize), + bodySpacing: tooltipOpts.bodySpacing, + + // Title + titleFontColor: tooltipOpts.titleFontColor, + _titleFontFamily: valueOrDefault(tooltipOpts.titleFontFamily, globalDefaults.defaultFontFamily), + _titleFontStyle: valueOrDefault(tooltipOpts.titleFontStyle, globalDefaults.defaultFontStyle), + titleFontSize: valueOrDefault(tooltipOpts.titleFontSize, globalDefaults.defaultFontSize), + _titleAlign: tooltipOpts.titleAlign, + titleSpacing: tooltipOpts.titleSpacing, + titleMarginBottom: tooltipOpts.titleMarginBottom, + + // Footer + footerFontColor: tooltipOpts.footerFontColor, + _footerFontFamily: valueOrDefault(tooltipOpts.footerFontFamily, globalDefaults.defaultFontFamily), + _footerFontStyle: valueOrDefault(tooltipOpts.footerFontStyle, globalDefaults.defaultFontStyle), + footerFontSize: valueOrDefault(tooltipOpts.footerFontSize, globalDefaults.defaultFontSize), + _footerAlign: tooltipOpts.footerAlign, + footerSpacing: tooltipOpts.footerSpacing, + footerMarginTop: tooltipOpts.footerMarginTop, + + // Appearance + caretSize: tooltipOpts.caretSize, + cornerRadius: tooltipOpts.cornerRadius, + backgroundColor: tooltipOpts.backgroundColor, + opacity: 0, + legendColorBackground: tooltipOpts.multiKeyBackground, + displayColors: tooltipOpts.displayColors, + borderColor: tooltipOpts.borderColor, + borderWidth: tooltipOpts.borderWidth + }; +} + +/** + * Get the size of the tooltip + */ +function getTooltipSize(tooltip, model) { + var ctx = tooltip._chart.ctx; + + var height = model.yPadding * 2; // Tooltip Padding + var width = 0; + + // Count of all lines in the body + var body = model.body; + var combinedBodyLength = body.reduce(function(count, bodyItem) { + return count + bodyItem.before.length + bodyItem.lines.length + bodyItem.after.length; + }, 0); + combinedBodyLength += model.beforeBody.length + model.afterBody.length; + + var titleLineCount = model.title.length; + var footerLineCount = model.footer.length; + var titleFontSize = model.titleFontSize; + var bodyFontSize = model.bodyFontSize; + var footerFontSize = model.footerFontSize; + + height += titleLineCount * titleFontSize; // Title Lines + height += titleLineCount ? (titleLineCount - 1) * model.titleSpacing : 0; // Title Line Spacing + height += titleLineCount ? model.titleMarginBottom : 0; // Title's bottom Margin + height += combinedBodyLength * bodyFontSize; // Body Lines + height += combinedBodyLength ? (combinedBodyLength - 1) * model.bodySpacing : 0; // Body Line Spacing + height += footerLineCount ? model.footerMarginTop : 0; // Footer Margin + height += footerLineCount * (footerFontSize); // Footer Lines + height += footerLineCount ? (footerLineCount - 1) * model.footerSpacing : 0; // Footer Line Spacing + + // Title width + var widthPadding = 0; + var maxLineWidth = function(line) { + width = Math.max(width, ctx.measureText(line).width + widthPadding); + }; + + ctx.font = helpers.fontString(titleFontSize, model._titleFontStyle, model._titleFontFamily); + helpers.each(model.title, maxLineWidth); + + // Body width + ctx.font = helpers.fontString(bodyFontSize, model._bodyFontStyle, model._bodyFontFamily); + helpers.each(model.beforeBody.concat(model.afterBody), maxLineWidth); + + // Body lines may include some extra width due to the color box + widthPadding = model.displayColors ? (bodyFontSize + 2) : 0; + helpers.each(body, function(bodyItem) { + helpers.each(bodyItem.before, maxLineWidth); + helpers.each(bodyItem.lines, maxLineWidth); + helpers.each(bodyItem.after, maxLineWidth); + }); + + // Reset back to 0 + widthPadding = 0; + + // Footer width + ctx.font = helpers.fontString(footerFontSize, model._footerFontStyle, model._footerFontFamily); + helpers.each(model.footer, maxLineWidth); + + // Add padding + width += 2 * model.xPadding; + + return { + width: width, + height: height + }; +} + +/** + * Helper to get the alignment of a tooltip given the size + */ +function determineAlignment(tooltip, size) { + var model = tooltip._model; + var chart = tooltip._chart; + var chartArea = tooltip._chart.chartArea; + var xAlign = 'center'; + var yAlign = 'center'; + + if (model.y < size.height) { + yAlign = 'top'; + } else if (model.y > (chart.height - size.height)) { + yAlign = 'bottom'; + } + + var lf, rf; // functions to determine left, right alignment + var olf, orf; // functions to determine if left/right alignment causes tooltip to go outside chart + var yf; // function to get the y alignment if the tooltip goes outside of the left or right edges + var midX = (chartArea.left + chartArea.right) / 2; + var midY = (chartArea.top + chartArea.bottom) / 2; + + if (yAlign === 'center') { + lf = function(x) { + return x <= midX; + }; + rf = function(x) { + return x > midX; + }; + } else { + lf = function(x) { + return x <= (size.width / 2); + }; + rf = function(x) { + return x >= (chart.width - (size.width / 2)); + }; + } + + olf = function(x) { + return x + size.width + model.caretSize + model.caretPadding > chart.width; + }; + orf = function(x) { + return x - size.width - model.caretSize - model.caretPadding < 0; + }; + yf = function(y) { + return y <= midY ? 'top' : 'bottom'; + }; + + if (lf(model.x)) { + xAlign = 'left'; + + // Is tooltip too wide and goes over the right side of the chart.? + if (olf(model.x)) { + xAlign = 'center'; + yAlign = yf(model.y); + } + } else if (rf(model.x)) { + xAlign = 'right'; + + // Is tooltip too wide and goes outside left edge of canvas? + if (orf(model.x)) { + xAlign = 'center'; + yAlign = yf(model.y); + } + } + + var opts = tooltip._options; + return { + xAlign: opts.xAlign ? opts.xAlign : xAlign, + yAlign: opts.yAlign ? opts.yAlign : yAlign + }; +} + +/** + * Helper to get the location a tooltip needs to be placed at given the initial position (via the vm) and the size and alignment + */ +function getBackgroundPoint(vm, size, alignment, chart) { + // Background Position + var x = vm.x; + var y = vm.y; + + var caretSize = vm.caretSize; + var caretPadding = vm.caretPadding; + var cornerRadius = vm.cornerRadius; + var xAlign = alignment.xAlign; + var yAlign = alignment.yAlign; + var paddingAndSize = caretSize + caretPadding; + var radiusAndPadding = cornerRadius + caretPadding; + + if (xAlign === 'right') { + x -= size.width; + } else if (xAlign === 'center') { + x -= (size.width / 2); + if (x + size.width > chart.width) { + x = chart.width - size.width; + } + if (x < 0) { + x = 0; + } + } + + if (yAlign === 'top') { + y += paddingAndSize; + } else if (yAlign === 'bottom') { + y -= size.height + paddingAndSize; + } else { + y -= (size.height / 2); + } + + if (yAlign === 'center') { + if (xAlign === 'left') { + x += paddingAndSize; + } else if (xAlign === 'right') { + x -= paddingAndSize; + } + } else if (xAlign === 'left') { + x -= radiusAndPadding; + } else if (xAlign === 'right') { + x += radiusAndPadding; + } + + return { + x: x, + y: y + }; +} + +/** + * Helper to build before and after body lines + */ +function getBeforeAfterBodyLines(callback) { + return pushOrConcat([], splitNewlines(callback)); +} + +var exports = module.exports = Element.extend({ + initialize: function() { + this._model = getBaseModel(this._options); + this._lastActive = []; + }, + + // Get the title + // Args are: (tooltipItem, data) + getTitle: function() { + var me = this; + var opts = me._options; + var callbacks = opts.callbacks; + + var beforeTitle = callbacks.beforeTitle.apply(me, arguments); + var title = callbacks.title.apply(me, arguments); + var afterTitle = callbacks.afterTitle.apply(me, arguments); + + var lines = []; + lines = pushOrConcat(lines, splitNewlines(beforeTitle)); + lines = pushOrConcat(lines, splitNewlines(title)); + lines = pushOrConcat(lines, splitNewlines(afterTitle)); + + return lines; + }, + + // Args are: (tooltipItem, data) + getBeforeBody: function() { + return getBeforeAfterBodyLines(this._options.callbacks.beforeBody.apply(this, arguments)); + }, + + // Args are: (tooltipItem, data) + getBody: function(tooltipItems, data) { + var me = this; + var callbacks = me._options.callbacks; + var bodyItems = []; + + helpers.each(tooltipItems, function(tooltipItem) { + var bodyItem = { + before: [], + lines: [], + after: [] + }; + pushOrConcat(bodyItem.before, splitNewlines(callbacks.beforeLabel.call(me, tooltipItem, data))); + pushOrConcat(bodyItem.lines, callbacks.label.call(me, tooltipItem, data)); + pushOrConcat(bodyItem.after, splitNewlines(callbacks.afterLabel.call(me, tooltipItem, data))); + + bodyItems.push(bodyItem); + }); + + return bodyItems; + }, + + // Args are: (tooltipItem, data) + getAfterBody: function() { + return getBeforeAfterBodyLines(this._options.callbacks.afterBody.apply(this, arguments)); + }, + + // Get the footer and beforeFooter and afterFooter lines + // Args are: (tooltipItem, data) + getFooter: function() { + var me = this; + var callbacks = me._options.callbacks; + + var beforeFooter = callbacks.beforeFooter.apply(me, arguments); + var footer = callbacks.footer.apply(me, arguments); + var afterFooter = callbacks.afterFooter.apply(me, arguments); + + var lines = []; + lines = pushOrConcat(lines, splitNewlines(beforeFooter)); + lines = pushOrConcat(lines, splitNewlines(footer)); + lines = pushOrConcat(lines, splitNewlines(afterFooter)); + + return lines; + }, + + update: function(changed) { + var me = this; + var opts = me._options; + + // Need to regenerate the model because its faster than using extend and it is necessary due to the optimization in Chart.Element.transition + // that does _view = _model if ease === 1. This causes the 2nd tooltip update to set properties in both the view and model at the same time + // which breaks any animations. + var existingModel = me._model; + var model = me._model = getBaseModel(opts); + var active = me._active; + + var data = me._data; + + // In the case where active.length === 0 we need to keep these at existing values for good animations + var alignment = { + xAlign: existingModel.xAlign, + yAlign: existingModel.yAlign + }; + var backgroundPoint = { + x: existingModel.x, + y: existingModel.y + }; + var tooltipSize = { + width: existingModel.width, + height: existingModel.height + }; + var tooltipPosition = { + x: existingModel.caretX, + y: existingModel.caretY + }; + + var i, len; + + if (active.length) { + model.opacity = 1; + + var labelColors = []; + var labelTextColors = []; + tooltipPosition = positioners[opts.position].call(me, active, me._eventPosition); + + var tooltipItems = []; + for (i = 0, len = active.length; i < len; ++i) { + tooltipItems.push(createTooltipItem(active[i])); + } + + // If the user provided a filter function, use it to modify the tooltip items + if (opts.filter) { + tooltipItems = tooltipItems.filter(function(a) { + return opts.filter(a, data); + }); + } + + // If the user provided a sorting function, use it to modify the tooltip items + if (opts.itemSort) { + tooltipItems = tooltipItems.sort(function(a, b) { + return opts.itemSort(a, b, data); + }); + } + + // Determine colors for boxes + helpers.each(tooltipItems, function(tooltipItem) { + labelColors.push(opts.callbacks.labelColor.call(me, tooltipItem, me._chart)); + labelTextColors.push(opts.callbacks.labelTextColor.call(me, tooltipItem, me._chart)); + }); + + + // Build the Text Lines + model.title = me.getTitle(tooltipItems, data); + model.beforeBody = me.getBeforeBody(tooltipItems, data); + model.body = me.getBody(tooltipItems, data); + model.afterBody = me.getAfterBody(tooltipItems, data); + model.footer = me.getFooter(tooltipItems, data); + + // Initial positioning and colors + model.x = Math.round(tooltipPosition.x); + model.y = Math.round(tooltipPosition.y); + model.caretPadding = opts.caretPadding; + model.labelColors = labelColors; + model.labelTextColors = labelTextColors; + + // data points + model.dataPoints = tooltipItems; + + // We need to determine alignment of the tooltip + tooltipSize = getTooltipSize(this, model); + alignment = determineAlignment(this, tooltipSize); + // Final Size and Position + backgroundPoint = getBackgroundPoint(model, tooltipSize, alignment, me._chart); + } else { + model.opacity = 0; + } + + model.xAlign = alignment.xAlign; + model.yAlign = alignment.yAlign; + model.x = backgroundPoint.x; + model.y = backgroundPoint.y; + model.width = tooltipSize.width; + model.height = tooltipSize.height; + + // Point where the caret on the tooltip points to + model.caretX = tooltipPosition.x; + model.caretY = tooltipPosition.y; + + me._model = model; + + if (changed && opts.custom) { + opts.custom.call(me, model); + } + + return me; + }, + + drawCaret: function(tooltipPoint, size) { + var ctx = this._chart.ctx; + var vm = this._view; + var caretPosition = this.getCaretPosition(tooltipPoint, size, vm); + + ctx.lineTo(caretPosition.x1, caretPosition.y1); + ctx.lineTo(caretPosition.x2, caretPosition.y2); + ctx.lineTo(caretPosition.x3, caretPosition.y3); + }, + getCaretPosition: function(tooltipPoint, size, vm) { + var x1, x2, x3, y1, y2, y3; + var caretSize = vm.caretSize; + var cornerRadius = vm.cornerRadius; + var xAlign = vm.xAlign; + var yAlign = vm.yAlign; + var ptX = tooltipPoint.x; + var ptY = tooltipPoint.y; + var width = size.width; + var height = size.height; + + if (yAlign === 'center') { + y2 = ptY + (height / 2); + + if (xAlign === 'left') { + x1 = ptX; + x2 = x1 - caretSize; + x3 = x1; + + y1 = y2 + caretSize; + y3 = y2 - caretSize; + } else { + x1 = ptX + width; + x2 = x1 + caretSize; + x3 = x1; + + y1 = y2 - caretSize; + y3 = y2 + caretSize; + } + } else { + if (xAlign === 'left') { + x2 = ptX + cornerRadius + (caretSize); + x1 = x2 - caretSize; + x3 = x2 + caretSize; + } else if (xAlign === 'right') { + x2 = ptX + width - cornerRadius - caretSize; + x1 = x2 - caretSize; + x3 = x2 + caretSize; + } else { + x2 = vm.caretX; + x1 = x2 - caretSize; + x3 = x2 + caretSize; + } + if (yAlign === 'top') { + y1 = ptY; + y2 = y1 - caretSize; + y3 = y1; + } else { + y1 = ptY + height; + y2 = y1 + caretSize; + y3 = y1; + // invert drawing order + var tmp = x3; + x3 = x1; + x1 = tmp; + } + } + return {x1: x1, x2: x2, x3: x3, y1: y1, y2: y2, y3: y3}; + }, + + drawTitle: function(pt, vm, ctx, opacity) { + var title = vm.title; + + if (title.length) { + ctx.textAlign = vm._titleAlign; + ctx.textBaseline = 'top'; + + var titleFontSize = vm.titleFontSize; + var titleSpacing = vm.titleSpacing; + + ctx.fillStyle = mergeOpacity(vm.titleFontColor, opacity); + ctx.font = helpers.fontString(titleFontSize, vm._titleFontStyle, vm._titleFontFamily); + + var i, len; + for (i = 0, len = title.length; i < len; ++i) { + ctx.fillText(title[i], pt.x, pt.y); + pt.y += titleFontSize + titleSpacing; // Line Height and spacing + + if (i + 1 === title.length) { + pt.y += vm.titleMarginBottom - titleSpacing; // If Last, add margin, remove spacing + } + } + } + }, + + drawBody: function(pt, vm, ctx, opacity) { + var bodyFontSize = vm.bodyFontSize; + var bodySpacing = vm.bodySpacing; + var body = vm.body; + + ctx.textAlign = vm._bodyAlign; + ctx.textBaseline = 'top'; + ctx.font = helpers.fontString(bodyFontSize, vm._bodyFontStyle, vm._bodyFontFamily); + + // Before Body + var xLinePadding = 0; + var fillLineOfText = function(line) { + ctx.fillText(line, pt.x + xLinePadding, pt.y); + pt.y += bodyFontSize + bodySpacing; + }; + + // Before body lines + ctx.fillStyle = mergeOpacity(vm.bodyFontColor, opacity); + helpers.each(vm.beforeBody, fillLineOfText); + + var drawColorBoxes = vm.displayColors; + xLinePadding = drawColorBoxes ? (bodyFontSize + 2) : 0; + + // Draw body lines now + helpers.each(body, function(bodyItem, i) { + var textColor = mergeOpacity(vm.labelTextColors[i], opacity); + ctx.fillStyle = textColor; + helpers.each(bodyItem.before, fillLineOfText); + + helpers.each(bodyItem.lines, function(line) { + // Draw Legend-like boxes if needed + if (drawColorBoxes) { + // Fill a white rect so that colours merge nicely if the opacity is < 1 + ctx.fillStyle = mergeOpacity(vm.legendColorBackground, opacity); + ctx.fillRect(pt.x, pt.y, bodyFontSize, bodyFontSize); + + // Border + ctx.lineWidth = 1; + ctx.strokeStyle = mergeOpacity(vm.labelColors[i].borderColor, opacity); + ctx.strokeRect(pt.x, pt.y, bodyFontSize, bodyFontSize); + + // Inner square + ctx.fillStyle = mergeOpacity(vm.labelColors[i].backgroundColor, opacity); + ctx.fillRect(pt.x + 1, pt.y + 1, bodyFontSize - 2, bodyFontSize - 2); + ctx.fillStyle = textColor; + } + + fillLineOfText(line); + }); + + helpers.each(bodyItem.after, fillLineOfText); + }); + + // Reset back to 0 for after body + xLinePadding = 0; + + // After body lines + helpers.each(vm.afterBody, fillLineOfText); + pt.y -= bodySpacing; // Remove last body spacing + }, + + drawFooter: function(pt, vm, ctx, opacity) { + var footer = vm.footer; + + if (footer.length) { + pt.y += vm.footerMarginTop; + + ctx.textAlign = vm._footerAlign; + ctx.textBaseline = 'top'; + + ctx.fillStyle = mergeOpacity(vm.footerFontColor, opacity); + ctx.font = helpers.fontString(vm.footerFontSize, vm._footerFontStyle, vm._footerFontFamily); + + helpers.each(footer, function(line) { + ctx.fillText(line, pt.x, pt.y); + pt.y += vm.footerFontSize + vm.footerSpacing; + }); + } + }, + + drawBackground: function(pt, vm, ctx, tooltipSize, opacity) { + ctx.fillStyle = mergeOpacity(vm.backgroundColor, opacity); + ctx.strokeStyle = mergeOpacity(vm.borderColor, opacity); + ctx.lineWidth = vm.borderWidth; + var xAlign = vm.xAlign; + var yAlign = vm.yAlign; + var x = pt.x; + var y = pt.y; + var width = tooltipSize.width; + var height = tooltipSize.height; + var radius = vm.cornerRadius; + + ctx.beginPath(); + ctx.moveTo(x + radius, y); + if (yAlign === 'top') { + this.drawCaret(pt, tooltipSize); + } + ctx.lineTo(x + width - radius, y); + ctx.quadraticCurveTo(x + width, y, x + width, y + radius); + if (yAlign === 'center' && xAlign === 'right') { + this.drawCaret(pt, tooltipSize); + } + ctx.lineTo(x + width, y + height - radius); + ctx.quadraticCurveTo(x + width, y + height, x + width - radius, y + height); + if (yAlign === 'bottom') { + this.drawCaret(pt, tooltipSize); + } + ctx.lineTo(x + radius, y + height); + ctx.quadraticCurveTo(x, y + height, x, y + height - radius); + if (yAlign === 'center' && xAlign === 'left') { + this.drawCaret(pt, tooltipSize); + } + ctx.lineTo(x, y + radius); + ctx.quadraticCurveTo(x, y, x + radius, y); + ctx.closePath(); + + ctx.fill(); + + if (vm.borderWidth > 0) { + ctx.stroke(); + } + }, + + draw: function() { + var ctx = this._chart.ctx; + var vm = this._view; + + if (vm.opacity === 0) { + return; + } + + var tooltipSize = { + width: vm.width, + height: vm.height + }; + var pt = { + x: vm.x, + y: vm.y + }; + + // IE11/Edge does not like very small opacities, so snap to 0 + var opacity = Math.abs(vm.opacity < 1e-3) ? 0 : vm.opacity; + + // Truthy/falsey value for empty tooltip + var hasTooltipContent = vm.title.length || vm.beforeBody.length || vm.body.length || vm.afterBody.length || vm.footer.length; + + if (this._options.enabled && hasTooltipContent) { + // Draw Background + this.drawBackground(pt, vm, ctx, tooltipSize, opacity); + + // Draw Title, Body, and Footer + pt.x += vm.xPadding; + pt.y += vm.yPadding; + + // Titles + this.drawTitle(pt, vm, ctx, opacity); + + // Body + this.drawBody(pt, vm, ctx, opacity); + + // Footer + this.drawFooter(pt, vm, ctx, opacity); + } + }, + + /** + * Handle an event + * @private + * @param {IEvent} event - The event to handle + * @returns {Boolean} true if the tooltip changed + */ + handleEvent: function(e) { + var me = this; + var options = me._options; + var changed = false; + + me._lastActive = me._lastActive || []; + + // Find Active Elements for tooltips + if (e.type === 'mouseout') { + me._active = []; + } else { + me._active = me._chart.getElementsAtEventForMode(e, options.mode, options); + } + + // Remember Last Actives + changed = !helpers.arrayEquals(me._active, me._lastActive); + + // Only handle target event on tooltip change + if (changed) { + me._lastActive = me._active; + + if (options.enabled || options.custom) { + me._eventPosition = { + x: e.x, + y: e.y + }; + + me.update(true); + me.pivot(); + } + } + + return changed; + } +}); + +/** + * @namespace Chart.Tooltip.positioners + */ +exports.positioners = positioners; + + +},{"26":26,"27":27,"46":46}],37:[function(require,module,exports){ +'use strict'; + +var defaults = require(26); +var Element = require(27); +var helpers = require(46); + +defaults._set('global', { + elements: { + arc: { + backgroundColor: defaults.global.defaultColor, + borderColor: '#fff', + borderWidth: 2 + } + } +}); + +module.exports = Element.extend({ + inLabelRange: function(mouseX) { + var vm = this._view; + + if (vm) { + return (Math.pow(mouseX - vm.x, 2) < Math.pow(vm.radius + vm.hoverRadius, 2)); + } + return false; + }, + + inRange: function(chartX, chartY) { + var vm = this._view; + + if (vm) { + var pointRelativePosition = helpers.getAngleFromPoint(vm, {x: chartX, y: chartY}); + var angle = pointRelativePosition.angle; + var distance = pointRelativePosition.distance; + + // Sanitise angle range + var startAngle = vm.startAngle; + var endAngle = vm.endAngle; + while (endAngle < startAngle) { + endAngle += 2.0 * Math.PI; + } + while (angle > endAngle) { + angle -= 2.0 * Math.PI; + } + while (angle < startAngle) { + angle += 2.0 * Math.PI; + } + + // Check if within the range of the open/close angle + var betweenAngles = (angle >= startAngle && angle <= endAngle); + var withinRadius = (distance >= vm.innerRadius && distance <= vm.outerRadius); + + return (betweenAngles && withinRadius); + } + return false; + }, + + getCenterPoint: function() { + var vm = this._view; + var halfAngle = (vm.startAngle + vm.endAngle) / 2; + var halfRadius = (vm.innerRadius + vm.outerRadius) / 2; + return { + x: vm.x + Math.cos(halfAngle) * halfRadius, + y: vm.y + Math.sin(halfAngle) * halfRadius + }; + }, + + getArea: function() { + var vm = this._view; + return Math.PI * ((vm.endAngle - vm.startAngle) / (2 * Math.PI)) * (Math.pow(vm.outerRadius, 2) - Math.pow(vm.innerRadius, 2)); + }, + + tooltipPosition: function() { + var vm = this._view; + var centreAngle = vm.startAngle + ((vm.endAngle - vm.startAngle) / 2); + var rangeFromCentre = (vm.outerRadius - vm.innerRadius) / 2 + vm.innerRadius; + + return { + x: vm.x + (Math.cos(centreAngle) * rangeFromCentre), + y: vm.y + (Math.sin(centreAngle) * rangeFromCentre) + }; + }, + + draw: function() { + var ctx = this._chart.ctx; + var vm = this._view; + var sA = vm.startAngle; + var eA = vm.endAngle; + + ctx.beginPath(); + + ctx.arc(vm.x, vm.y, vm.outerRadius, sA, eA); + ctx.arc(vm.x, vm.y, vm.innerRadius, eA, sA, true); + + ctx.closePath(); + ctx.strokeStyle = vm.borderColor; + ctx.lineWidth = vm.borderWidth; + + ctx.fillStyle = vm.backgroundColor; + + ctx.fill(); + ctx.lineJoin = 'bevel'; + + if (vm.borderWidth) { + ctx.stroke(); + } + } +}); + +},{"26":26,"27":27,"46":46}],38:[function(require,module,exports){ +'use strict'; + +var defaults = require(26); +var Element = require(27); +var helpers = require(46); + +var globalDefaults = defaults.global; + +defaults._set('global', { + elements: { + line: { + tension: 0.4, + backgroundColor: globalDefaults.defaultColor, + borderWidth: 3, + borderColor: globalDefaults.defaultColor, + borderCapStyle: 'butt', + borderDash: [], + borderDashOffset: 0.0, + borderJoinStyle: 'miter', + capBezierPoints: true, + fill: true, // do we fill in the area between the line and its base axis + } + } +}); + +module.exports = Element.extend({ + draw: function() { + var me = this; + var vm = me._view; + var ctx = me._chart.ctx; + var spanGaps = vm.spanGaps; + var points = me._children.slice(); // clone array + var globalOptionLineElements = globalDefaults.elements.line; + var lastDrawnIndex = -1; + var index, current, previous, currentVM; + + // If we are looping, adding the first point again + if (me._loop && points.length) { + points.push(points[0]); + } + + ctx.save(); + + // Stroke Line Options + ctx.lineCap = vm.borderCapStyle || globalOptionLineElements.borderCapStyle; + + // IE 9 and 10 do not support line dash + if (ctx.setLineDash) { + ctx.setLineDash(vm.borderDash || globalOptionLineElements.borderDash); + } + + ctx.lineDashOffset = vm.borderDashOffset || globalOptionLineElements.borderDashOffset; + ctx.lineJoin = vm.borderJoinStyle || globalOptionLineElements.borderJoinStyle; + ctx.lineWidth = vm.borderWidth || globalOptionLineElements.borderWidth; + ctx.strokeStyle = vm.borderColor || globalDefaults.defaultColor; + + // Stroke Line + ctx.beginPath(); + lastDrawnIndex = -1; + + for (index = 0; index < points.length; ++index) { + current = points[index]; + previous = helpers.previousItem(points, index); + currentVM = current._view; + + // First point moves to it's starting position no matter what + if (index === 0) { + if (!currentVM.skip) { + ctx.moveTo(currentVM.x, currentVM.y); + lastDrawnIndex = index; + } + } else { + previous = lastDrawnIndex === -1 ? previous : points[lastDrawnIndex]; + + if (!currentVM.skip) { + if ((lastDrawnIndex !== (index - 1) && !spanGaps) || lastDrawnIndex === -1) { + // There was a gap and this is the first point after the gap + ctx.moveTo(currentVM.x, currentVM.y); + } else { + // Line to next point + helpers.canvas.lineTo(ctx, previous._view, current._view); + } + lastDrawnIndex = index; + } + } + } + + ctx.stroke(); + ctx.restore(); + } +}); + +},{"26":26,"27":27,"46":46}],39:[function(require,module,exports){ +'use strict'; + +var defaults = require(26); +var Element = require(27); +var helpers = require(46); + +var defaultColor = defaults.global.defaultColor; + +defaults._set('global', { + elements: { + point: { + radius: 3, + pointStyle: 'circle', + backgroundColor: defaultColor, + borderColor: defaultColor, + borderWidth: 1, + // Hover + hitRadius: 1, + hoverRadius: 4, + hoverBorderWidth: 1 + } + } +}); + +function xRange(mouseX) { + var vm = this._view; + return vm ? (Math.abs(mouseX - vm.x) < vm.radius + vm.hitRadius) : false; +} + +function yRange(mouseY) { + var vm = this._view; + return vm ? (Math.abs(mouseY - vm.y) < vm.radius + vm.hitRadius) : false; +} + +module.exports = Element.extend({ + inRange: function(mouseX, mouseY) { + var vm = this._view; + return vm ? ((Math.pow(mouseX - vm.x, 2) + Math.pow(mouseY - vm.y, 2)) < Math.pow(vm.hitRadius + vm.radius, 2)) : false; + }, + + inLabelRange: xRange, + inXRange: xRange, + inYRange: yRange, + + getCenterPoint: function() { + var vm = this._view; + return { + x: vm.x, + y: vm.y + }; + }, + + getArea: function() { + return Math.PI * Math.pow(this._view.radius, 2); + }, + + tooltipPosition: function() { + var vm = this._view; + return { + x: vm.x, + y: vm.y, + padding: vm.radius + vm.borderWidth + }; + }, + + draw: function(chartArea) { + var vm = this._view; + var model = this._model; + var ctx = this._chart.ctx; + var pointStyle = vm.pointStyle; + var rotation = vm.rotation; + var radius = vm.radius; + var x = vm.x; + var y = vm.y; + var errMargin = 1.01; // 1.01 is margin for Accumulated error. (Especially Edge, IE.) + + if (vm.skip) { + return; + } + + // Clipping for Points. + if (chartArea === undefined || (model.x >= chartArea.left && chartArea.right * errMargin >= model.x && model.y >= chartArea.top && chartArea.bottom * errMargin >= model.y)) { + ctx.strokeStyle = vm.borderColor || defaultColor; + ctx.lineWidth = helpers.valueOrDefault(vm.borderWidth, defaults.global.elements.point.borderWidth); + ctx.fillStyle = vm.backgroundColor || defaultColor; + helpers.canvas.drawPoint(ctx, pointStyle, radius, x, y, rotation); + } + } +}); + +},{"26":26,"27":27,"46":46}],40:[function(require,module,exports){ +'use strict'; + +var defaults = require(26); +var Element = require(27); + +defaults._set('global', { + elements: { + rectangle: { + backgroundColor: defaults.global.defaultColor, + borderColor: defaults.global.defaultColor, + borderSkipped: 'bottom', + borderWidth: 0 + } + } +}); + +function isVertical(bar) { + return bar._view.width !== undefined; +} + +/** + * Helper function to get the bounds of the bar regardless of the orientation + * @param bar {Chart.Element.Rectangle} the bar + * @return {Bounds} bounds of the bar + * @private + */ +function getBarBounds(bar) { + var vm = bar._view; + var x1, x2, y1, y2; + + if (isVertical(bar)) { + // vertical + var halfWidth = vm.width / 2; + x1 = vm.x - halfWidth; + x2 = vm.x + halfWidth; + y1 = Math.min(vm.y, vm.base); + y2 = Math.max(vm.y, vm.base); + } else { + // horizontal bar + var halfHeight = vm.height / 2; + x1 = Math.min(vm.x, vm.base); + x2 = Math.max(vm.x, vm.base); + y1 = vm.y - halfHeight; + y2 = vm.y + halfHeight; + } + + return { + left: x1, + top: y1, + right: x2, + bottom: y2 + }; +} + +module.exports = Element.extend({ + draw: function() { + var ctx = this._chart.ctx; + var vm = this._view; + var left, right, top, bottom, signX, signY, borderSkipped; + var borderWidth = vm.borderWidth; + + if (!vm.horizontal) { + // bar + left = vm.x - vm.width / 2; + right = vm.x + vm.width / 2; + top = vm.y; + bottom = vm.base; + signX = 1; + signY = bottom > top ? 1 : -1; + borderSkipped = vm.borderSkipped || 'bottom'; + } else { + // horizontal bar + left = vm.base; + right = vm.x; + top = vm.y - vm.height / 2; + bottom = vm.y + vm.height / 2; + signX = right > left ? 1 : -1; + signY = 1; + borderSkipped = vm.borderSkipped || 'left'; + } + + // Canvas doesn't allow us to stroke inside the width so we can + // adjust the sizes to fit if we're setting a stroke on the line + if (borderWidth) { + // borderWidth shold be less than bar width and bar height. + var barSize = Math.min(Math.abs(left - right), Math.abs(top - bottom)); + borderWidth = borderWidth > barSize ? barSize : borderWidth; + var halfStroke = borderWidth / 2; + // Adjust borderWidth when bar top position is near vm.base(zero). + var borderLeft = left + (borderSkipped !== 'left' ? halfStroke * signX : 0); + var borderRight = right + (borderSkipped !== 'right' ? -halfStroke * signX : 0); + var borderTop = top + (borderSkipped !== 'top' ? halfStroke * signY : 0); + var borderBottom = bottom + (borderSkipped !== 'bottom' ? -halfStroke * signY : 0); + // not become a vertical line? + if (borderLeft !== borderRight) { + top = borderTop; + bottom = borderBottom; + } + // not become a horizontal line? + if (borderTop !== borderBottom) { + left = borderLeft; + right = borderRight; + } + } + + ctx.beginPath(); + ctx.fillStyle = vm.backgroundColor; + ctx.strokeStyle = vm.borderColor; + ctx.lineWidth = borderWidth; + + // Corner points, from bottom-left to bottom-right clockwise + // | 1 2 | + // | 0 3 | + var corners = [ + [left, bottom], + [left, top], + [right, top], + [right, bottom] + ]; + + // Find first (starting) corner with fallback to 'bottom' + var borders = ['bottom', 'left', 'top', 'right']; + var startCorner = borders.indexOf(borderSkipped, 0); + if (startCorner === -1) { + startCorner = 0; + } + + function cornerAt(index) { + return corners[(startCorner + index) % 4]; + } + + // Draw rectangle from 'startCorner' + var corner = cornerAt(0); + ctx.moveTo(corner[0], corner[1]); + + for (var i = 1; i < 4; i++) { + corner = cornerAt(i); + ctx.lineTo(corner[0], corner[1]); + } + + ctx.fill(); + if (borderWidth) { + ctx.stroke(); + } + }, + + height: function() { + var vm = this._view; + return vm.base - vm.y; + }, + + inRange: function(mouseX, mouseY) { + var inRange = false; + + if (this._view) { + var bounds = getBarBounds(this); + inRange = mouseX >= bounds.left && mouseX <= bounds.right && mouseY >= bounds.top && mouseY <= bounds.bottom; + } + + return inRange; + }, + + inLabelRange: function(mouseX, mouseY) { + var me = this; + if (!me._view) { + return false; + } + + var inRange = false; + var bounds = getBarBounds(me); + + if (isVertical(me)) { + inRange = mouseX >= bounds.left && mouseX <= bounds.right; + } else { + inRange = mouseY >= bounds.top && mouseY <= bounds.bottom; + } + + return inRange; + }, + + inXRange: function(mouseX) { + var bounds = getBarBounds(this); + return mouseX >= bounds.left && mouseX <= bounds.right; + }, + + inYRange: function(mouseY) { + var bounds = getBarBounds(this); + return mouseY >= bounds.top && mouseY <= bounds.bottom; + }, + + getCenterPoint: function() { + var vm = this._view; + var x, y; + if (isVertical(this)) { + x = vm.x; + y = (vm.y + vm.base) / 2; + } else { + x = (vm.x + vm.base) / 2; + y = vm.y; + } + + return {x: x, y: y}; + }, + + getArea: function() { + var vm = this._view; + return vm.width * Math.abs(vm.y - vm.base); + }, + + tooltipPosition: function() { + var vm = this._view; + return { + x: vm.x, + y: vm.y + }; + } +}); + +},{"26":26,"27":27}],41:[function(require,module,exports){ +'use strict'; + +module.exports = {}; +module.exports.Arc = require(37); +module.exports.Line = require(38); +module.exports.Point = require(39); +module.exports.Rectangle = require(40); + +},{"37":37,"38":38,"39":39,"40":40}],42:[function(require,module,exports){ +'use strict'; + +var helpers = require(43); + +/** + * @namespace Chart.helpers.canvas + */ +var exports = module.exports = { + /** + * Clears the entire canvas associated to the given `chart`. + * @param {Chart} chart - The chart for which to clear the canvas. + */ + clear: function(chart) { + chart.ctx.clearRect(0, 0, chart.width, chart.height); + }, + + /** + * Creates a "path" for a rectangle with rounded corners at position (x, y) with a + * given size (width, height) and the same `radius` for all corners. + * @param {CanvasRenderingContext2D} ctx - The canvas 2D Context. + * @param {Number} x - The x axis of the coordinate for the rectangle starting point. + * @param {Number} y - The y axis of the coordinate for the rectangle starting point. + * @param {Number} width - The rectangle's width. + * @param {Number} height - The rectangle's height. + * @param {Number} radius - The rounded amount (in pixels) for the four corners. + * @todo handle `radius` as top-left, top-right, bottom-right, bottom-left array/object? + */ + roundedRect: function(ctx, x, y, width, height, radius) { + if (radius) { + // NOTE(SB) `epsilon` helps to prevent minor artifacts appearing + // on Chrome when `r` is exactly half the height or the width. + var epsilon = 0.0000001; + var r = Math.min(radius, (height / 2) - epsilon, (width / 2) - epsilon); + + ctx.moveTo(x + r, y); + ctx.lineTo(x + width - r, y); + ctx.arcTo(x + width, y, x + width, y + r, r); + ctx.lineTo(x + width, y + height - r); + ctx.arcTo(x + width, y + height, x + width - r, y + height, r); + ctx.lineTo(x + r, y + height); + ctx.arcTo(x, y + height, x, y + height - r, r); + ctx.lineTo(x, y + r); + ctx.arcTo(x, y, x + r, y, r); + ctx.closePath(); + ctx.moveTo(x, y); + } else { + ctx.rect(x, y, width, height); + } + }, + + drawPoint: function(ctx, style, radius, x, y, rotation) { + var type, edgeLength, xOffset, yOffset, height, size; + rotation = rotation || 0; + + if (style && typeof style === 'object') { + type = style.toString(); + if (type === '[object HTMLImageElement]' || type === '[object HTMLCanvasElement]') { + ctx.drawImage(style, x - style.width / 2, y - style.height / 2, style.width, style.height); + return; + } + } + + if (isNaN(radius) || radius <= 0) { + return; + } + + ctx.save(); + ctx.translate(x, y); + ctx.rotate(rotation * Math.PI / 180); + ctx.beginPath(); + + switch (style) { + // Default includes circle + default: + ctx.arc(0, 0, radius, 0, Math.PI * 2); + ctx.closePath(); + break; + case 'triangle': + edgeLength = 3 * radius / Math.sqrt(3); + height = edgeLength * Math.sqrt(3) / 2; + ctx.moveTo(-edgeLength / 2, height / 3); + ctx.lineTo(edgeLength / 2, height / 3); + ctx.lineTo(0, -2 * height / 3); + ctx.closePath(); + break; + case 'rect': + size = 1 / Math.SQRT2 * radius; + ctx.rect(-size, -size, 2 * size, 2 * size); + break; + case 'rectRounded': + var offset = radius / Math.SQRT2; + var leftX = -offset; + var topY = -offset; + var sideSize = Math.SQRT2 * radius; + + // NOTE(SB) the rounded rect implementation changed to use `arcTo` + // instead of `quadraticCurveTo` since it generates better results + // when rect is almost a circle. 0.425 (instead of 0.5) produces + // results visually closer to the previous impl. + this.roundedRect(ctx, leftX, topY, sideSize, sideSize, radius * 0.425); + break; + case 'rectRot': + size = 1 / Math.SQRT2 * radius; + ctx.moveTo(-size, 0); + ctx.lineTo(0, size); + ctx.lineTo(size, 0); + ctx.lineTo(0, -size); + ctx.closePath(); + break; + case 'cross': + ctx.moveTo(0, radius); + ctx.lineTo(0, -radius); + ctx.moveTo(-radius, 0); + ctx.lineTo(radius, 0); + break; + case 'crossRot': + xOffset = Math.cos(Math.PI / 4) * radius; + yOffset = Math.sin(Math.PI / 4) * radius; + ctx.moveTo(-xOffset, -yOffset); + ctx.lineTo(xOffset, yOffset); + ctx.moveTo(-xOffset, yOffset); + ctx.lineTo(xOffset, -yOffset); + break; + case 'star': + ctx.moveTo(0, radius); + ctx.lineTo(0, -radius); + ctx.moveTo(-radius, 0); + ctx.lineTo(radius, 0); + xOffset = Math.cos(Math.PI / 4) * radius; + yOffset = Math.sin(Math.PI / 4) * radius; + ctx.moveTo(-xOffset, -yOffset); + ctx.lineTo(xOffset, yOffset); + ctx.moveTo(-xOffset, yOffset); + ctx.lineTo(xOffset, -yOffset); + break; + case 'line': + ctx.moveTo(-radius, 0); + ctx.lineTo(radius, 0); + break; + case 'dash': + ctx.moveTo(0, 0); + ctx.lineTo(radius, 0); + break; + } + + ctx.fill(); + ctx.stroke(); + ctx.restore(); + }, + + clipArea: function(ctx, area) { + ctx.save(); + ctx.beginPath(); + ctx.rect(area.left, area.top, area.right - area.left, area.bottom - area.top); + ctx.clip(); + }, + + unclipArea: function(ctx) { + ctx.restore(); + }, + + lineTo: function(ctx, previous, target, flip) { + if (target.steppedLine) { + if ((target.steppedLine === 'after' && !flip) || (target.steppedLine !== 'after' && flip)) { + ctx.lineTo(previous.x, target.y); + } else { + ctx.lineTo(target.x, previous.y); + } + ctx.lineTo(target.x, target.y); + return; + } + + if (!target.tension) { + ctx.lineTo(target.x, target.y); + return; + } + + ctx.bezierCurveTo( + flip ? previous.controlPointPreviousX : previous.controlPointNextX, + flip ? previous.controlPointPreviousY : previous.controlPointNextY, + flip ? target.controlPointNextX : target.controlPointPreviousX, + flip ? target.controlPointNextY : target.controlPointPreviousY, + target.x, + target.y); + } +}; + +// DEPRECATIONS + +/** + * Provided for backward compatibility, use Chart.helpers.canvas.clear instead. + * @namespace Chart.helpers.clear + * @deprecated since version 2.7.0 + * @todo remove at version 3 + * @private + */ +helpers.clear = exports.clear; + +/** + * Provided for backward compatibility, use Chart.helpers.canvas.roundedRect instead. + * @namespace Chart.helpers.drawRoundedRectangle + * @deprecated since version 2.7.0 + * @todo remove at version 3 + * @private + */ +helpers.drawRoundedRectangle = function(ctx) { + ctx.beginPath(); + exports.roundedRect.apply(exports, arguments); +}; + +},{"43":43}],43:[function(require,module,exports){ +'use strict'; + +/** + * @namespace Chart.helpers + */ +var helpers = { + /** + * An empty function that can be used, for example, for optional callback. + */ + noop: function() {}, + + /** + * Returns a unique id, sequentially generated from a global variable. + * @returns {Number} + * @function + */ + uid: (function() { + var id = 0; + return function() { + return id++; + }; + }()), + + /** + * Returns true if `value` is neither null nor undefined, else returns false. + * @param {*} value - The value to test. + * @returns {Boolean} + * @since 2.7.0 + */ + isNullOrUndef: function(value) { + return value === null || typeof value === 'undefined'; + }, + + /** + * Returns true if `value` is an array, else returns false. + * @param {*} value - The value to test. + * @returns {Boolean} + * @function + */ + isArray: Array.isArray ? Array.isArray : function(value) { + return Object.prototype.toString.call(value) === '[object Array]'; + }, + + /** + * Returns true if `value` is an object (excluding null), else returns false. + * @param {*} value - The value to test. + * @returns {Boolean} + * @since 2.7.0 + */ + isObject: function(value) { + return value !== null && Object.prototype.toString.call(value) === '[object Object]'; + }, + + /** + * Returns `value` if defined, else returns `defaultValue`. + * @param {*} value - The value to return if defined. + * @param {*} defaultValue - The value to return if `value` is undefined. + * @returns {*} + */ + valueOrDefault: function(value, defaultValue) { + return typeof value === 'undefined' ? defaultValue : value; + }, + + /** + * Returns value at the given `index` in array if defined, else returns `defaultValue`. + * @param {Array} value - The array to lookup for value at `index`. + * @param {Number} index - The index in `value` to lookup for value. + * @param {*} defaultValue - The value to return if `value[index]` is undefined. + * @returns {*} + */ + valueAtIndexOrDefault: function(value, index, defaultValue) { + return helpers.valueOrDefault(helpers.isArray(value) ? value[index] : value, defaultValue); + }, + + /** + * Calls `fn` with the given `args` in the scope defined by `thisArg` and returns the + * value returned by `fn`. If `fn` is not a function, this method returns undefined. + * @param {Function} fn - The function to call. + * @param {Array|undefined|null} args - The arguments with which `fn` should be called. + * @param {Object} [thisArg] - The value of `this` provided for the call to `fn`. + * @returns {*} + */ + callback: function(fn, args, thisArg) { + if (fn && typeof fn.call === 'function') { + return fn.apply(thisArg, args); + } + }, + + /** + * Note(SB) for performance sake, this method should only be used when loopable type + * is unknown or in none intensive code (not called often and small loopable). Else + * it's preferable to use a regular for() loop and save extra function calls. + * @param {Object|Array} loopable - The object or array to be iterated. + * @param {Function} fn - The function to call for each item. + * @param {Object} [thisArg] - The value of `this` provided for the call to `fn`. + * @param {Boolean} [reverse] - If true, iterates backward on the loopable. + */ + each: function(loopable, fn, thisArg, reverse) { + var i, len, keys; + if (helpers.isArray(loopable)) { + len = loopable.length; + if (reverse) { + for (i = len - 1; i >= 0; i--) { + fn.call(thisArg, loopable[i], i); + } + } else { + for (i = 0; i < len; i++) { + fn.call(thisArg, loopable[i], i); + } + } + } else if (helpers.isObject(loopable)) { + keys = Object.keys(loopable); + len = keys.length; + for (i = 0; i < len; i++) { + fn.call(thisArg, loopable[keys[i]], keys[i]); + } + } + }, + + /** + * Returns true if the `a0` and `a1` arrays have the same content, else returns false. + * @see http://stackoverflow.com/a/14853974 + * @param {Array} a0 - The array to compare + * @param {Array} a1 - The array to compare + * @returns {Boolean} + */ + arrayEquals: function(a0, a1) { + var i, ilen, v0, v1; + + if (!a0 || !a1 || a0.length !== a1.length) { + return false; + } + + for (i = 0, ilen = a0.length; i < ilen; ++i) { + v0 = a0[i]; + v1 = a1[i]; + + if (v0 instanceof Array && v1 instanceof Array) { + if (!helpers.arrayEquals(v0, v1)) { + return false; + } + } else if (v0 !== v1) { + // NOTE: two different object instances will never be equal: {x:20} != {x:20} + return false; + } + } + + return true; + }, + + /** + * Returns a deep copy of `source` without keeping references on objects and arrays. + * @param {*} source - The value to clone. + * @returns {*} + */ + clone: function(source) { + if (helpers.isArray(source)) { + return source.map(helpers.clone); + } + + if (helpers.isObject(source)) { + var target = {}; + var keys = Object.keys(source); + var klen = keys.length; + var k = 0; + + for (; k < klen; ++k) { + target[keys[k]] = helpers.clone(source[keys[k]]); + } + + return target; + } + + return source; + }, + + /** + * The default merger when Chart.helpers.merge is called without merger option. + * Note(SB): this method is also used by configMerge and scaleMerge as fallback. + * @private + */ + _merger: function(key, target, source, options) { + var tval = target[key]; + var sval = source[key]; + + if (helpers.isObject(tval) && helpers.isObject(sval)) { + helpers.merge(tval, sval, options); + } else { + target[key] = helpers.clone(sval); + } + }, + + /** + * Merges source[key] in target[key] only if target[key] is undefined. + * @private + */ + _mergerIf: function(key, target, source) { + var tval = target[key]; + var sval = source[key]; + + if (helpers.isObject(tval) && helpers.isObject(sval)) { + helpers.mergeIf(tval, sval); + } else if (!target.hasOwnProperty(key)) { + target[key] = helpers.clone(sval); + } + }, + + /** + * Recursively deep copies `source` properties into `target` with the given `options`. + * IMPORTANT: `target` is not cloned and will be updated with `source` properties. + * @param {Object} target - The target object in which all sources are merged into. + * @param {Object|Array(Object)} source - Object(s) to merge into `target`. + * @param {Object} [options] - Merging options: + * @param {Function} [options.merger] - The merge method (key, target, source, options) + * @returns {Object} The `target` object. + */ + merge: function(target, source, options) { + var sources = helpers.isArray(source) ? source : [source]; + var ilen = sources.length; + var merge, i, keys, klen, k; + + if (!helpers.isObject(target)) { + return target; + } + + options = options || {}; + merge = options.merger || helpers._merger; + + for (i = 0; i < ilen; ++i) { + source = sources[i]; + if (!helpers.isObject(source)) { + continue; + } + + keys = Object.keys(source); + for (k = 0, klen = keys.length; k < klen; ++k) { + merge(keys[k], target, source, options); + } + } + + return target; + }, + + /** + * Recursively deep copies `source` properties into `target` *only* if not defined in target. + * IMPORTANT: `target` is not cloned and will be updated with `source` properties. + * @param {Object} target - The target object in which all sources are merged into. + * @param {Object|Array(Object)} source - Object(s) to merge into `target`. + * @returns {Object} The `target` object. + */ + mergeIf: function(target, source) { + return helpers.merge(target, source, {merger: helpers._mergerIf}); + }, + + /** + * Applies the contents of two or more objects together into the first object. + * @param {Object} target - The target object in which all objects are merged into. + * @param {Object} arg1 - Object containing additional properties to merge in target. + * @param {Object} argN - Additional objects containing properties to merge in target. + * @returns {Object} The `target` object. + */ + extend: function(target) { + var setFn = function(value, key) { + target[key] = value; + }; + for (var i = 1, ilen = arguments.length; i < ilen; ++i) { + helpers.each(arguments[i], setFn); + } + return target; + }, + + /** + * Basic javascript inheritance based on the model created in Backbone.js + */ + inherits: function(extensions) { + var me = this; + var ChartElement = (extensions && extensions.hasOwnProperty('constructor')) ? extensions.constructor : function() { + return me.apply(this, arguments); + }; + + var Surrogate = function() { + this.constructor = ChartElement; + }; + + Surrogate.prototype = me.prototype; + ChartElement.prototype = new Surrogate(); + ChartElement.extend = helpers.inherits; + + if (extensions) { + helpers.extend(ChartElement.prototype, extensions); + } + + ChartElement.__super__ = me.prototype; + return ChartElement; + } +}; + +module.exports = helpers; + +// DEPRECATIONS + +/** + * Provided for backward compatibility, use Chart.helpers.callback instead. + * @function Chart.helpers.callCallback + * @deprecated since version 2.6.0 + * @todo remove at version 3 + * @private + */ +helpers.callCallback = helpers.callback; + +/** + * Provided for backward compatibility, use Array.prototype.indexOf instead. + * Array.prototype.indexOf compatibility: Chrome, Opera, Safari, FF1.5+, IE9+ + * @function Chart.helpers.indexOf + * @deprecated since version 2.7.0 + * @todo remove at version 3 + * @private + */ +helpers.indexOf = function(array, item, fromIndex) { + return Array.prototype.indexOf.call(array, item, fromIndex); +}; + +/** + * Provided for backward compatibility, use Chart.helpers.valueOrDefault instead. + * @function Chart.helpers.getValueOrDefault + * @deprecated since version 2.7.0 + * @todo remove at version 3 + * @private + */ +helpers.getValueOrDefault = helpers.valueOrDefault; + +/** + * Provided for backward compatibility, use Chart.helpers.valueAtIndexOrDefault instead. + * @function Chart.helpers.getValueAtIndexOrDefault + * @deprecated since version 2.7.0 + * @todo remove at version 3 + * @private + */ +helpers.getValueAtIndexOrDefault = helpers.valueAtIndexOrDefault; + +},{}],44:[function(require,module,exports){ +'use strict'; + +var helpers = require(43); + +/** + * Easing functions adapted from Robert Penner's easing equations. + * @namespace Chart.helpers.easingEffects + * @see http://www.robertpenner.com/easing/ + */ +var effects = { + linear: function(t) { + return t; + }, + + easeInQuad: function(t) { + return t * t; + }, + + easeOutQuad: function(t) { + return -t * (t - 2); + }, + + easeInOutQuad: function(t) { + if ((t /= 0.5) < 1) { + return 0.5 * t * t; + } + return -0.5 * ((--t) * (t - 2) - 1); + }, + + easeInCubic: function(t) { + return t * t * t; + }, + + easeOutCubic: function(t) { + return (t = t - 1) * t * t + 1; + }, + + easeInOutCubic: function(t) { + if ((t /= 0.5) < 1) { + return 0.5 * t * t * t; + } + return 0.5 * ((t -= 2) * t * t + 2); + }, + + easeInQuart: function(t) { + return t * t * t * t; + }, + + easeOutQuart: function(t) { + return -((t = t - 1) * t * t * t - 1); + }, + + easeInOutQuart: function(t) { + if ((t /= 0.5) < 1) { + return 0.5 * t * t * t * t; + } + return -0.5 * ((t -= 2) * t * t * t - 2); + }, + + easeInQuint: function(t) { + return t * t * t * t * t; + }, + + easeOutQuint: function(t) { + return (t = t - 1) * t * t * t * t + 1; + }, + + easeInOutQuint: function(t) { + if ((t /= 0.5) < 1) { + return 0.5 * t * t * t * t * t; + } + return 0.5 * ((t -= 2) * t * t * t * t + 2); + }, + + easeInSine: function(t) { + return -Math.cos(t * (Math.PI / 2)) + 1; + }, + + easeOutSine: function(t) { + return Math.sin(t * (Math.PI / 2)); + }, + + easeInOutSine: function(t) { + return -0.5 * (Math.cos(Math.PI * t) - 1); + }, + + easeInExpo: function(t) { + return (t === 0) ? 0 : Math.pow(2, 10 * (t - 1)); + }, + + easeOutExpo: function(t) { + return (t === 1) ? 1 : -Math.pow(2, -10 * t) + 1; + }, + + easeInOutExpo: function(t) { + if (t === 0) { + return 0; + } + if (t === 1) { + return 1; + } + if ((t /= 0.5) < 1) { + return 0.5 * Math.pow(2, 10 * (t - 1)); + } + return 0.5 * (-Math.pow(2, -10 * --t) + 2); + }, + + easeInCirc: function(t) { + if (t >= 1) { + return t; + } + return -(Math.sqrt(1 - t * t) - 1); + }, + + easeOutCirc: function(t) { + return Math.sqrt(1 - (t = t - 1) * t); + }, + + easeInOutCirc: function(t) { + if ((t /= 0.5) < 1) { + return -0.5 * (Math.sqrt(1 - t * t) - 1); + } + return 0.5 * (Math.sqrt(1 - (t -= 2) * t) + 1); + }, + + easeInElastic: function(t) { + var s = 1.70158; + var p = 0; + var a = 1; + if (t === 0) { + return 0; + } + if (t === 1) { + return 1; + } + if (!p) { + p = 0.3; + } + if (a < 1) { + a = 1; + s = p / 4; + } else { + s = p / (2 * Math.PI) * Math.asin(1 / a); + } + return -(a * Math.pow(2, 10 * (t -= 1)) * Math.sin((t - s) * (2 * Math.PI) / p)); + }, + + easeOutElastic: function(t) { + var s = 1.70158; + var p = 0; + var a = 1; + if (t === 0) { + return 0; + } + if (t === 1) { + return 1; + } + if (!p) { + p = 0.3; + } + if (a < 1) { + a = 1; + s = p / 4; + } else { + s = p / (2 * Math.PI) * Math.asin(1 / a); + } + return a * Math.pow(2, -10 * t) * Math.sin((t - s) * (2 * Math.PI) / p) + 1; + }, + + easeInOutElastic: function(t) { + var s = 1.70158; + var p = 0; + var a = 1; + if (t === 0) { + return 0; + } + if ((t /= 0.5) === 2) { + return 1; + } + if (!p) { + p = 0.45; + } + if (a < 1) { + a = 1; + s = p / 4; + } else { + s = p / (2 * Math.PI) * Math.asin(1 / a); + } + if (t < 1) { + return -0.5 * (a * Math.pow(2, 10 * (t -= 1)) * Math.sin((t - s) * (2 * Math.PI) / p)); + } + return a * Math.pow(2, -10 * (t -= 1)) * Math.sin((t - s) * (2 * Math.PI) / p) * 0.5 + 1; + }, + easeInBack: function(t) { + var s = 1.70158; + return t * t * ((s + 1) * t - s); + }, + + easeOutBack: function(t) { + var s = 1.70158; + return (t = t - 1) * t * ((s + 1) * t + s) + 1; + }, + + easeInOutBack: function(t) { + var s = 1.70158; + if ((t /= 0.5) < 1) { + return 0.5 * (t * t * (((s *= (1.525)) + 1) * t - s)); + } + return 0.5 * ((t -= 2) * t * (((s *= (1.525)) + 1) * t + s) + 2); + }, + + easeInBounce: function(t) { + return 1 - effects.easeOutBounce(1 - t); + }, + + easeOutBounce: function(t) { + if (t < (1 / 2.75)) { + return 7.5625 * t * t; + } + if (t < (2 / 2.75)) { + return 7.5625 * (t -= (1.5 / 2.75)) * t + 0.75; + } + if (t < (2.5 / 2.75)) { + return 7.5625 * (t -= (2.25 / 2.75)) * t + 0.9375; + } + return 7.5625 * (t -= (2.625 / 2.75)) * t + 0.984375; + }, + + easeInOutBounce: function(t) { + if (t < 0.5) { + return effects.easeInBounce(t * 2) * 0.5; + } + return effects.easeOutBounce(t * 2 - 1) * 0.5 + 0.5; + } +}; + +module.exports = { + effects: effects +}; + +// DEPRECATIONS + +/** + * Provided for backward compatibility, use Chart.helpers.easing.effects instead. + * @function Chart.helpers.easingEffects + * @deprecated since version 2.7.0 + * @todo remove at version 3 + * @private + */ +helpers.easingEffects = effects; + +},{"43":43}],45:[function(require,module,exports){ +'use strict'; + +var helpers = require(43); + +/** + * @alias Chart.helpers.options + * @namespace + */ +module.exports = { + /** + * Converts the given line height `value` in pixels for a specific font `size`. + * @param {Number|String} value - The lineHeight to parse (eg. 1.6, '14px', '75%', '1.6em'). + * @param {Number} size - The font size (in pixels) used to resolve relative `value`. + * @returns {Number} The effective line height in pixels (size * 1.2 if value is invalid). + * @see https://developer.mozilla.org/en-US/docs/Web/CSS/line-height + * @since 2.7.0 + */ + toLineHeight: function(value, size) { + var matches = ('' + value).match(/^(normal|(\d+(?:\.\d+)?)(px|em|%)?)$/); + if (!matches || matches[1] === 'normal') { + return size * 1.2; + } + + value = +matches[2]; + + switch (matches[3]) { + case 'px': + return value; + case '%': + value /= 100; + break; + default: + break; + } + + return size * value; + }, + + /** + * Converts the given value into a padding object with pre-computed width/height. + * @param {Number|Object} value - If a number, set the value to all TRBL component, + * else, if and object, use defined properties and sets undefined ones to 0. + * @returns {Object} The padding values (top, right, bottom, left, width, height) + * @since 2.7.0 + */ + toPadding: function(value) { + var t, r, b, l; + + if (helpers.isObject(value)) { + t = +value.top || 0; + r = +value.right || 0; + b = +value.bottom || 0; + l = +value.left || 0; + } else { + t = r = b = l = +value || 0; + } + + return { + top: t, + right: r, + bottom: b, + left: l, + height: t + b, + width: l + r + }; + }, + + /** + * Evaluates the given `inputs` sequentially and returns the first defined value. + * @param {Array[]} inputs - An array of values, falling back to the last value. + * @param {Object} [context] - If defined and the current value is a function, the value + * is called with `context` as first argument and the result becomes the new input. + * @param {Number} [index] - If defined and the current value is an array, the value + * at `index` become the new input. + * @since 2.7.0 + */ + resolve: function(inputs, context, index) { + var i, ilen, value; + + for (i = 0, ilen = inputs.length; i < ilen; ++i) { + value = inputs[i]; + if (value === undefined) { + continue; + } + if (context !== undefined && typeof value === 'function') { + value = value(context); + } + if (index !== undefined && helpers.isArray(value)) { + value = value[index]; + } + if (value !== undefined) { + return value; + } + } + } +}; + +},{"43":43}],46:[function(require,module,exports){ +'use strict'; + +module.exports = require(43); +module.exports.easing = require(44); +module.exports.canvas = require(42); +module.exports.options = require(45); + +},{"42":42,"43":43,"44":44,"45":45}],47:[function(require,module,exports){ +/** + * Platform fallback implementation (minimal). + * @see https://github.com/chartjs/Chart.js/pull/4591#issuecomment-319575939 + */ + +module.exports = { + acquireContext: function(item) { + if (item && item.canvas) { + // Support for any object associated to a canvas (including a context2d) + item = item.canvas; + } + + return item && item.getContext('2d') || null; + } +}; + +},{}],48:[function(require,module,exports){ +/** + * Chart.Platform implementation for targeting a web browser + */ + +'use strict'; + +var helpers = require(46); + +var EXPANDO_KEY = '$chartjs'; +var CSS_PREFIX = 'chartjs-'; +var CSS_RENDER_MONITOR = CSS_PREFIX + 'render-monitor'; +var CSS_RENDER_ANIMATION = CSS_PREFIX + 'render-animation'; +var ANIMATION_START_EVENTS = ['animationstart', 'webkitAnimationStart']; + +/** + * DOM event types -> Chart.js event types. + * Note: only events with different types are mapped. + * @see https://developer.mozilla.org/en-US/docs/Web/Events + */ +var EVENT_TYPES = { + touchstart: 'mousedown', + touchmove: 'mousemove', + touchend: 'mouseup', + pointerenter: 'mouseenter', + pointerdown: 'mousedown', + pointermove: 'mousemove', + pointerup: 'mouseup', + pointerleave: 'mouseout', + pointerout: 'mouseout' +}; + +/** + * The "used" size is the final value of a dimension property after all calculations have + * been performed. This method uses the computed style of `element` but returns undefined + * if the computed style is not expressed in pixels. That can happen in some cases where + * `element` has a size relative to its parent and this last one is not yet displayed, + * for example because of `display: none` on a parent node. + * @see https://developer.mozilla.org/en-US/docs/Web/CSS/used_value + * @returns {Number} Size in pixels or undefined if unknown. + */ +function readUsedSize(element, property) { + var value = helpers.getStyle(element, property); + var matches = value && value.match(/^(\d+)(\.\d+)?px$/); + return matches ? Number(matches[1]) : undefined; +} + +/** + * Initializes the canvas style and render size without modifying the canvas display size, + * since responsiveness is handled by the controller.resize() method. The config is used + * to determine the aspect ratio to apply in case no explicit height has been specified. + */ +function initCanvas(canvas, config) { + var style = canvas.style; + + // NOTE(SB) canvas.getAttribute('width') !== canvas.width: in the first case it + // returns null or '' if no explicit value has been set to the canvas attribute. + var renderHeight = canvas.getAttribute('height'); + var renderWidth = canvas.getAttribute('width'); + + // Chart.js modifies some canvas values that we want to restore on destroy + canvas[EXPANDO_KEY] = { + initial: { + height: renderHeight, + width: renderWidth, + style: { + display: style.display, + height: style.height, + width: style.width + } + } + }; + + // Force canvas to display as block to avoid extra space caused by inline + // elements, which would interfere with the responsive resize process. + // https://github.com/chartjs/Chart.js/issues/2538 + style.display = style.display || 'block'; + + if (renderWidth === null || renderWidth === '') { + var displayWidth = readUsedSize(canvas, 'width'); + if (displayWidth !== undefined) { + canvas.width = displayWidth; + } + } + + if (renderHeight === null || renderHeight === '') { + if (canvas.style.height === '') { + // If no explicit render height and style height, let's apply the aspect ratio, + // which one can be specified by the user but also by charts as default option + // (i.e. options.aspectRatio). If not specified, use canvas aspect ratio of 2. + canvas.height = canvas.width / (config.options.aspectRatio || 2); + } else { + var displayHeight = readUsedSize(canvas, 'height'); + if (displayWidth !== undefined) { + canvas.height = displayHeight; + } + } + } + + return canvas; +} + +/** + * Detects support for options object argument in addEventListener. + * https://developer.mozilla.org/en-US/docs/Web/API/EventTarget/addEventListener#Safely_detecting_option_support + * @private + */ +var supportsEventListenerOptions = (function() { + var supports = false; + try { + var options = Object.defineProperty({}, 'passive', { + get: function() { + supports = true; + } + }); + window.addEventListener('e', null, options); + } catch (e) { + // continue regardless of error + } + return supports; +}()); + +// Default passive to true as expected by Chrome for 'touchstart' and 'touchend' events. +// https://github.com/chartjs/Chart.js/issues/4287 +var eventListenerOptions = supportsEventListenerOptions ? {passive: true} : false; + +function addEventListener(node, type, listener) { + node.addEventListener(type, listener, eventListenerOptions); +} + +function removeEventListener(node, type, listener) { + node.removeEventListener(type, listener, eventListenerOptions); +} + +function createEvent(type, chart, x, y, nativeEvent) { + return { + type: type, + chart: chart, + native: nativeEvent || null, + x: x !== undefined ? x : null, + y: y !== undefined ? y : null, + }; +} + +function fromNativeEvent(event, chart) { + var type = EVENT_TYPES[event.type] || event.type; + var pos = helpers.getRelativePosition(event, chart); + return createEvent(type, chart, pos.x, pos.y, event); +} + +function throttled(fn, thisArg) { + var ticking = false; + var args = []; + + return function() { + args = Array.prototype.slice.call(arguments); + thisArg = thisArg || this; + + if (!ticking) { + ticking = true; + helpers.requestAnimFrame.call(window, function() { + ticking = false; + fn.apply(thisArg, args); + }); + } + }; +} + +// Implementation based on https://github.com/marcj/css-element-queries +function createResizer(handler) { + var resizer = document.createElement('div'); + var cls = CSS_PREFIX + 'size-monitor'; + var maxSize = 1000000; + var style = + 'position:absolute;' + + 'left:0;' + + 'top:0;' + + 'right:0;' + + 'bottom:0;' + + 'overflow:hidden;' + + 'pointer-events:none;' + + 'visibility:hidden;' + + 'z-index:-1;'; + + resizer.style.cssText = style; + resizer.className = cls; + resizer.innerHTML = + '
' + + '
' + + '
' + + '
' + + '
' + + '
' + + '
' + + '
'; + + var expand = resizer.childNodes[0]; + var shrink = resizer.childNodes[1]; + + resizer._reset = function() { + expand.scrollLeft = maxSize; + expand.scrollTop = maxSize; + shrink.scrollLeft = maxSize; + shrink.scrollTop = maxSize; + }; + var onScroll = function() { + resizer._reset(); + handler(); + }; + + addEventListener(expand, 'scroll', onScroll.bind(expand, 'expand')); + addEventListener(shrink, 'scroll', onScroll.bind(shrink, 'shrink')); + + return resizer; +} + +// https://davidwalsh.name/detect-node-insertion +function watchForRender(node, handler) { + var expando = node[EXPANDO_KEY] || (node[EXPANDO_KEY] = {}); + var proxy = expando.renderProxy = function(e) { + if (e.animationName === CSS_RENDER_ANIMATION) { + handler(); + } + }; + + helpers.each(ANIMATION_START_EVENTS, function(type) { + addEventListener(node, type, proxy); + }); + + // #4737: Chrome might skip the CSS animation when the CSS_RENDER_MONITOR class + // is removed then added back immediately (same animation frame?). Accessing the + // `offsetParent` property will force a reflow and re-evaluate the CSS animation. + // https://gist.github.com/paulirish/5d52fb081b3570c81e3a#box-metrics + // https://github.com/chartjs/Chart.js/issues/4737 + expando.reflow = !!node.offsetParent; + + node.classList.add(CSS_RENDER_MONITOR); +} + +function unwatchForRender(node) { + var expando = node[EXPANDO_KEY] || {}; + var proxy = expando.renderProxy; + + if (proxy) { + helpers.each(ANIMATION_START_EVENTS, function(type) { + removeEventListener(node, type, proxy); + }); + + delete expando.renderProxy; + } + + node.classList.remove(CSS_RENDER_MONITOR); +} + +function addResizeListener(node, listener, chart) { + var expando = node[EXPANDO_KEY] || (node[EXPANDO_KEY] = {}); + + // Let's keep track of this added resizer and thus avoid DOM query when removing it. + var resizer = expando.resizer = createResizer(throttled(function() { + if (expando.resizer) { + return listener(createEvent('resize', chart)); + } + })); + + // The resizer needs to be attached to the node parent, so we first need to be + // sure that `node` is attached to the DOM before injecting the resizer element. + watchForRender(node, function() { + if (expando.resizer) { + var container = node.parentNode; + if (container && container !== resizer.parentNode) { + container.insertBefore(resizer, container.firstChild); + } + + // The container size might have changed, let's reset the resizer state. + resizer._reset(); + } + }); +} + +function removeResizeListener(node) { + var expando = node[EXPANDO_KEY] || {}; + var resizer = expando.resizer; + + delete expando.resizer; + unwatchForRender(node); + + if (resizer && resizer.parentNode) { + resizer.parentNode.removeChild(resizer); + } +} + +function injectCSS(platform, css) { + // http://stackoverflow.com/q/3922139 + var style = platform._style || document.createElement('style'); + if (!platform._style) { + platform._style = style; + css = '/* Chart.js */\n' + css; + style.setAttribute('type', 'text/css'); + document.getElementsByTagName('head')[0].appendChild(style); + } + + style.appendChild(document.createTextNode(css)); +} + +module.exports = { + /** + * This property holds whether this platform is enabled for the current environment. + * Currently used by platform.js to select the proper implementation. + * @private + */ + _enabled: typeof window !== 'undefined' && typeof document !== 'undefined', + + initialize: function() { + var keyframes = 'from{opacity:0.99}to{opacity:1}'; + + injectCSS(this, + // DOM rendering detection + // https://davidwalsh.name/detect-node-insertion + '@-webkit-keyframes ' + CSS_RENDER_ANIMATION + '{' + keyframes + '}' + + '@keyframes ' + CSS_RENDER_ANIMATION + '{' + keyframes + '}' + + '.' + CSS_RENDER_MONITOR + '{' + + '-webkit-animation:' + CSS_RENDER_ANIMATION + ' 0.001s;' + + 'animation:' + CSS_RENDER_ANIMATION + ' 0.001s;' + + '}' + ); + }, + + acquireContext: function(item, config) { + if (typeof item === 'string') { + item = document.getElementById(item); + } else if (item.length) { + // Support for array based queries (such as jQuery) + item = item[0]; + } + + if (item && item.canvas) { + // Support for any object associated to a canvas (including a context2d) + item = item.canvas; + } + + // To prevent canvas fingerprinting, some add-ons undefine the getContext + // method, for example: https://github.com/kkapsner/CanvasBlocker + // https://github.com/chartjs/Chart.js/issues/2807 + var context = item && item.getContext && item.getContext('2d'); + + // `instanceof HTMLCanvasElement/CanvasRenderingContext2D` fails when the item is + // inside an iframe or when running in a protected environment. We could guess the + // types from their toString() value but let's keep things flexible and assume it's + // a sufficient condition if the item has a context2D which has item as `canvas`. + // https://github.com/chartjs/Chart.js/issues/3887 + // https://github.com/chartjs/Chart.js/issues/4102 + // https://github.com/chartjs/Chart.js/issues/4152 + if (context && context.canvas === item) { + initCanvas(item, config); + return context; + } + + return null; + }, + + releaseContext: function(context) { + var canvas = context.canvas; + if (!canvas[EXPANDO_KEY]) { + return; + } + + var initial = canvas[EXPANDO_KEY].initial; + ['height', 'width'].forEach(function(prop) { + var value = initial[prop]; + if (helpers.isNullOrUndef(value)) { + canvas.removeAttribute(prop); + } else { + canvas.setAttribute(prop, value); + } + }); + + helpers.each(initial.style || {}, function(value, key) { + canvas.style[key] = value; + }); + + // The canvas render size might have been changed (and thus the state stack discarded), + // we can't use save() and restore() to restore the initial state. So make sure that at + // least the canvas context is reset to the default state by setting the canvas width. + // https://www.w3.org/TR/2011/WD-html5-20110525/the-canvas-element.html + canvas.width = canvas.width; + + delete canvas[EXPANDO_KEY]; + }, + + addEventListener: function(chart, type, listener) { + var canvas = chart.canvas; + if (type === 'resize') { + // Note: the resize event is not supported on all browsers. + addResizeListener(canvas, listener, chart); + return; + } + + var expando = listener[EXPANDO_KEY] || (listener[EXPANDO_KEY] = {}); + var proxies = expando.proxies || (expando.proxies = {}); + var proxy = proxies[chart.id + '_' + type] = function(event) { + listener(fromNativeEvent(event, chart)); + }; + + addEventListener(canvas, type, proxy); + }, + + removeEventListener: function(chart, type, listener) { + var canvas = chart.canvas; + if (type === 'resize') { + // Note: the resize event is not supported on all browsers. + removeResizeListener(canvas, listener); + return; + } + + var expando = listener[EXPANDO_KEY] || {}; + var proxies = expando.proxies || {}; + var proxy = proxies[chart.id + '_' + type]; + if (!proxy) { + return; + } + + removeEventListener(canvas, type, proxy); + } +}; + +// DEPRECATIONS + +/** + * Provided for backward compatibility, use EventTarget.addEventListener instead. + * EventTarget.addEventListener compatibility: Chrome, Opera 7, Safari, FF1.5+, IE9+ + * @see https://developer.mozilla.org/en-US/docs/Web/API/EventTarget/addEventListener + * @function Chart.helpers.addEvent + * @deprecated since version 2.7.0 + * @todo remove at version 3 + * @private + */ +helpers.addEvent = addEventListener; + +/** + * Provided for backward compatibility, use EventTarget.removeEventListener instead. + * EventTarget.removeEventListener compatibility: Chrome, Opera 7, Safari, FF1.5+, IE9+ + * @see https://developer.mozilla.org/en-US/docs/Web/API/EventTarget/removeEventListener + * @function Chart.helpers.removeEvent + * @deprecated since version 2.7.0 + * @todo remove at version 3 + * @private + */ +helpers.removeEvent = removeEventListener; + +},{"46":46}],49:[function(require,module,exports){ +'use strict'; + +var helpers = require(46); +var basic = require(47); +var dom = require(48); + +// @TODO Make possible to select another platform at build time. +var implementation = dom._enabled ? dom : basic; + +/** + * @namespace Chart.platform + * @see https://chartjs.gitbooks.io/proposals/content/Platform.html + * @since 2.4.0 + */ +module.exports = helpers.extend({ + /** + * @since 2.7.0 + */ + initialize: function() {}, + + /** + * Called at chart construction time, returns a context2d instance implementing + * the [W3C Canvas 2D Context API standard]{@link https://www.w3.org/TR/2dcontext/}. + * @param {*} item - The native item from which to acquire context (platform specific) + * @param {Object} options - The chart options + * @returns {CanvasRenderingContext2D} context2d instance + */ + acquireContext: function() {}, + + /** + * Called at chart destruction time, releases any resources associated to the context + * previously returned by the acquireContext() method. + * @param {CanvasRenderingContext2D} context - The context2d instance + * @returns {Boolean} true if the method succeeded, else false + */ + releaseContext: function() {}, + + /** + * Registers the specified listener on the given chart. + * @param {Chart} chart - Chart from which to listen for event + * @param {String} type - The ({@link IEvent}) type to listen for + * @param {Function} listener - Receives a notification (an object that implements + * the {@link IEvent} interface) when an event of the specified type occurs. + */ + addEventListener: function() {}, + + /** + * Removes the specified listener previously registered with addEventListener. + * @param {Chart} chart -Chart from which to remove the listener + * @param {String} type - The ({@link IEvent}) type to remove + * @param {Function} listener - The listener function to remove from the event target. + */ + removeEventListener: function() {} + +}, implementation); + +/** + * @interface IPlatform + * Allows abstracting platform dependencies away from the chart + * @borrows Chart.platform.acquireContext as acquireContext + * @borrows Chart.platform.releaseContext as releaseContext + * @borrows Chart.platform.addEventListener as addEventListener + * @borrows Chart.platform.removeEventListener as removeEventListener + */ + +/** + * @interface IEvent + * @prop {String} type - The event type name, possible values are: + * 'contextmenu', 'mouseenter', 'mousedown', 'mousemove', 'mouseup', 'mouseout', + * 'click', 'dblclick', 'keydown', 'keypress', 'keyup' and 'resize' + * @prop {*} native - The original native event (null for emulated events, e.g. 'resize') + * @prop {Number} x - The mouse x position, relative to the canvas (null for incompatible events) + * @prop {Number} y - The mouse y position, relative to the canvas (null for incompatible events) + */ + +},{"46":46,"47":47,"48":48}],50:[function(require,module,exports){ +'use strict'; + +module.exports = {}; +module.exports.filler = require(51); +module.exports.legend = require(52); +module.exports.title = require(53); + +},{"51":51,"52":52,"53":53}],51:[function(require,module,exports){ +/** + * Plugin based on discussion from the following Chart.js issues: + * @see https://github.com/chartjs/Chart.js/issues/2380#issuecomment-279961569 + * @see https://github.com/chartjs/Chart.js/issues/2440#issuecomment-256461897 + */ + +'use strict'; + +var defaults = require(26); +var elements = require(41); +var helpers = require(46); + +defaults._set('global', { + plugins: { + filler: { + propagate: true + } + } +}); + +var mappers = { + dataset: function(source) { + var index = source.fill; + var chart = source.chart; + var meta = chart.getDatasetMeta(index); + var visible = meta && chart.isDatasetVisible(index); + var points = (visible && meta.dataset._children) || []; + var length = points.length || 0; + + return !length ? null : function(point, i) { + return (i < length && points[i]._view) || null; + }; + }, + + boundary: function(source) { + var boundary = source.boundary; + var x = boundary ? boundary.x : null; + var y = boundary ? boundary.y : null; + + return function(point) { + return { + x: x === null ? point.x : x, + y: y === null ? point.y : y, + }; + }; + } +}; + +// @todo if (fill[0] === '#') +function decodeFill(el, index, count) { + var model = el._model || {}; + var fill = model.fill; + var target; + + if (fill === undefined) { + fill = !!model.backgroundColor; + } + + if (fill === false || fill === null) { + return false; + } + + if (fill === true) { + return 'origin'; + } + + target = parseFloat(fill, 10); + if (isFinite(target) && Math.floor(target) === target) { + if (fill[0] === '-' || fill[0] === '+') { + target = index + target; + } + + if (target === index || target < 0 || target >= count) { + return false; + } + + return target; + } + + switch (fill) { + // compatibility + case 'bottom': + return 'start'; + case 'top': + return 'end'; + case 'zero': + return 'origin'; + // supported boundaries + case 'origin': + case 'start': + case 'end': + return fill; + // invalid fill values + default: + return false; + } +} + +function computeBoundary(source) { + var model = source.el._model || {}; + var scale = source.el._scale || {}; + var fill = source.fill; + var target = null; + var horizontal; + + if (isFinite(fill)) { + return null; + } + + // Backward compatibility: until v3, we still need to support boundary values set on + // the model (scaleTop, scaleBottom and scaleZero) because some external plugins and + // controllers might still use it (e.g. the Smith chart). + + if (fill === 'start') { + target = model.scaleBottom === undefined ? scale.bottom : model.scaleBottom; + } else if (fill === 'end') { + target = model.scaleTop === undefined ? scale.top : model.scaleTop; + } else if (model.scaleZero !== undefined) { + target = model.scaleZero; + } else if (scale.getBasePosition) { + target = scale.getBasePosition(); + } else if (scale.getBasePixel) { + target = scale.getBasePixel(); + } + + if (target !== undefined && target !== null) { + if (target.x !== undefined && target.y !== undefined) { + return target; + } + + if (typeof target === 'number' && isFinite(target)) { + horizontal = scale.isHorizontal(); + return { + x: horizontal ? target : null, + y: horizontal ? null : target + }; + } + } + + return null; +} + +function resolveTarget(sources, index, propagate) { + var source = sources[index]; + var fill = source.fill; + var visited = [index]; + var target; + + if (!propagate) { + return fill; + } + + while (fill !== false && visited.indexOf(fill) === -1) { + if (!isFinite(fill)) { + return fill; + } + + target = sources[fill]; + if (!target) { + return false; + } + + if (target.visible) { + return fill; + } + + visited.push(fill); + fill = target.fill; + } + + return false; +} + +function createMapper(source) { + var fill = source.fill; + var type = 'dataset'; + + if (fill === false) { + return null; + } + + if (!isFinite(fill)) { + type = 'boundary'; + } + + return mappers[type](source); +} + +function isDrawable(point) { + return point && !point.skip; +} + +function drawArea(ctx, curve0, curve1, len0, len1) { + var i; + + if (!len0 || !len1) { + return; + } + + // building first area curve (normal) + ctx.moveTo(curve0[0].x, curve0[0].y); + for (i = 1; i < len0; ++i) { + helpers.canvas.lineTo(ctx, curve0[i - 1], curve0[i]); + } + + // joining the two area curves + ctx.lineTo(curve1[len1 - 1].x, curve1[len1 - 1].y); + + // building opposite area curve (reverse) + for (i = len1 - 1; i > 0; --i) { + helpers.canvas.lineTo(ctx, curve1[i], curve1[i - 1], true); + } +} + +function doFill(ctx, points, mapper, view, color, loop) { + var count = points.length; + var span = view.spanGaps; + var curve0 = []; + var curve1 = []; + var len0 = 0; + var len1 = 0; + var i, ilen, index, p0, p1, d0, d1; + + ctx.beginPath(); + + for (i = 0, ilen = (count + !!loop); i < ilen; ++i) { + index = i % count; + p0 = points[index]._view; + p1 = mapper(p0, index, view); + d0 = isDrawable(p0); + d1 = isDrawable(p1); + + if (d0 && d1) { + len0 = curve0.push(p0); + len1 = curve1.push(p1); + } else if (len0 && len1) { + if (!span) { + drawArea(ctx, curve0, curve1, len0, len1); + len0 = len1 = 0; + curve0 = []; + curve1 = []; + } else { + if (d0) { + curve0.push(p0); + } + if (d1) { + curve1.push(p1); + } + } + } + } + + drawArea(ctx, curve0, curve1, len0, len1); + + ctx.closePath(); + ctx.fillStyle = color; + ctx.fill(); +} + +module.exports = { + id: 'filler', + + afterDatasetsUpdate: function(chart, options) { + var count = (chart.data.datasets || []).length; + var propagate = options.propagate; + var sources = []; + var meta, i, el, source; + + for (i = 0; i < count; ++i) { + meta = chart.getDatasetMeta(i); + el = meta.dataset; + source = null; + + if (el && el._model && el instanceof elements.Line) { + source = { + visible: chart.isDatasetVisible(i), + fill: decodeFill(el, i, count), + chart: chart, + el: el + }; + } + + meta.$filler = source; + sources.push(source); + } + + for (i = 0; i < count; ++i) { + source = sources[i]; + if (!source) { + continue; + } + + source.fill = resolveTarget(sources, i, propagate); + source.boundary = computeBoundary(source); + source.mapper = createMapper(source); + } + }, + + beforeDatasetDraw: function(chart, args) { + var meta = args.meta.$filler; + if (!meta) { + return; + } + + var ctx = chart.ctx; + var el = meta.el; + var view = el._view; + var points = el._children || []; + var mapper = meta.mapper; + var color = view.backgroundColor || defaults.global.defaultColor; + + if (mapper && color && points.length) { + helpers.canvas.clipArea(ctx, chart.chartArea); + doFill(ctx, points, mapper, view, color, el._loop); + helpers.canvas.unclipArea(ctx); + } + } +}; + +},{"26":26,"41":41,"46":46}],52:[function(require,module,exports){ +'use strict'; + +var defaults = require(26); +var Element = require(27); +var helpers = require(46); +var layouts = require(31); + +var noop = helpers.noop; + +defaults._set('global', { + legend: { + display: true, + position: 'top', + fullWidth: true, + reverse: false, + weight: 1000, + + // a callback that will handle + onClick: function(e, legendItem) { + var index = legendItem.datasetIndex; + var ci = this.chart; + var meta = ci.getDatasetMeta(index); + + // See controller.isDatasetVisible comment + meta.hidden = meta.hidden === null ? !ci.data.datasets[index].hidden : null; + + // We hid a dataset ... rerender the chart + ci.update(); + }, + + onHover: null, + + labels: { + boxWidth: 40, + padding: 10, + // Generates labels shown in the legend + // Valid properties to return: + // text : text to display + // fillStyle : fill of coloured box + // strokeStyle: stroke of coloured box + // hidden : if this legend item refers to a hidden item + // lineCap : cap style for line + // lineDash + // lineDashOffset : + // lineJoin : + // lineWidth : + generateLabels: function(chart) { + var data = chart.data; + return helpers.isArray(data.datasets) ? data.datasets.map(function(dataset, i) { + return { + text: dataset.label, + fillStyle: (!helpers.isArray(dataset.backgroundColor) ? dataset.backgroundColor : dataset.backgroundColor[0]), + hidden: !chart.isDatasetVisible(i), + lineCap: dataset.borderCapStyle, + lineDash: dataset.borderDash, + lineDashOffset: dataset.borderDashOffset, + lineJoin: dataset.borderJoinStyle, + lineWidth: dataset.borderWidth, + strokeStyle: dataset.borderColor, + pointStyle: dataset.pointStyle, + + // Below is extra data used for toggling the datasets + datasetIndex: i + }; + }, this) : []; + } + } + }, + + legendCallback: function(chart) { + var text = []; + text.push('
    '); + for (var i = 0; i < chart.data.datasets.length; i++) { + text.push('
  • '); + if (chart.data.datasets[i].label) { + text.push(chart.data.datasets[i].label); + } + text.push('
  • '); + } + text.push('
'); + return text.join(''); + } +}); + +/** + * Helper function to get the box width based on the usePointStyle option + * @param labelopts {Object} the label options on the legend + * @param fontSize {Number} the label font size + * @return {Number} width of the color box area + */ +function getBoxWidth(labelOpts, fontSize) { + return labelOpts.usePointStyle ? + fontSize * Math.SQRT2 : + labelOpts.boxWidth; +} + +/** + * IMPORTANT: this class is exposed publicly as Chart.Legend, backward compatibility required! + */ +var Legend = Element.extend({ + + initialize: function(config) { + helpers.extend(this, config); + + // Contains hit boxes for each dataset (in dataset order) + this.legendHitBoxes = []; + + // Are we in doughnut mode which has a different data type + this.doughnutMode = false; + }, + + // These methods are ordered by lifecycle. Utilities then follow. + // Any function defined here is inherited by all legend types. + // Any function can be extended by the legend type + + beforeUpdate: noop, + update: function(maxWidth, maxHeight, margins) { + var me = this; + + // Update Lifecycle - Probably don't want to ever extend or overwrite this function ;) + me.beforeUpdate(); + + // Absorb the master measurements + me.maxWidth = maxWidth; + me.maxHeight = maxHeight; + me.margins = margins; + + // Dimensions + me.beforeSetDimensions(); + me.setDimensions(); + me.afterSetDimensions(); + // Labels + me.beforeBuildLabels(); + me.buildLabels(); + me.afterBuildLabels(); + + // Fit + me.beforeFit(); + me.fit(); + me.afterFit(); + // + me.afterUpdate(); + + return me.minSize; + }, + afterUpdate: noop, + + // + + beforeSetDimensions: noop, + setDimensions: function() { + var me = this; + // Set the unconstrained dimension before label rotation + if (me.isHorizontal()) { + // Reset position before calculating rotation + me.width = me.maxWidth; + me.left = 0; + me.right = me.width; + } else { + me.height = me.maxHeight; + + // Reset position before calculating rotation + me.top = 0; + me.bottom = me.height; + } + + // Reset padding + me.paddingLeft = 0; + me.paddingTop = 0; + me.paddingRight = 0; + me.paddingBottom = 0; + + // Reset minSize + me.minSize = { + width: 0, + height: 0 + }; + }, + afterSetDimensions: noop, + + // + + beforeBuildLabels: noop, + buildLabels: function() { + var me = this; + var labelOpts = me.options.labels || {}; + var legendItems = helpers.callback(labelOpts.generateLabels, [me.chart], me) || []; + + if (labelOpts.filter) { + legendItems = legendItems.filter(function(item) { + return labelOpts.filter(item, me.chart.data); + }); + } + + if (me.options.reverse) { + legendItems.reverse(); + } + + me.legendItems = legendItems; + }, + afterBuildLabels: noop, + + // + + beforeFit: noop, + fit: function() { + var me = this; + var opts = me.options; + var labelOpts = opts.labels; + var display = opts.display; + + var ctx = me.ctx; + + var globalDefault = defaults.global; + var valueOrDefault = helpers.valueOrDefault; + var fontSize = valueOrDefault(labelOpts.fontSize, globalDefault.defaultFontSize); + var fontStyle = valueOrDefault(labelOpts.fontStyle, globalDefault.defaultFontStyle); + var fontFamily = valueOrDefault(labelOpts.fontFamily, globalDefault.defaultFontFamily); + var labelFont = helpers.fontString(fontSize, fontStyle, fontFamily); + + // Reset hit boxes + var hitboxes = me.legendHitBoxes = []; + + var minSize = me.minSize; + var isHorizontal = me.isHorizontal(); + + if (isHorizontal) { + minSize.width = me.maxWidth; // fill all the width + minSize.height = display ? 10 : 0; + } else { + minSize.width = display ? 10 : 0; + minSize.height = me.maxHeight; // fill all the height + } + + // Increase sizes here + if (display) { + ctx.font = labelFont; + + if (isHorizontal) { + // Labels + + // Width of each line of legend boxes. Labels wrap onto multiple lines when there are too many to fit on one + var lineWidths = me.lineWidths = [0]; + var totalHeight = me.legendItems.length ? fontSize + (labelOpts.padding) : 0; + + ctx.textAlign = 'left'; + ctx.textBaseline = 'top'; + + helpers.each(me.legendItems, function(legendItem, i) { + var boxWidth = getBoxWidth(labelOpts, fontSize); + var width = boxWidth + (fontSize / 2) + ctx.measureText(legendItem.text).width; + + if (lineWidths[lineWidths.length - 1] + width + labelOpts.padding >= me.width) { + totalHeight += fontSize + (labelOpts.padding); + lineWidths[lineWidths.length] = me.left; + } + + // Store the hitbox width and height here. Final position will be updated in `draw` + hitboxes[i] = { + left: 0, + top: 0, + width: width, + height: fontSize + }; + + lineWidths[lineWidths.length - 1] += width + labelOpts.padding; + }); + + minSize.height += totalHeight; + + } else { + var vPadding = labelOpts.padding; + var columnWidths = me.columnWidths = []; + var totalWidth = labelOpts.padding; + var currentColWidth = 0; + var currentColHeight = 0; + var itemHeight = fontSize + vPadding; + + helpers.each(me.legendItems, function(legendItem, i) { + var boxWidth = getBoxWidth(labelOpts, fontSize); + var itemWidth = boxWidth + (fontSize / 2) + ctx.measureText(legendItem.text).width; + + // If too tall, go to new column + if (currentColHeight + itemHeight > minSize.height) { + totalWidth += currentColWidth + labelOpts.padding; + columnWidths.push(currentColWidth); // previous column width + + currentColWidth = 0; + currentColHeight = 0; + } + + // Get max width + currentColWidth = Math.max(currentColWidth, itemWidth); + currentColHeight += itemHeight; + + // Store the hitbox width and height here. Final position will be updated in `draw` + hitboxes[i] = { + left: 0, + top: 0, + width: itemWidth, + height: fontSize + }; + }); + + totalWidth += currentColWidth; + columnWidths.push(currentColWidth); + minSize.width += totalWidth; + } + } + + me.width = minSize.width; + me.height = minSize.height; + }, + afterFit: noop, + + // Shared Methods + isHorizontal: function() { + return this.options.position === 'top' || this.options.position === 'bottom'; + }, + + // Actually draw the legend on the canvas + draw: function() { + var me = this; + var opts = me.options; + var labelOpts = opts.labels; + var globalDefault = defaults.global; + var lineDefault = globalDefault.elements.line; + var legendWidth = me.width; + var lineWidths = me.lineWidths; + + if (opts.display) { + var ctx = me.ctx; + var valueOrDefault = helpers.valueOrDefault; + var fontColor = valueOrDefault(labelOpts.fontColor, globalDefault.defaultFontColor); + var fontSize = valueOrDefault(labelOpts.fontSize, globalDefault.defaultFontSize); + var fontStyle = valueOrDefault(labelOpts.fontStyle, globalDefault.defaultFontStyle); + var fontFamily = valueOrDefault(labelOpts.fontFamily, globalDefault.defaultFontFamily); + var labelFont = helpers.fontString(fontSize, fontStyle, fontFamily); + var cursor; + + // Canvas setup + ctx.textAlign = 'left'; + ctx.textBaseline = 'middle'; + ctx.lineWidth = 0.5; + ctx.strokeStyle = fontColor; // for strikethrough effect + ctx.fillStyle = fontColor; // render in correct colour + ctx.font = labelFont; + + var boxWidth = getBoxWidth(labelOpts, fontSize); + var hitboxes = me.legendHitBoxes; + + // current position + var drawLegendBox = function(x, y, legendItem) { + if (isNaN(boxWidth) || boxWidth <= 0) { + return; + } + + // Set the ctx for the box + ctx.save(); + + ctx.fillStyle = valueOrDefault(legendItem.fillStyle, globalDefault.defaultColor); + ctx.lineCap = valueOrDefault(legendItem.lineCap, lineDefault.borderCapStyle); + ctx.lineDashOffset = valueOrDefault(legendItem.lineDashOffset, lineDefault.borderDashOffset); + ctx.lineJoin = valueOrDefault(legendItem.lineJoin, lineDefault.borderJoinStyle); + ctx.lineWidth = valueOrDefault(legendItem.lineWidth, lineDefault.borderWidth); + ctx.strokeStyle = valueOrDefault(legendItem.strokeStyle, globalDefault.defaultColor); + var isLineWidthZero = (valueOrDefault(legendItem.lineWidth, lineDefault.borderWidth) === 0); + + if (ctx.setLineDash) { + // IE 9 and 10 do not support line dash + ctx.setLineDash(valueOrDefault(legendItem.lineDash, lineDefault.borderDash)); + } + + if (opts.labels && opts.labels.usePointStyle) { + // Recalculate x and y for drawPoint() because its expecting + // x and y to be center of figure (instead of top left) + var radius = fontSize * Math.SQRT2 / 2; + var offSet = radius / Math.SQRT2; + var centerX = x + offSet; + var centerY = y + offSet; + + // Draw pointStyle as legend symbol + helpers.canvas.drawPoint(ctx, legendItem.pointStyle, radius, centerX, centerY); + } else { + // Draw box as legend symbol + if (!isLineWidthZero) { + ctx.strokeRect(x, y, boxWidth, fontSize); + } + ctx.fillRect(x, y, boxWidth, fontSize); + } + + ctx.restore(); + }; + var fillText = function(x, y, legendItem, textWidth) { + var halfFontSize = fontSize / 2; + var xLeft = boxWidth + halfFontSize + x; + var yMiddle = y + halfFontSize; + + ctx.fillText(legendItem.text, xLeft, yMiddle); + + if (legendItem.hidden) { + // Strikethrough the text if hidden + ctx.beginPath(); + ctx.lineWidth = 2; + ctx.moveTo(xLeft, yMiddle); + ctx.lineTo(xLeft + textWidth, yMiddle); + ctx.stroke(); + } + }; + + // Horizontal + var isHorizontal = me.isHorizontal(); + if (isHorizontal) { + cursor = { + x: me.left + ((legendWidth - lineWidths[0]) / 2), + y: me.top + labelOpts.padding, + line: 0 + }; + } else { + cursor = { + x: me.left + labelOpts.padding, + y: me.top + labelOpts.padding, + line: 0 + }; + } + + var itemHeight = fontSize + labelOpts.padding; + helpers.each(me.legendItems, function(legendItem, i) { + var textWidth = ctx.measureText(legendItem.text).width; + var width = boxWidth + (fontSize / 2) + textWidth; + var x = cursor.x; + var y = cursor.y; + + if (isHorizontal) { + if (x + width >= legendWidth) { + y = cursor.y += itemHeight; + cursor.line++; + x = cursor.x = me.left + ((legendWidth - lineWidths[cursor.line]) / 2); + } + } else if (y + itemHeight > me.bottom) { + x = cursor.x = x + me.columnWidths[cursor.line] + labelOpts.padding; + y = cursor.y = me.top + labelOpts.padding; + cursor.line++; + } + + drawLegendBox(x, y, legendItem); + + hitboxes[i].left = x; + hitboxes[i].top = y; + + // Fill the actual label + fillText(x, y, legendItem, textWidth); + + if (isHorizontal) { + cursor.x += width + (labelOpts.padding); + } else { + cursor.y += itemHeight; + } + + }); + } + }, + + /** + * Handle an event + * @private + * @param {IEvent} event - The event to handle + * @return {Boolean} true if a change occured + */ + handleEvent: function(e) { + var me = this; + var opts = me.options; + var type = e.type === 'mouseup' ? 'click' : e.type; + var changed = false; + + if (type === 'mousemove') { + if (!opts.onHover) { + return; + } + } else if (type === 'click') { + if (!opts.onClick) { + return; + } + } else { + return; + } + + // Chart event already has relative position in it + var x = e.x; + var y = e.y; + + if (x >= me.left && x <= me.right && y >= me.top && y <= me.bottom) { + // See if we are touching one of the dataset boxes + var lh = me.legendHitBoxes; + for (var i = 0; i < lh.length; ++i) { + var hitBox = lh[i]; + + if (x >= hitBox.left && x <= hitBox.left + hitBox.width && y >= hitBox.top && y <= hitBox.top + hitBox.height) { + // Touching an element + if (type === 'click') { + // use e.native for backwards compatibility + opts.onClick.call(me, e.native, me.legendItems[i]); + changed = true; + break; + } else if (type === 'mousemove') { + // use e.native for backwards compatibility + opts.onHover.call(me, e.native, me.legendItems[i]); + changed = true; + break; + } + } + } + } + + return changed; + } +}); + +function createNewLegendAndAttach(chart, legendOpts) { + var legend = new Legend({ + ctx: chart.ctx, + options: legendOpts, + chart: chart + }); + + layouts.configure(chart, legend, legendOpts); + layouts.addBox(chart, legend); + chart.legend = legend; +} + +module.exports = { + id: 'legend', + + /** + * Backward compatibility: since 2.1.5, the legend is registered as a plugin, making + * Chart.Legend obsolete. To avoid a breaking change, we export the Legend as part of + * the plugin, which one will be re-exposed in the chart.js file. + * https://github.com/chartjs/Chart.js/pull/2640 + * @private + */ + _element: Legend, + + beforeInit: function(chart) { + var legendOpts = chart.options.legend; + + if (legendOpts) { + createNewLegendAndAttach(chart, legendOpts); + } + }, + + beforeUpdate: function(chart) { + var legendOpts = chart.options.legend; + var legend = chart.legend; + + if (legendOpts) { + helpers.mergeIf(legendOpts, defaults.global.legend); + + if (legend) { + layouts.configure(chart, legend, legendOpts); + legend.options = legendOpts; + } else { + createNewLegendAndAttach(chart, legendOpts); + } + } else if (legend) { + layouts.removeBox(chart, legend); + delete chart.legend; + } + }, + + afterEvent: function(chart, e) { + var legend = chart.legend; + if (legend) { + legend.handleEvent(e); + } + } +}; + +},{"26":26,"27":27,"31":31,"46":46}],53:[function(require,module,exports){ +'use strict'; + +var defaults = require(26); +var Element = require(27); +var helpers = require(46); +var layouts = require(31); + +var noop = helpers.noop; + +defaults._set('global', { + title: { + display: false, + fontStyle: 'bold', + fullWidth: true, + lineHeight: 1.2, + padding: 10, + position: 'top', + text: '', + weight: 2000 // by default greater than legend (1000) to be above + } +}); + +/** + * IMPORTANT: this class is exposed publicly as Chart.Legend, backward compatibility required! + */ +var Title = Element.extend({ + initialize: function(config) { + var me = this; + helpers.extend(me, config); + + // Contains hit boxes for each dataset (in dataset order) + me.legendHitBoxes = []; + }, + + // These methods are ordered by lifecycle. Utilities then follow. + + beforeUpdate: noop, + update: function(maxWidth, maxHeight, margins) { + var me = this; + + // Update Lifecycle - Probably don't want to ever extend or overwrite this function ;) + me.beforeUpdate(); + + // Absorb the master measurements + me.maxWidth = maxWidth; + me.maxHeight = maxHeight; + me.margins = margins; + + // Dimensions + me.beforeSetDimensions(); + me.setDimensions(); + me.afterSetDimensions(); + // Labels + me.beforeBuildLabels(); + me.buildLabels(); + me.afterBuildLabels(); + + // Fit + me.beforeFit(); + me.fit(); + me.afterFit(); + // + me.afterUpdate(); + + return me.minSize; + + }, + afterUpdate: noop, + + // + + beforeSetDimensions: noop, + setDimensions: function() { + var me = this; + // Set the unconstrained dimension before label rotation + if (me.isHorizontal()) { + // Reset position before calculating rotation + me.width = me.maxWidth; + me.left = 0; + me.right = me.width; + } else { + me.height = me.maxHeight; + + // Reset position before calculating rotation + me.top = 0; + me.bottom = me.height; + } + + // Reset padding + me.paddingLeft = 0; + me.paddingTop = 0; + me.paddingRight = 0; + me.paddingBottom = 0; + + // Reset minSize + me.minSize = { + width: 0, + height: 0 + }; + }, + afterSetDimensions: noop, + + // + + beforeBuildLabels: noop, + buildLabels: noop, + afterBuildLabels: noop, + + // + + beforeFit: noop, + fit: function() { + var me = this; + var valueOrDefault = helpers.valueOrDefault; + var opts = me.options; + var display = opts.display; + var fontSize = valueOrDefault(opts.fontSize, defaults.global.defaultFontSize); + var minSize = me.minSize; + var lineCount = helpers.isArray(opts.text) ? opts.text.length : 1; + var lineHeight = helpers.options.toLineHeight(opts.lineHeight, fontSize); + var textSize = display ? (lineCount * lineHeight) + (opts.padding * 2) : 0; + + if (me.isHorizontal()) { + minSize.width = me.maxWidth; // fill all the width + minSize.height = textSize; + } else { + minSize.width = textSize; + minSize.height = me.maxHeight; // fill all the height + } + + me.width = minSize.width; + me.height = minSize.height; + + }, + afterFit: noop, + + // Shared Methods + isHorizontal: function() { + var pos = this.options.position; + return pos === 'top' || pos === 'bottom'; + }, + + // Actually draw the title block on the canvas + draw: function() { + var me = this; + var ctx = me.ctx; + var valueOrDefault = helpers.valueOrDefault; + var opts = me.options; + var globalDefaults = defaults.global; + + if (opts.display) { + var fontSize = valueOrDefault(opts.fontSize, globalDefaults.defaultFontSize); + var fontStyle = valueOrDefault(opts.fontStyle, globalDefaults.defaultFontStyle); + var fontFamily = valueOrDefault(opts.fontFamily, globalDefaults.defaultFontFamily); + var titleFont = helpers.fontString(fontSize, fontStyle, fontFamily); + var lineHeight = helpers.options.toLineHeight(opts.lineHeight, fontSize); + var offset = lineHeight / 2 + opts.padding; + var rotation = 0; + var top = me.top; + var left = me.left; + var bottom = me.bottom; + var right = me.right; + var maxWidth, titleX, titleY; + + ctx.fillStyle = valueOrDefault(opts.fontColor, globalDefaults.defaultFontColor); // render in correct colour + ctx.font = titleFont; + + // Horizontal + if (me.isHorizontal()) { + titleX = left + ((right - left) / 2); // midpoint of the width + titleY = top + offset; + maxWidth = right - left; + } else { + titleX = opts.position === 'left' ? left + offset : right - offset; + titleY = top + ((bottom - top) / 2); + maxWidth = bottom - top; + rotation = Math.PI * (opts.position === 'left' ? -0.5 : 0.5); + } + + ctx.save(); + ctx.translate(titleX, titleY); + ctx.rotate(rotation); + ctx.textAlign = 'center'; + ctx.textBaseline = 'middle'; + + var text = opts.text; + if (helpers.isArray(text)) { + var y = 0; + for (var i = 0; i < text.length; ++i) { + ctx.fillText(text[i], 0, y, maxWidth); + y += lineHeight; + } + } else { + ctx.fillText(text, 0, 0, maxWidth); + } + + ctx.restore(); + } + } +}); + +function createNewTitleBlockAndAttach(chart, titleOpts) { + var title = new Title({ + ctx: chart.ctx, + options: titleOpts, + chart: chart + }); + + layouts.configure(chart, title, titleOpts); + layouts.addBox(chart, title); + chart.titleBlock = title; +} + +module.exports = { + id: 'title', + + /** + * Backward compatibility: since 2.1.5, the title is registered as a plugin, making + * Chart.Title obsolete. To avoid a breaking change, we export the Title as part of + * the plugin, which one will be re-exposed in the chart.js file. + * https://github.com/chartjs/Chart.js/pull/2640 + * @private + */ + _element: Title, + + beforeInit: function(chart) { + var titleOpts = chart.options.title; + + if (titleOpts) { + createNewTitleBlockAndAttach(chart, titleOpts); + } + }, + + beforeUpdate: function(chart) { + var titleOpts = chart.options.title; + var titleBlock = chart.titleBlock; + + if (titleOpts) { + helpers.mergeIf(titleOpts, defaults.global.title); + + if (titleBlock) { + layouts.configure(chart, titleBlock, titleOpts); + titleBlock.options = titleOpts; + } else { + createNewTitleBlockAndAttach(chart, titleOpts); + } + } else if (titleBlock) { + layouts.removeBox(chart, titleBlock); + delete chart.titleBlock; + } + } +}; + +},{"26":26,"27":27,"31":31,"46":46}],54:[function(require,module,exports){ +'use strict'; + +var Scale = require(33); +var scaleService = require(34); + +module.exports = function() { + + // Default config for a category scale + var defaultConfig = { + position: 'bottom' + }; + + var DatasetScale = Scale.extend({ + /** + * Internal function to get the correct labels. If data.xLabels or data.yLabels are defined, use those + * else fall back to data.labels + * @private + */ + getLabels: function() { + var data = this.chart.data; + return this.options.labels || (this.isHorizontal() ? data.xLabels : data.yLabels) || data.labels; + }, + + determineDataLimits: function() { + var me = this; + var labels = me.getLabels(); + me.minIndex = 0; + me.maxIndex = labels.length - 1; + var findIndex; + + if (me.options.ticks.min !== undefined) { + // user specified min value + findIndex = labels.indexOf(me.options.ticks.min); + me.minIndex = findIndex !== -1 ? findIndex : me.minIndex; + } + + if (me.options.ticks.max !== undefined) { + // user specified max value + findIndex = labels.indexOf(me.options.ticks.max); + me.maxIndex = findIndex !== -1 ? findIndex : me.maxIndex; + } + + me.min = labels[me.minIndex]; + me.max = labels[me.maxIndex]; + }, + + buildTicks: function() { + var me = this; + var labels = me.getLabels(); + // If we are viewing some subset of labels, slice the original array + me.ticks = (me.minIndex === 0 && me.maxIndex === labels.length - 1) ? labels : labels.slice(me.minIndex, me.maxIndex + 1); + }, + + getLabelForIndex: function(index, datasetIndex) { + var me = this; + var data = me.chart.data; + var isHorizontal = me.isHorizontal(); + + if (data.yLabels && !isHorizontal) { + return me.getRightValue(data.datasets[datasetIndex].data[index]); + } + return me.ticks[index - me.minIndex]; + }, + + // Used to get data value locations. Value can either be an index or a numerical value + getPixelForValue: function(value, index) { + var me = this; + var offset = me.options.offset; + // 1 is added because we need the length but we have the indexes + var offsetAmt = Math.max((me.maxIndex + 1 - me.minIndex - (offset ? 0 : 1)), 1); + + // If value is a data object, then index is the index in the data array, + // not the index of the scale. We need to change that. + var valueCategory; + if (value !== undefined && value !== null) { + valueCategory = me.isHorizontal() ? value.x : value.y; + } + if (valueCategory !== undefined || (value !== undefined && isNaN(index))) { + var labels = me.getLabels(); + value = valueCategory || value; + var idx = labels.indexOf(value); + index = idx !== -1 ? idx : index; + } + + if (me.isHorizontal()) { + var valueWidth = me.width / offsetAmt; + var widthOffset = (valueWidth * (index - me.minIndex)); + + if (offset) { + widthOffset += (valueWidth / 2); + } + + return me.left + Math.round(widthOffset); + } + var valueHeight = me.height / offsetAmt; + var heightOffset = (valueHeight * (index - me.minIndex)); + + if (offset) { + heightOffset += (valueHeight / 2); + } + + return me.top + Math.round(heightOffset); + }, + getPixelForTick: function(index) { + return this.getPixelForValue(this.ticks[index], index + this.minIndex, null); + }, + getValueForPixel: function(pixel) { + var me = this; + var offset = me.options.offset; + var value; + var offsetAmt = Math.max((me._ticks.length - (offset ? 0 : 1)), 1); + var horz = me.isHorizontal(); + var valueDimension = (horz ? me.width : me.height) / offsetAmt; + + pixel -= horz ? me.left : me.top; + + if (offset) { + pixel -= (valueDimension / 2); + } + + if (pixel <= 0) { + value = 0; + } else { + value = Math.round(pixel / valueDimension); + } + + return value + me.minIndex; + }, + getBasePixel: function() { + return this.bottom; + } + }); + + scaleService.registerScaleType('category', DatasetScale, defaultConfig); +}; + +},{"33":33,"34":34}],55:[function(require,module,exports){ +'use strict'; + +var defaults = require(26); +var helpers = require(46); +var scaleService = require(34); +var Ticks = require(35); + +module.exports = function(Chart) { + + var defaultConfig = { + position: 'left', + ticks: { + callback: Ticks.formatters.linear + } + }; + + var LinearScale = Chart.LinearScaleBase.extend({ + + determineDataLimits: function() { + var me = this; + var opts = me.options; + var chart = me.chart; + var data = chart.data; + var datasets = data.datasets; + var isHorizontal = me.isHorizontal(); + var DEFAULT_MIN = 0; + var DEFAULT_MAX = 1; + + function IDMatches(meta) { + return isHorizontal ? meta.xAxisID === me.id : meta.yAxisID === me.id; + } + + // First Calculate the range + me.min = null; + me.max = null; + + var hasStacks = opts.stacked; + if (hasStacks === undefined) { + helpers.each(datasets, function(dataset, datasetIndex) { + if (hasStacks) { + return; + } + + var meta = chart.getDatasetMeta(datasetIndex); + if (chart.isDatasetVisible(datasetIndex) && IDMatches(meta) && + meta.stack !== undefined) { + hasStacks = true; + } + }); + } + + if (opts.stacked || hasStacks) { + var valuesPerStack = {}; + + helpers.each(datasets, function(dataset, datasetIndex) { + var meta = chart.getDatasetMeta(datasetIndex); + var key = [ + meta.type, + // we have a separate stack for stack=undefined datasets when the opts.stacked is undefined + ((opts.stacked === undefined && meta.stack === undefined) ? datasetIndex : ''), + meta.stack + ].join('.'); + + if (valuesPerStack[key] === undefined) { + valuesPerStack[key] = { + positiveValues: [], + negativeValues: [] + }; + } + + // Store these per type + var positiveValues = valuesPerStack[key].positiveValues; + var negativeValues = valuesPerStack[key].negativeValues; + + if (chart.isDatasetVisible(datasetIndex) && IDMatches(meta)) { + helpers.each(dataset.data, function(rawValue, index) { + var value = +me.getRightValue(rawValue); + if (isNaN(value) || meta.data[index].hidden) { + return; + } + + positiveValues[index] = positiveValues[index] || 0; + negativeValues[index] = negativeValues[index] || 0; + + if (opts.relativePoints) { + positiveValues[index] = 100; + } else if (value < 0) { + negativeValues[index] += value; + } else { + positiveValues[index] += value; + } + }); + } + }); + + helpers.each(valuesPerStack, function(valuesForType) { + var values = valuesForType.positiveValues.concat(valuesForType.negativeValues); + var minVal = helpers.min(values); + var maxVal = helpers.max(values); + me.min = me.min === null ? minVal : Math.min(me.min, minVal); + me.max = me.max === null ? maxVal : Math.max(me.max, maxVal); + }); + + } else { + helpers.each(datasets, function(dataset, datasetIndex) { + var meta = chart.getDatasetMeta(datasetIndex); + if (chart.isDatasetVisible(datasetIndex) && IDMatches(meta)) { + helpers.each(dataset.data, function(rawValue, index) { + var value = +me.getRightValue(rawValue); + if (isNaN(value) || meta.data[index].hidden) { + return; + } + + if (me.min === null) { + me.min = value; + } else if (value < me.min) { + me.min = value; + } + + if (me.max === null) { + me.max = value; + } else if (value > me.max) { + me.max = value; + } + }); + } + }); + } + + me.min = isFinite(me.min) && !isNaN(me.min) ? me.min : DEFAULT_MIN; + me.max = isFinite(me.max) && !isNaN(me.max) ? me.max : DEFAULT_MAX; + + // Common base implementation to handle ticks.min, ticks.max, ticks.beginAtZero + this.handleTickRangeOptions(); + }, + getTickLimit: function() { + var maxTicks; + var me = this; + var tickOpts = me.options.ticks; + + if (me.isHorizontal()) { + maxTicks = Math.min(tickOpts.maxTicksLimit ? tickOpts.maxTicksLimit : 11, Math.ceil(me.width / 50)); + } else { + // The factor of 2 used to scale the font size has been experimentally determined. + var tickFontSize = helpers.valueOrDefault(tickOpts.fontSize, defaults.global.defaultFontSize); + maxTicks = Math.min(tickOpts.maxTicksLimit ? tickOpts.maxTicksLimit : 11, Math.ceil(me.height / (2 * tickFontSize))); + } + + return maxTicks; + }, + // Called after the ticks are built. We need + handleDirectionalChanges: function() { + if (!this.isHorizontal()) { + // We are in a vertical orientation. The top value is the highest. So reverse the array + this.ticks.reverse(); + } + }, + getLabelForIndex: function(index, datasetIndex) { + return +this.getRightValue(this.chart.data.datasets[datasetIndex].data[index]); + }, + // Utils + getPixelForValue: function(value) { + // This must be called after fit has been run so that + // this.left, this.top, this.right, and this.bottom have been defined + var me = this; + var start = me.start; + + var rightValue = +me.getRightValue(value); + var pixel; + var range = me.end - start; + + if (me.isHorizontal()) { + pixel = me.left + (me.width / range * (rightValue - start)); + } else { + pixel = me.bottom - (me.height / range * (rightValue - start)); + } + return pixel; + }, + getValueForPixel: function(pixel) { + var me = this; + var isHorizontal = me.isHorizontal(); + var innerDimension = isHorizontal ? me.width : me.height; + var offset = (isHorizontal ? pixel - me.left : me.bottom - pixel) / innerDimension; + return me.start + ((me.end - me.start) * offset); + }, + getPixelForTick: function(index) { + return this.getPixelForValue(this.ticksAsNumbers[index]); + } + }); + + scaleService.registerScaleType('linear', LinearScale, defaultConfig); +}; + +},{"26":26,"34":34,"35":35,"46":46}],56:[function(require,module,exports){ +'use strict'; + +var helpers = require(46); +var Scale = require(33); + +/** + * Generate a set of linear ticks + * @param generationOptions the options used to generate the ticks + * @param dataRange the range of the data + * @returns {Array} array of tick values + */ +function generateTicks(generationOptions, dataRange) { + var ticks = []; + // To get a "nice" value for the tick spacing, we will use the appropriately named + // "nice number" algorithm. See http://stackoverflow.com/questions/8506881/nice-label-algorithm-for-charts-with-minimum-ticks + // for details. + + var factor; + var precision; + var spacing; + + if (generationOptions.stepSize && generationOptions.stepSize > 0) { + spacing = generationOptions.stepSize; + } else { + var niceRange = helpers.niceNum(dataRange.max - dataRange.min, false); + spacing = helpers.niceNum(niceRange / (generationOptions.maxTicks - 1), true); + + precision = generationOptions.precision; + if (precision !== undefined) { + // If the user specified a precision, round to that number of decimal places + factor = Math.pow(10, precision); + spacing = Math.ceil(spacing * factor) / factor; + } + } + var niceMin = Math.floor(dataRange.min / spacing) * spacing; + var niceMax = Math.ceil(dataRange.max / spacing) * spacing; + + // If min, max and stepSize is set and they make an evenly spaced scale use it. + if (!helpers.isNullOrUndef(generationOptions.min) && !helpers.isNullOrUndef(generationOptions.max) && generationOptions.stepSize) { + // If very close to our whole number, use it. + if (helpers.almostWhole((generationOptions.max - generationOptions.min) / generationOptions.stepSize, spacing / 1000)) { + niceMin = generationOptions.min; + niceMax = generationOptions.max; + } + } + + var numSpaces = (niceMax - niceMin) / spacing; + // If very close to our rounded value, use it. + if (helpers.almostEquals(numSpaces, Math.round(numSpaces), spacing / 1000)) { + numSpaces = Math.round(numSpaces); + } else { + numSpaces = Math.ceil(numSpaces); + } + + precision = 1; + if (spacing < 1) { + precision = Math.pow(10, 1 - Math.floor(helpers.log10(spacing))); + niceMin = Math.round(niceMin * precision) / precision; + niceMax = Math.round(niceMax * precision) / precision; + } + ticks.push(generationOptions.min !== undefined ? generationOptions.min : niceMin); + for (var j = 1; j < numSpaces; ++j) { + ticks.push(Math.round((niceMin + j * spacing) * precision) / precision); + } + ticks.push(generationOptions.max !== undefined ? generationOptions.max : niceMax); + + return ticks; +} + +module.exports = function(Chart) { + + var noop = helpers.noop; + + Chart.LinearScaleBase = Scale.extend({ + getRightValue: function(value) { + if (typeof value === 'string') { + return +value; + } + return Scale.prototype.getRightValue.call(this, value); + }, + + handleTickRangeOptions: function() { + var me = this; + var opts = me.options; + var tickOpts = opts.ticks; + + // If we are forcing it to begin at 0, but 0 will already be rendered on the chart, + // do nothing since that would make the chart weird. If the user really wants a weird chart + // axis, they can manually override it + if (tickOpts.beginAtZero) { + var minSign = helpers.sign(me.min); + var maxSign = helpers.sign(me.max); + + if (minSign < 0 && maxSign < 0) { + // move the top up to 0 + me.max = 0; + } else if (minSign > 0 && maxSign > 0) { + // move the bottom down to 0 + me.min = 0; + } + } + + var setMin = tickOpts.min !== undefined || tickOpts.suggestedMin !== undefined; + var setMax = tickOpts.max !== undefined || tickOpts.suggestedMax !== undefined; + + if (tickOpts.min !== undefined) { + me.min = tickOpts.min; + } else if (tickOpts.suggestedMin !== undefined) { + if (me.min === null) { + me.min = tickOpts.suggestedMin; + } else { + me.min = Math.min(me.min, tickOpts.suggestedMin); + } + } + + if (tickOpts.max !== undefined) { + me.max = tickOpts.max; + } else if (tickOpts.suggestedMax !== undefined) { + if (me.max === null) { + me.max = tickOpts.suggestedMax; + } else { + me.max = Math.max(me.max, tickOpts.suggestedMax); + } + } + + if (setMin !== setMax) { + // We set the min or the max but not both. + // So ensure that our range is good + // Inverted or 0 length range can happen when + // ticks.min is set, and no datasets are visible + if (me.min >= me.max) { + if (setMin) { + me.max = me.min + 1; + } else { + me.min = me.max - 1; + } + } + } + + if (me.min === me.max) { + me.max++; + + if (!tickOpts.beginAtZero) { + me.min--; + } + } + }, + getTickLimit: noop, + handleDirectionalChanges: noop, + + buildTicks: function() { + var me = this; + var opts = me.options; + var tickOpts = opts.ticks; + + // Figure out what the max number of ticks we can support it is based on the size of + // the axis area. For now, we say that the minimum tick spacing in pixels must be 50 + // We also limit the maximum number of ticks to 11 which gives a nice 10 squares on + // the graph. Make sure we always have at least 2 ticks + var maxTicks = me.getTickLimit(); + maxTicks = Math.max(2, maxTicks); + + var numericGeneratorOptions = { + maxTicks: maxTicks, + min: tickOpts.min, + max: tickOpts.max, + precision: tickOpts.precision, + stepSize: helpers.valueOrDefault(tickOpts.fixedStepSize, tickOpts.stepSize) + }; + var ticks = me.ticks = generateTicks(numericGeneratorOptions, me); + + me.handleDirectionalChanges(); + + // At this point, we need to update our max and min given the tick values since we have expanded the + // range of the scale + me.max = helpers.max(ticks); + me.min = helpers.min(ticks); + + if (tickOpts.reverse) { + ticks.reverse(); + + me.start = me.max; + me.end = me.min; + } else { + me.start = me.min; + me.end = me.max; + } + }, + convertTicksToLabels: function() { + var me = this; + me.ticksAsNumbers = me.ticks.slice(); + me.zeroLineIndex = me.ticks.indexOf(0); + + Scale.prototype.convertTicksToLabels.call(me); + } + }); +}; + +},{"33":33,"46":46}],57:[function(require,module,exports){ +'use strict'; + +var helpers = require(46); +var Scale = require(33); +var scaleService = require(34); +var Ticks = require(35); + +/** + * Generate a set of logarithmic ticks + * @param generationOptions the options used to generate the ticks + * @param dataRange the range of the data + * @returns {Array} array of tick values + */ +function generateTicks(generationOptions, dataRange) { + var ticks = []; + var valueOrDefault = helpers.valueOrDefault; + + // Figure out what the max number of ticks we can support it is based on the size of + // the axis area. For now, we say that the minimum tick spacing in pixels must be 50 + // We also limit the maximum number of ticks to 11 which gives a nice 10 squares on + // the graph + var tickVal = valueOrDefault(generationOptions.min, Math.pow(10, Math.floor(helpers.log10(dataRange.min)))); + + var endExp = Math.floor(helpers.log10(dataRange.max)); + var endSignificand = Math.ceil(dataRange.max / Math.pow(10, endExp)); + var exp, significand; + + if (tickVal === 0) { + exp = Math.floor(helpers.log10(dataRange.minNotZero)); + significand = Math.floor(dataRange.minNotZero / Math.pow(10, exp)); + + ticks.push(tickVal); + tickVal = significand * Math.pow(10, exp); + } else { + exp = Math.floor(helpers.log10(tickVal)); + significand = Math.floor(tickVal / Math.pow(10, exp)); + } + var precision = exp < 0 ? Math.pow(10, Math.abs(exp)) : 1; + + do { + ticks.push(tickVal); + + ++significand; + if (significand === 10) { + significand = 1; + ++exp; + precision = exp >= 0 ? 1 : precision; + } + + tickVal = Math.round(significand * Math.pow(10, exp) * precision) / precision; + } while (exp < endExp || (exp === endExp && significand < endSignificand)); + + var lastTick = valueOrDefault(generationOptions.max, tickVal); + ticks.push(lastTick); + + return ticks; +} + + +module.exports = function(Chart) { + + var defaultConfig = { + position: 'left', + + // label settings + ticks: { + callback: Ticks.formatters.logarithmic + } + }; + + var LogarithmicScale = Scale.extend({ + determineDataLimits: function() { + var me = this; + var opts = me.options; + var chart = me.chart; + var data = chart.data; + var datasets = data.datasets; + var isHorizontal = me.isHorizontal(); + function IDMatches(meta) { + return isHorizontal ? meta.xAxisID === me.id : meta.yAxisID === me.id; + } + + // Calculate Range + me.min = null; + me.max = null; + me.minNotZero = null; + + var hasStacks = opts.stacked; + if (hasStacks === undefined) { + helpers.each(datasets, function(dataset, datasetIndex) { + if (hasStacks) { + return; + } + + var meta = chart.getDatasetMeta(datasetIndex); + if (chart.isDatasetVisible(datasetIndex) && IDMatches(meta) && + meta.stack !== undefined) { + hasStacks = true; + } + }); + } + + if (opts.stacked || hasStacks) { + var valuesPerStack = {}; + + helpers.each(datasets, function(dataset, datasetIndex) { + var meta = chart.getDatasetMeta(datasetIndex); + var key = [ + meta.type, + // we have a separate stack for stack=undefined datasets when the opts.stacked is undefined + ((opts.stacked === undefined && meta.stack === undefined) ? datasetIndex : ''), + meta.stack + ].join('.'); + + if (chart.isDatasetVisible(datasetIndex) && IDMatches(meta)) { + if (valuesPerStack[key] === undefined) { + valuesPerStack[key] = []; + } + + helpers.each(dataset.data, function(rawValue, index) { + var values = valuesPerStack[key]; + var value = +me.getRightValue(rawValue); + // invalid, hidden and negative values are ignored + if (isNaN(value) || meta.data[index].hidden || value < 0) { + return; + } + values[index] = values[index] || 0; + values[index] += value; + }); + } + }); + + helpers.each(valuesPerStack, function(valuesForType) { + if (valuesForType.length > 0) { + var minVal = helpers.min(valuesForType); + var maxVal = helpers.max(valuesForType); + me.min = me.min === null ? minVal : Math.min(me.min, minVal); + me.max = me.max === null ? maxVal : Math.max(me.max, maxVal); + } + }); + + } else { + helpers.each(datasets, function(dataset, datasetIndex) { + var meta = chart.getDatasetMeta(datasetIndex); + if (chart.isDatasetVisible(datasetIndex) && IDMatches(meta)) { + helpers.each(dataset.data, function(rawValue, index) { + var value = +me.getRightValue(rawValue); + // invalid, hidden and negative values are ignored + if (isNaN(value) || meta.data[index].hidden || value < 0) { + return; + } + + if (me.min === null) { + me.min = value; + } else if (value < me.min) { + me.min = value; + } + + if (me.max === null) { + me.max = value; + } else if (value > me.max) { + me.max = value; + } + + if (value !== 0 && (me.minNotZero === null || value < me.minNotZero)) { + me.minNotZero = value; + } + }); + } + }); + } + + // Common base implementation to handle ticks.min, ticks.max + this.handleTickRangeOptions(); + }, + handleTickRangeOptions: function() { + var me = this; + var opts = me.options; + var tickOpts = opts.ticks; + var valueOrDefault = helpers.valueOrDefault; + var DEFAULT_MIN = 1; + var DEFAULT_MAX = 10; + + me.min = valueOrDefault(tickOpts.min, me.min); + me.max = valueOrDefault(tickOpts.max, me.max); + + if (me.min === me.max) { + if (me.min !== 0 && me.min !== null) { + me.min = Math.pow(10, Math.floor(helpers.log10(me.min)) - 1); + me.max = Math.pow(10, Math.floor(helpers.log10(me.max)) + 1); + } else { + me.min = DEFAULT_MIN; + me.max = DEFAULT_MAX; + } + } + if (me.min === null) { + me.min = Math.pow(10, Math.floor(helpers.log10(me.max)) - 1); + } + if (me.max === null) { + me.max = me.min !== 0 + ? Math.pow(10, Math.floor(helpers.log10(me.min)) + 1) + : DEFAULT_MAX; + } + if (me.minNotZero === null) { + if (me.min > 0) { + me.minNotZero = me.min; + } else if (me.max < 1) { + me.minNotZero = Math.pow(10, Math.floor(helpers.log10(me.max))); + } else { + me.minNotZero = DEFAULT_MIN; + } + } + }, + buildTicks: function() { + var me = this; + var opts = me.options; + var tickOpts = opts.ticks; + var reverse = !me.isHorizontal(); + + var generationOptions = { + min: tickOpts.min, + max: tickOpts.max + }; + var ticks = me.ticks = generateTicks(generationOptions, me); + + // At this point, we need to update our max and min given the tick values since we have expanded the + // range of the scale + me.max = helpers.max(ticks); + me.min = helpers.min(ticks); + + if (tickOpts.reverse) { + reverse = !reverse; + me.start = me.max; + me.end = me.min; + } else { + me.start = me.min; + me.end = me.max; + } + if (reverse) { + ticks.reverse(); + } + }, + convertTicksToLabels: function() { + this.tickValues = this.ticks.slice(); + + Scale.prototype.convertTicksToLabels.call(this); + }, + // Get the correct tooltip label + getLabelForIndex: function(index, datasetIndex) { + return +this.getRightValue(this.chart.data.datasets[datasetIndex].data[index]); + }, + getPixelForTick: function(index) { + return this.getPixelForValue(this.tickValues[index]); + }, + /** + * Returns the value of the first tick. + * @param {Number} value - The minimum not zero value. + * @return {Number} The first tick value. + * @private + */ + _getFirstTickValue: function(value) { + var exp = Math.floor(helpers.log10(value)); + var significand = Math.floor(value / Math.pow(10, exp)); + + return significand * Math.pow(10, exp); + }, + getPixelForValue: function(value) { + var me = this; + var reverse = me.options.ticks.reverse; + var log10 = helpers.log10; + var firstTickValue = me._getFirstTickValue(me.minNotZero); + var offset = 0; + var innerDimension, pixel, start, end, sign; + + value = +me.getRightValue(value); + if (reverse) { + start = me.end; + end = me.start; + sign = -1; + } else { + start = me.start; + end = me.end; + sign = 1; + } + if (me.isHorizontal()) { + innerDimension = me.width; + pixel = reverse ? me.right : me.left; + } else { + innerDimension = me.height; + sign *= -1; // invert, since the upper-left corner of the canvas is at pixel (0, 0) + pixel = reverse ? me.top : me.bottom; + } + if (value !== start) { + if (start === 0) { // include zero tick + offset = helpers.getValueOrDefault( + me.options.ticks.fontSize, + Chart.defaults.global.defaultFontSize + ); + innerDimension -= offset; + start = firstTickValue; + } + if (value !== 0) { + offset += innerDimension / (log10(end) - log10(start)) * (log10(value) - log10(start)); + } + pixel += sign * offset; + } + return pixel; + }, + getValueForPixel: function(pixel) { + var me = this; + var reverse = me.options.ticks.reverse; + var log10 = helpers.log10; + var firstTickValue = me._getFirstTickValue(me.minNotZero); + var innerDimension, start, end, value; + + if (reverse) { + start = me.end; + end = me.start; + } else { + start = me.start; + end = me.end; + } + if (me.isHorizontal()) { + innerDimension = me.width; + value = reverse ? me.right - pixel : pixel - me.left; + } else { + innerDimension = me.height; + value = reverse ? pixel - me.top : me.bottom - pixel; + } + if (value !== start) { + if (start === 0) { // include zero tick + var offset = helpers.getValueOrDefault( + me.options.ticks.fontSize, + Chart.defaults.global.defaultFontSize + ); + value -= offset; + innerDimension -= offset; + start = firstTickValue; + } + value *= log10(end) - log10(start); + value /= innerDimension; + value = Math.pow(10, log10(start) + value); + } + return value; + } + }); + + scaleService.registerScaleType('logarithmic', LogarithmicScale, defaultConfig); +}; + +},{"33":33,"34":34,"35":35,"46":46}],58:[function(require,module,exports){ +'use strict'; + +var defaults = require(26); +var helpers = require(46); +var scaleService = require(34); +var Ticks = require(35); + +module.exports = function(Chart) { + + var globalDefaults = defaults.global; + + var defaultConfig = { + display: true, + + // Boolean - Whether to animate scaling the chart from the centre + animate: true, + position: 'chartArea', + + angleLines: { + display: true, + color: 'rgba(0, 0, 0, 0.1)', + lineWidth: 1 + }, + + gridLines: { + circular: false + }, + + // label settings + ticks: { + // Boolean - Show a backdrop to the scale label + showLabelBackdrop: true, + + // String - The colour of the label backdrop + backdropColor: 'rgba(255,255,255,0.75)', + + // Number - The backdrop padding above & below the label in pixels + backdropPaddingY: 2, + + // Number - The backdrop padding to the side of the label in pixels + backdropPaddingX: 2, + + callback: Ticks.formatters.linear + }, + + pointLabels: { + // Boolean - if true, show point labels + display: true, + + // Number - Point label font size in pixels + fontSize: 10, + + // Function - Used to convert point labels + callback: function(label) { + return label; + } + } + }; + + function getValueCount(scale) { + var opts = scale.options; + return opts.angleLines.display || opts.pointLabels.display ? scale.chart.data.labels.length : 0; + } + + function getPointLabelFontOptions(scale) { + var pointLabelOptions = scale.options.pointLabels; + var fontSize = helpers.valueOrDefault(pointLabelOptions.fontSize, globalDefaults.defaultFontSize); + var fontStyle = helpers.valueOrDefault(pointLabelOptions.fontStyle, globalDefaults.defaultFontStyle); + var fontFamily = helpers.valueOrDefault(pointLabelOptions.fontFamily, globalDefaults.defaultFontFamily); + var font = helpers.fontString(fontSize, fontStyle, fontFamily); + + return { + size: fontSize, + style: fontStyle, + family: fontFamily, + font: font + }; + } + + function measureLabelSize(ctx, fontSize, label) { + if (helpers.isArray(label)) { + return { + w: helpers.longestText(ctx, ctx.font, label), + h: (label.length * fontSize) + ((label.length - 1) * 1.5 * fontSize) + }; + } + + return { + w: ctx.measureText(label).width, + h: fontSize + }; + } + + function determineLimits(angle, pos, size, min, max) { + if (angle === min || angle === max) { + return { + start: pos - (size / 2), + end: pos + (size / 2) + }; + } else if (angle < min || angle > max) { + return { + start: pos - size - 5, + end: pos + }; + } + + return { + start: pos, + end: pos + size + 5 + }; + } + + /** + * Helper function to fit a radial linear scale with point labels + */ + function fitWithPointLabels(scale) { + /* + * Right, this is really confusing and there is a lot of maths going on here + * The gist of the problem is here: https://gist.github.com/nnnick/696cc9c55f4b0beb8fe9 + * + * Reaction: https://dl.dropboxusercontent.com/u/34601363/toomuchscience.gif + * + * Solution: + * + * We assume the radius of the polygon is half the size of the canvas at first + * at each index we check if the text overlaps. + * + * Where it does, we store that angle and that index. + * + * After finding the largest index and angle we calculate how much we need to remove + * from the shape radius to move the point inwards by that x. + * + * We average the left and right distances to get the maximum shape radius that can fit in the box + * along with labels. + * + * Once we have that, we can find the centre point for the chart, by taking the x text protrusion + * on each side, removing that from the size, halving it and adding the left x protrusion width. + * + * This will mean we have a shape fitted to the canvas, as large as it can be with the labels + * and position it in the most space efficient manner + * + * https://dl.dropboxusercontent.com/u/34601363/yeahscience.gif + */ + + var plFont = getPointLabelFontOptions(scale); + + // Get maximum radius of the polygon. Either half the height (minus the text width) or half the width. + // Use this to calculate the offset + change. - Make sure L/R protrusion is at least 0 to stop issues with centre points + var largestPossibleRadius = Math.min(scale.height / 2, scale.width / 2); + var furthestLimits = { + r: scale.width, + l: 0, + t: scale.height, + b: 0 + }; + var furthestAngles = {}; + var i, textSize, pointPosition; + + scale.ctx.font = plFont.font; + scale._pointLabelSizes = []; + + var valueCount = getValueCount(scale); + for (i = 0; i < valueCount; i++) { + pointPosition = scale.getPointPosition(i, largestPossibleRadius); + textSize = measureLabelSize(scale.ctx, plFont.size, scale.pointLabels[i] || ''); + scale._pointLabelSizes[i] = textSize; + + // Add quarter circle to make degree 0 mean top of circle + var angleRadians = scale.getIndexAngle(i); + var angle = helpers.toDegrees(angleRadians) % 360; + var hLimits = determineLimits(angle, pointPosition.x, textSize.w, 0, 180); + var vLimits = determineLimits(angle, pointPosition.y, textSize.h, 90, 270); + + if (hLimits.start < furthestLimits.l) { + furthestLimits.l = hLimits.start; + furthestAngles.l = angleRadians; + } + + if (hLimits.end > furthestLimits.r) { + furthestLimits.r = hLimits.end; + furthestAngles.r = angleRadians; + } + + if (vLimits.start < furthestLimits.t) { + furthestLimits.t = vLimits.start; + furthestAngles.t = angleRadians; + } + + if (vLimits.end > furthestLimits.b) { + furthestLimits.b = vLimits.end; + furthestAngles.b = angleRadians; + } + } + + scale.setReductions(largestPossibleRadius, furthestLimits, furthestAngles); + } + + /** + * Helper function to fit a radial linear scale with no point labels + */ + function fit(scale) { + var largestPossibleRadius = Math.min(scale.height / 2, scale.width / 2); + scale.drawingArea = Math.round(largestPossibleRadius); + scale.setCenterPoint(0, 0, 0, 0); + } + + function getTextAlignForAngle(angle) { + if (angle === 0 || angle === 180) { + return 'center'; + } else if (angle < 180) { + return 'left'; + } + + return 'right'; + } + + function fillText(ctx, text, position, fontSize) { + if (helpers.isArray(text)) { + var y = position.y; + var spacing = 1.5 * fontSize; + + for (var i = 0; i < text.length; ++i) { + ctx.fillText(text[i], position.x, y); + y += spacing; + } + } else { + ctx.fillText(text, position.x, position.y); + } + } + + function adjustPointPositionForLabelHeight(angle, textSize, position) { + if (angle === 90 || angle === 270) { + position.y -= (textSize.h / 2); + } else if (angle > 270 || angle < 90) { + position.y -= textSize.h; + } + } + + function drawPointLabels(scale) { + var ctx = scale.ctx; + var opts = scale.options; + var angleLineOpts = opts.angleLines; + var pointLabelOpts = opts.pointLabels; + + ctx.lineWidth = angleLineOpts.lineWidth; + ctx.strokeStyle = angleLineOpts.color; + + var outerDistance = scale.getDistanceFromCenterForValue(opts.ticks.reverse ? scale.min : scale.max); + + // Point Label Font + var plFont = getPointLabelFontOptions(scale); + + ctx.textBaseline = 'top'; + + for (var i = getValueCount(scale) - 1; i >= 0; i--) { + if (angleLineOpts.display) { + var outerPosition = scale.getPointPosition(i, outerDistance); + ctx.beginPath(); + ctx.moveTo(scale.xCenter, scale.yCenter); + ctx.lineTo(outerPosition.x, outerPosition.y); + ctx.stroke(); + ctx.closePath(); + } + + if (pointLabelOpts.display) { + // Extra 3px out for some label spacing + var pointLabelPosition = scale.getPointPosition(i, outerDistance + 5); + + // Keep this in loop since we may support array properties here + var pointLabelFontColor = helpers.valueAtIndexOrDefault(pointLabelOpts.fontColor, i, globalDefaults.defaultFontColor); + ctx.font = plFont.font; + ctx.fillStyle = pointLabelFontColor; + + var angleRadians = scale.getIndexAngle(i); + var angle = helpers.toDegrees(angleRadians); + ctx.textAlign = getTextAlignForAngle(angle); + adjustPointPositionForLabelHeight(angle, scale._pointLabelSizes[i], pointLabelPosition); + fillText(ctx, scale.pointLabels[i] || '', pointLabelPosition, plFont.size); + } + } + } + + function drawRadiusLine(scale, gridLineOpts, radius, index) { + var ctx = scale.ctx; + ctx.strokeStyle = helpers.valueAtIndexOrDefault(gridLineOpts.color, index - 1); + ctx.lineWidth = helpers.valueAtIndexOrDefault(gridLineOpts.lineWidth, index - 1); + + if (scale.options.gridLines.circular) { + // Draw circular arcs between the points + ctx.beginPath(); + ctx.arc(scale.xCenter, scale.yCenter, radius, 0, Math.PI * 2); + ctx.closePath(); + ctx.stroke(); + } else { + // Draw straight lines connecting each index + var valueCount = getValueCount(scale); + + if (valueCount === 0) { + return; + } + + ctx.beginPath(); + var pointPosition = scale.getPointPosition(0, radius); + ctx.moveTo(pointPosition.x, pointPosition.y); + + for (var i = 1; i < valueCount; i++) { + pointPosition = scale.getPointPosition(i, radius); + ctx.lineTo(pointPosition.x, pointPosition.y); + } + + ctx.closePath(); + ctx.stroke(); + } + } + + function numberOrZero(param) { + return helpers.isNumber(param) ? param : 0; + } + + var LinearRadialScale = Chart.LinearScaleBase.extend({ + setDimensions: function() { + var me = this; + var opts = me.options; + var tickOpts = opts.ticks; + // Set the unconstrained dimension before label rotation + me.width = me.maxWidth; + me.height = me.maxHeight; + me.xCenter = Math.round(me.width / 2); + me.yCenter = Math.round(me.height / 2); + + var minSize = helpers.min([me.height, me.width]); + var tickFontSize = helpers.valueOrDefault(tickOpts.fontSize, globalDefaults.defaultFontSize); + me.drawingArea = opts.display ? (minSize / 2) - (tickFontSize / 2 + tickOpts.backdropPaddingY) : (minSize / 2); + }, + determineDataLimits: function() { + var me = this; + var chart = me.chart; + var min = Number.POSITIVE_INFINITY; + var max = Number.NEGATIVE_INFINITY; + + helpers.each(chart.data.datasets, function(dataset, datasetIndex) { + if (chart.isDatasetVisible(datasetIndex)) { + var meta = chart.getDatasetMeta(datasetIndex); + + helpers.each(dataset.data, function(rawValue, index) { + var value = +me.getRightValue(rawValue); + if (isNaN(value) || meta.data[index].hidden) { + return; + } + + min = Math.min(value, min); + max = Math.max(value, max); + }); + } + }); + + me.min = (min === Number.POSITIVE_INFINITY ? 0 : min); + me.max = (max === Number.NEGATIVE_INFINITY ? 0 : max); + + // Common base implementation to handle ticks.min, ticks.max, ticks.beginAtZero + me.handleTickRangeOptions(); + }, + getTickLimit: function() { + var tickOpts = this.options.ticks; + var tickFontSize = helpers.valueOrDefault(tickOpts.fontSize, globalDefaults.defaultFontSize); + return Math.min(tickOpts.maxTicksLimit ? tickOpts.maxTicksLimit : 11, Math.ceil(this.drawingArea / (1.5 * tickFontSize))); + }, + convertTicksToLabels: function() { + var me = this; + + Chart.LinearScaleBase.prototype.convertTicksToLabels.call(me); + + // Point labels + me.pointLabels = me.chart.data.labels.map(me.options.pointLabels.callback, me); + }, + getLabelForIndex: function(index, datasetIndex) { + return +this.getRightValue(this.chart.data.datasets[datasetIndex].data[index]); + }, + fit: function() { + if (this.options.pointLabels.display) { + fitWithPointLabels(this); + } else { + fit(this); + } + }, + /** + * Set radius reductions and determine new radius and center point + * @private + */ + setReductions: function(largestPossibleRadius, furthestLimits, furthestAngles) { + var me = this; + var radiusReductionLeft = furthestLimits.l / Math.sin(furthestAngles.l); + var radiusReductionRight = Math.max(furthestLimits.r - me.width, 0) / Math.sin(furthestAngles.r); + var radiusReductionTop = -furthestLimits.t / Math.cos(furthestAngles.t); + var radiusReductionBottom = -Math.max(furthestLimits.b - me.height, 0) / Math.cos(furthestAngles.b); + + radiusReductionLeft = numberOrZero(radiusReductionLeft); + radiusReductionRight = numberOrZero(radiusReductionRight); + radiusReductionTop = numberOrZero(radiusReductionTop); + radiusReductionBottom = numberOrZero(radiusReductionBottom); + + me.drawingArea = Math.min( + Math.round(largestPossibleRadius - (radiusReductionLeft + radiusReductionRight) / 2), + Math.round(largestPossibleRadius - (radiusReductionTop + radiusReductionBottom) / 2)); + me.setCenterPoint(radiusReductionLeft, radiusReductionRight, radiusReductionTop, radiusReductionBottom); + }, + setCenterPoint: function(leftMovement, rightMovement, topMovement, bottomMovement) { + var me = this; + var maxRight = me.width - rightMovement - me.drawingArea; + var maxLeft = leftMovement + me.drawingArea; + var maxTop = topMovement + me.drawingArea; + var maxBottom = me.height - bottomMovement - me.drawingArea; + + me.xCenter = Math.round(((maxLeft + maxRight) / 2) + me.left); + me.yCenter = Math.round(((maxTop + maxBottom) / 2) + me.top); + }, + + getIndexAngle: function(index) { + var angleMultiplier = (Math.PI * 2) / getValueCount(this); + var startAngle = this.chart.options && this.chart.options.startAngle ? + this.chart.options.startAngle : + 0; + + var startAngleRadians = startAngle * Math.PI * 2 / 360; + + // Start from the top instead of right, so remove a quarter of the circle + return index * angleMultiplier + startAngleRadians; + }, + getDistanceFromCenterForValue: function(value) { + var me = this; + + if (value === null) { + return 0; // null always in center + } + + // Take into account half font size + the yPadding of the top value + var scalingFactor = me.drawingArea / (me.max - me.min); + if (me.options.ticks.reverse) { + return (me.max - value) * scalingFactor; + } + return (value - me.min) * scalingFactor; + }, + getPointPosition: function(index, distanceFromCenter) { + var me = this; + var thisAngle = me.getIndexAngle(index) - (Math.PI / 2); + return { + x: Math.round(Math.cos(thisAngle) * distanceFromCenter) + me.xCenter, + y: Math.round(Math.sin(thisAngle) * distanceFromCenter) + me.yCenter + }; + }, + getPointPositionForValue: function(index, value) { + return this.getPointPosition(index, this.getDistanceFromCenterForValue(value)); + }, + + getBasePosition: function() { + var me = this; + var min = me.min; + var max = me.max; + + return me.getPointPositionForValue(0, + me.beginAtZero ? 0 : + min < 0 && max < 0 ? max : + min > 0 && max > 0 ? min : + 0); + }, + + draw: function() { + var me = this; + var opts = me.options; + var gridLineOpts = opts.gridLines; + var tickOpts = opts.ticks; + var valueOrDefault = helpers.valueOrDefault; + + if (opts.display) { + var ctx = me.ctx; + var startAngle = this.getIndexAngle(0); + + // Tick Font + var tickFontSize = valueOrDefault(tickOpts.fontSize, globalDefaults.defaultFontSize); + var tickFontStyle = valueOrDefault(tickOpts.fontStyle, globalDefaults.defaultFontStyle); + var tickFontFamily = valueOrDefault(tickOpts.fontFamily, globalDefaults.defaultFontFamily); + var tickLabelFont = helpers.fontString(tickFontSize, tickFontStyle, tickFontFamily); + + helpers.each(me.ticks, function(label, index) { + // Don't draw a centre value (if it is minimum) + if (index > 0 || tickOpts.reverse) { + var yCenterOffset = me.getDistanceFromCenterForValue(me.ticksAsNumbers[index]); + + // Draw circular lines around the scale + if (gridLineOpts.display && index !== 0) { + drawRadiusLine(me, gridLineOpts, yCenterOffset, index); + } + + if (tickOpts.display) { + var tickFontColor = valueOrDefault(tickOpts.fontColor, globalDefaults.defaultFontColor); + ctx.font = tickLabelFont; + + ctx.save(); + ctx.translate(me.xCenter, me.yCenter); + ctx.rotate(startAngle); + + if (tickOpts.showLabelBackdrop) { + var labelWidth = ctx.measureText(label).width; + ctx.fillStyle = tickOpts.backdropColor; + ctx.fillRect( + -labelWidth / 2 - tickOpts.backdropPaddingX, + -yCenterOffset - tickFontSize / 2 - tickOpts.backdropPaddingY, + labelWidth + tickOpts.backdropPaddingX * 2, + tickFontSize + tickOpts.backdropPaddingY * 2 + ); + } + + ctx.textAlign = 'center'; + ctx.textBaseline = 'middle'; + ctx.fillStyle = tickFontColor; + ctx.fillText(label, 0, -yCenterOffset); + ctx.restore(); + } + } + }); + + if (opts.angleLines.display || opts.pointLabels.display) { + drawPointLabels(me); + } + } + } + }); + + scaleService.registerScaleType('radialLinear', LinearRadialScale, defaultConfig); +}; + +},{"26":26,"34":34,"35":35,"46":46}],59:[function(require,module,exports){ +/* global window: false */ +'use strict'; + +var moment = require(1); +moment = typeof moment === 'function' ? moment : window.moment; + +var defaults = require(26); +var helpers = require(46); +var Scale = require(33); +var scaleService = require(34); + +// Integer constants are from the ES6 spec. +var MIN_INTEGER = Number.MIN_SAFE_INTEGER || -9007199254740991; +var MAX_INTEGER = Number.MAX_SAFE_INTEGER || 9007199254740991; + +var INTERVALS = { + millisecond: { + common: true, + size: 1, + steps: [1, 2, 5, 10, 20, 50, 100, 250, 500] + }, + second: { + common: true, + size: 1000, + steps: [1, 2, 5, 10, 15, 30] + }, + minute: { + common: true, + size: 60000, + steps: [1, 2, 5, 10, 15, 30] + }, + hour: { + common: true, + size: 3600000, + steps: [1, 2, 3, 6, 12] + }, + day: { + common: true, + size: 86400000, + steps: [1, 2, 5] + }, + week: { + common: false, + size: 604800000, + steps: [1, 2, 3, 4] + }, + month: { + common: true, + size: 2.628e9, + steps: [1, 2, 3] + }, + quarter: { + common: false, + size: 7.884e9, + steps: [1, 2, 3, 4] + }, + year: { + common: true, + size: 3.154e10 + } +}; + +var UNITS = Object.keys(INTERVALS); + +function sorter(a, b) { + return a - b; +} + +function arrayUnique(items) { + var hash = {}; + var out = []; + var i, ilen, item; + + for (i = 0, ilen = items.length; i < ilen; ++i) { + item = items[i]; + if (!hash[item]) { + hash[item] = true; + out.push(item); + } + } + + return out; +} + +/** + * Returns an array of {time, pos} objects used to interpolate a specific `time` or position + * (`pos`) on the scale, by searching entries before and after the requested value. `pos` is + * a decimal between 0 and 1: 0 being the start of the scale (left or top) and 1 the other + * extremity (left + width or top + height). Note that it would be more optimized to directly + * store pre-computed pixels, but the scale dimensions are not guaranteed at the time we need + * to create the lookup table. The table ALWAYS contains at least two items: min and max. + * + * @param {Number[]} timestamps - timestamps sorted from lowest to highest. + * @param {String} distribution - If 'linear', timestamps will be spread linearly along the min + * and max range, so basically, the table will contains only two items: {min, 0} and {max, 1}. + * If 'series', timestamps will be positioned at the same distance from each other. In this + * case, only timestamps that break the time linearity are registered, meaning that in the + * best case, all timestamps are linear, the table contains only min and max. + */ +function buildLookupTable(timestamps, min, max, distribution) { + if (distribution === 'linear' || !timestamps.length) { + return [ + {time: min, pos: 0}, + {time: max, pos: 1} + ]; + } + + var table = []; + var items = [min]; + var i, ilen, prev, curr, next; + + for (i = 0, ilen = timestamps.length; i < ilen; ++i) { + curr = timestamps[i]; + if (curr > min && curr < max) { + items.push(curr); + } + } + + items.push(max); + + for (i = 0, ilen = items.length; i < ilen; ++i) { + next = items[i + 1]; + prev = items[i - 1]; + curr = items[i]; + + // only add points that breaks the scale linearity + if (prev === undefined || next === undefined || Math.round((next + prev) / 2) !== curr) { + table.push({time: curr, pos: i / (ilen - 1)}); + } + } + + return table; +} + +// @see adapted from http://www.anujgakhar.com/2014/03/01/binary-search-in-javascript/ +function lookup(table, key, value) { + var lo = 0; + var hi = table.length - 1; + var mid, i0, i1; + + while (lo >= 0 && lo <= hi) { + mid = (lo + hi) >> 1; + i0 = table[mid - 1] || null; + i1 = table[mid]; + + if (!i0) { + // given value is outside table (before first item) + return {lo: null, hi: i1}; + } else if (i1[key] < value) { + lo = mid + 1; + } else if (i0[key] > value) { + hi = mid - 1; + } else { + return {lo: i0, hi: i1}; + } + } + + // given value is outside table (after last item) + return {lo: i1, hi: null}; +} + +/** + * Linearly interpolates the given source `value` using the table items `skey` values and + * returns the associated `tkey` value. For example, interpolate(table, 'time', 42, 'pos') + * returns the position for a timestamp equal to 42. If value is out of bounds, values at + * index [0, 1] or [n - 1, n] are used for the interpolation. + */ +function interpolate(table, skey, sval, tkey) { + var range = lookup(table, skey, sval); + + // Note: the lookup table ALWAYS contains at least 2 items (min and max) + var prev = !range.lo ? table[0] : !range.hi ? table[table.length - 2] : range.lo; + var next = !range.lo ? table[1] : !range.hi ? table[table.length - 1] : range.hi; + + var span = next[skey] - prev[skey]; + var ratio = span ? (sval - prev[skey]) / span : 0; + var offset = (next[tkey] - prev[tkey]) * ratio; + + return prev[tkey] + offset; +} + +/** + * Convert the given value to a moment object using the given time options. + * @see http://momentjs.com/docs/#/parsing/ + */ +function momentify(value, options) { + var parser = options.parser; + var format = options.parser || options.format; + + if (typeof parser === 'function') { + return parser(value); + } + + if (typeof value === 'string' && typeof format === 'string') { + return moment(value, format); + } + + if (!(value instanceof moment)) { + value = moment(value); + } + + if (value.isValid()) { + return value; + } + + // Labels are in an incompatible moment format and no `parser` has been provided. + // The user might still use the deprecated `format` option to convert his inputs. + if (typeof format === 'function') { + return format(value); + } + + return value; +} + +function parse(input, scale) { + if (helpers.isNullOrUndef(input)) { + return null; + } + + var options = scale.options.time; + var value = momentify(scale.getRightValue(input), options); + if (!value.isValid()) { + return null; + } + + if (options.round) { + value.startOf(options.round); + } + + return value.valueOf(); +} + +/** + * Returns the number of unit to skip to be able to display up to `capacity` number of ticks + * in `unit` for the given `min` / `max` range and respecting the interval steps constraints. + */ +function determineStepSize(min, max, unit, capacity) { + var range = max - min; + var interval = INTERVALS[unit]; + var milliseconds = interval.size; + var steps = interval.steps; + var i, ilen, factor; + + if (!steps) { + return Math.ceil(range / (capacity * milliseconds)); + } + + for (i = 0, ilen = steps.length; i < ilen; ++i) { + factor = steps[i]; + if (Math.ceil(range / (milliseconds * factor)) <= capacity) { + break; + } + } + + return factor; +} + +/** + * Figures out what unit results in an appropriate number of auto-generated ticks + */ +function determineUnitForAutoTicks(minUnit, min, max, capacity) { + var ilen = UNITS.length; + var i, interval, factor; + + for (i = UNITS.indexOf(minUnit); i < ilen - 1; ++i) { + interval = INTERVALS[UNITS[i]]; + factor = interval.steps ? interval.steps[interval.steps.length - 1] : MAX_INTEGER; + + if (interval.common && Math.ceil((max - min) / (factor * interval.size)) <= capacity) { + return UNITS[i]; + } + } + + return UNITS[ilen - 1]; +} + +/** + * Figures out what unit to format a set of ticks with + */ +function determineUnitForFormatting(ticks, minUnit, min, max) { + var duration = moment.duration(moment(max).diff(moment(min))); + var ilen = UNITS.length; + var i, unit; + + for (i = ilen - 1; i >= UNITS.indexOf(minUnit); i--) { + unit = UNITS[i]; + if (INTERVALS[unit].common && duration.as(unit) >= ticks.length) { + return unit; + } + } + + return UNITS[minUnit ? UNITS.indexOf(minUnit) : 0]; +} + +function determineMajorUnit(unit) { + for (var i = UNITS.indexOf(unit) + 1, ilen = UNITS.length; i < ilen; ++i) { + if (INTERVALS[UNITS[i]].common) { + return UNITS[i]; + } + } +} + +/** + * Generates a maximum of `capacity` timestamps between min and max, rounded to the + * `minor` unit, aligned on the `major` unit and using the given scale time `options`. + * Important: this method can return ticks outside the min and max range, it's the + * responsibility of the calling code to clamp values if needed. + */ +function generate(min, max, capacity, options) { + var timeOpts = options.time; + var minor = timeOpts.unit || determineUnitForAutoTicks(timeOpts.minUnit, min, max, capacity); + var major = determineMajorUnit(minor); + var stepSize = helpers.valueOrDefault(timeOpts.stepSize, timeOpts.unitStepSize); + var weekday = minor === 'week' ? timeOpts.isoWeekday : false; + var majorTicksEnabled = options.ticks.major.enabled; + var interval = INTERVALS[minor]; + var first = moment(min); + var last = moment(max); + var ticks = []; + var time; + + if (!stepSize) { + stepSize = determineStepSize(min, max, minor, capacity); + } + + // For 'week' unit, handle the first day of week option + if (weekday) { + first = first.isoWeekday(weekday); + last = last.isoWeekday(weekday); + } + + // Align first/last ticks on unit + first = first.startOf(weekday ? 'day' : minor); + last = last.startOf(weekday ? 'day' : minor); + + // Make sure that the last tick include max + if (last < max) { + last.add(1, minor); + } + + time = moment(first); + + if (majorTicksEnabled && major && !weekday && !timeOpts.round) { + // Align the first tick on the previous `minor` unit aligned on the `major` unit: + // we first aligned time on the previous `major` unit then add the number of full + // stepSize there is between first and the previous major time. + time.startOf(major); + time.add(~~((first - time) / (interval.size * stepSize)) * stepSize, minor); + } + + for (; time < last; time.add(stepSize, minor)) { + ticks.push(+time); + } + + ticks.push(+time); + + return ticks; +} + +/** + * Returns the right and left offsets from edges in the form of {left, right}. + * Offsets are added when the `offset` option is true. + */ +function computeOffsets(table, ticks, min, max, options) { + var left = 0; + var right = 0; + var upper, lower; + + if (options.offset && ticks.length) { + if (!options.time.min) { + upper = ticks.length > 1 ? ticks[1] : max; + lower = ticks[0]; + left = ( + interpolate(table, 'time', upper, 'pos') - + interpolate(table, 'time', lower, 'pos') + ) / 2; + } + if (!options.time.max) { + upper = ticks[ticks.length - 1]; + lower = ticks.length > 1 ? ticks[ticks.length - 2] : min; + right = ( + interpolate(table, 'time', upper, 'pos') - + interpolate(table, 'time', lower, 'pos') + ) / 2; + } + } + + return {left: left, right: right}; +} + +function ticksFromTimestamps(values, majorUnit) { + var ticks = []; + var i, ilen, value, major; + + for (i = 0, ilen = values.length; i < ilen; ++i) { + value = values[i]; + major = majorUnit ? value === +moment(value).startOf(majorUnit) : false; + + ticks.push({ + value: value, + major: major + }); + } + + return ticks; +} + +function determineLabelFormat(data, timeOpts) { + var i, momentDate, hasTime; + var ilen = data.length; + + // find the label with the most parts (milliseconds, minutes, etc.) + // format all labels with the same level of detail as the most specific label + for (i = 0; i < ilen; i++) { + momentDate = momentify(data[i], timeOpts); + if (momentDate.millisecond() !== 0) { + return 'MMM D, YYYY h:mm:ss.SSS a'; + } + if (momentDate.second() !== 0 || momentDate.minute() !== 0 || momentDate.hour() !== 0) { + hasTime = true; + } + } + if (hasTime) { + return 'MMM D, YYYY h:mm:ss a'; + } + return 'MMM D, YYYY'; +} + +module.exports = function() { + + var defaultConfig = { + position: 'bottom', + + /** + * Data distribution along the scale: + * - 'linear': data are spread according to their time (distances can vary), + * - 'series': data are spread at the same distance from each other. + * @see https://github.com/chartjs/Chart.js/pull/4507 + * @since 2.7.0 + */ + distribution: 'linear', + + /** + * Scale boundary strategy (bypassed by min/max time options) + * - `data`: make sure data are fully visible, ticks outside are removed + * - `ticks`: make sure ticks are fully visible, data outside are truncated + * @see https://github.com/chartjs/Chart.js/pull/4556 + * @since 2.7.0 + */ + bounds: 'data', + + time: { + parser: false, // false == a pattern string from http://momentjs.com/docs/#/parsing/string-format/ or a custom callback that converts its argument to a moment + format: false, // DEPRECATED false == date objects, moment object, callback or a pattern string from http://momentjs.com/docs/#/parsing/string-format/ + unit: false, // false == automatic or override with week, month, year, etc. + round: false, // none, or override with week, month, year, etc. + displayFormat: false, // DEPRECATED + isoWeekday: false, // override week start day - see http://momentjs.com/docs/#/get-set/iso-weekday/ + minUnit: 'millisecond', + + // defaults to unit's corresponding unitFormat below or override using pattern string from http://momentjs.com/docs/#/displaying/format/ + displayFormats: { + millisecond: 'h:mm:ss.SSS a', // 11:20:01.123 AM, + second: 'h:mm:ss a', // 11:20:01 AM + minute: 'h:mm a', // 11:20 AM + hour: 'hA', // 5PM + day: 'MMM D', // Sep 4 + week: 'll', // Week 46, or maybe "[W]WW - YYYY" ? + month: 'MMM YYYY', // Sept 2015 + quarter: '[Q]Q - YYYY', // Q3 + year: 'YYYY' // 2015 + }, + }, + ticks: { + autoSkip: false, + + /** + * Ticks generation input values: + * - 'auto': generates "optimal" ticks based on scale size and time options. + * - 'data': generates ticks from data (including labels from data {t|x|y} objects). + * - 'labels': generates ticks from user given `data.labels` values ONLY. + * @see https://github.com/chartjs/Chart.js/pull/4507 + * @since 2.7.0 + */ + source: 'auto', + + major: { + enabled: false + } + } + }; + + var TimeScale = Scale.extend({ + initialize: function() { + if (!moment) { + throw new Error('Chart.js - Moment.js could not be found! You must include it before Chart.js to use the time scale. Download at https://momentjs.com'); + } + + this.mergeTicksOptions(); + + Scale.prototype.initialize.call(this); + }, + + update: function() { + var me = this; + var options = me.options; + + // DEPRECATIONS: output a message only one time per update + if (options.time && options.time.format) { + console.warn('options.time.format is deprecated and replaced by options.time.parser.'); + } + + return Scale.prototype.update.apply(me, arguments); + }, + + /** + * Allows data to be referenced via 't' attribute + */ + getRightValue: function(rawValue) { + if (rawValue && rawValue.t !== undefined) { + rawValue = rawValue.t; + } + return Scale.prototype.getRightValue.call(this, rawValue); + }, + + determineDataLimits: function() { + var me = this; + var chart = me.chart; + var timeOpts = me.options.time; + var unit = timeOpts.unit || 'day'; + var min = MAX_INTEGER; + var max = MIN_INTEGER; + var timestamps = []; + var datasets = []; + var labels = []; + var i, j, ilen, jlen, data, timestamp; + + // Convert labels to timestamps + for (i = 0, ilen = chart.data.labels.length; i < ilen; ++i) { + labels.push(parse(chart.data.labels[i], me)); + } + + // Convert data to timestamps + for (i = 0, ilen = (chart.data.datasets || []).length; i < ilen; ++i) { + if (chart.isDatasetVisible(i)) { + data = chart.data.datasets[i].data; + + // Let's consider that all data have the same format. + if (helpers.isObject(data[0])) { + datasets[i] = []; + + for (j = 0, jlen = data.length; j < jlen; ++j) { + timestamp = parse(data[j], me); + timestamps.push(timestamp); + datasets[i][j] = timestamp; + } + } else { + timestamps.push.apply(timestamps, labels); + datasets[i] = labels.slice(0); + } + } else { + datasets[i] = []; + } + } + + if (labels.length) { + // Sort labels **after** data have been converted + labels = arrayUnique(labels).sort(sorter); + min = Math.min(min, labels[0]); + max = Math.max(max, labels[labels.length - 1]); + } + + if (timestamps.length) { + timestamps = arrayUnique(timestamps).sort(sorter); + min = Math.min(min, timestamps[0]); + max = Math.max(max, timestamps[timestamps.length - 1]); + } + + min = parse(timeOpts.min, me) || min; + max = parse(timeOpts.max, me) || max; + + // In case there is no valid min/max, set limits based on unit time option + min = min === MAX_INTEGER ? +moment().startOf(unit) : min; + max = max === MIN_INTEGER ? +moment().endOf(unit) + 1 : max; + + // Make sure that max is strictly higher than min (required by the lookup table) + me.min = Math.min(min, max); + me.max = Math.max(min + 1, max); + + // PRIVATE + me._horizontal = me.isHorizontal(); + me._table = []; + me._timestamps = { + data: timestamps, + datasets: datasets, + labels: labels + }; + }, + + buildTicks: function() { + var me = this; + var min = me.min; + var max = me.max; + var options = me.options; + var timeOpts = options.time; + var timestamps = []; + var ticks = []; + var i, ilen, timestamp; + + switch (options.ticks.source) { + case 'data': + timestamps = me._timestamps.data; + break; + case 'labels': + timestamps = me._timestamps.labels; + break; + case 'auto': + default: + timestamps = generate(min, max, me.getLabelCapacity(min), options); + } + + if (options.bounds === 'ticks' && timestamps.length) { + min = timestamps[0]; + max = timestamps[timestamps.length - 1]; + } + + // Enforce limits with user min/max options + min = parse(timeOpts.min, me) || min; + max = parse(timeOpts.max, me) || max; + + // Remove ticks outside the min/max range + for (i = 0, ilen = timestamps.length; i < ilen; ++i) { + timestamp = timestamps[i]; + if (timestamp >= min && timestamp <= max) { + ticks.push(timestamp); + } + } + + me.min = min; + me.max = max; + + // PRIVATE + me._unit = timeOpts.unit || determineUnitForFormatting(ticks, timeOpts.minUnit, me.min, me.max); + me._majorUnit = determineMajorUnit(me._unit); + me._table = buildLookupTable(me._timestamps.data, min, max, options.distribution); + me._offsets = computeOffsets(me._table, ticks, min, max, options); + me._labelFormat = determineLabelFormat(me._timestamps.data, timeOpts); + + return ticksFromTimestamps(ticks, me._majorUnit); + }, + + getLabelForIndex: function(index, datasetIndex) { + var me = this; + var data = me.chart.data; + var timeOpts = me.options.time; + var label = data.labels && index < data.labels.length ? data.labels[index] : ''; + var value = data.datasets[datasetIndex].data[index]; + + if (helpers.isObject(value)) { + label = me.getRightValue(value); + } + if (timeOpts.tooltipFormat) { + return momentify(label, timeOpts).format(timeOpts.tooltipFormat); + } + if (typeof label === 'string') { + return label; + } + + return momentify(label, timeOpts).format(me._labelFormat); + }, + + /** + * Function to format an individual tick mark + * @private + */ + tickFormatFunction: function(tick, index, ticks, formatOverride) { + var me = this; + var options = me.options; + var time = tick.valueOf(); + var formats = options.time.displayFormats; + var minorFormat = formats[me._unit]; + var majorUnit = me._majorUnit; + var majorFormat = formats[majorUnit]; + var majorTime = tick.clone().startOf(majorUnit).valueOf(); + var majorTickOpts = options.ticks.major; + var major = majorTickOpts.enabled && majorUnit && majorFormat && time === majorTime; + var label = tick.format(formatOverride ? formatOverride : major ? majorFormat : minorFormat); + var tickOpts = major ? majorTickOpts : options.ticks.minor; + var formatter = helpers.valueOrDefault(tickOpts.callback, tickOpts.userCallback); + + return formatter ? formatter(label, index, ticks) : label; + }, + + convertTicksToLabels: function(ticks) { + var labels = []; + var i, ilen; + + for (i = 0, ilen = ticks.length; i < ilen; ++i) { + labels.push(this.tickFormatFunction(moment(ticks[i].value), i, ticks)); + } + + return labels; + }, + + /** + * @private + */ + getPixelForOffset: function(time) { + var me = this; + var size = me._horizontal ? me.width : me.height; + var start = me._horizontal ? me.left : me.top; + var pos = interpolate(me._table, 'time', time, 'pos'); + + return start + size * (me._offsets.left + pos) / (me._offsets.left + 1 + me._offsets.right); + }, + + getPixelForValue: function(value, index, datasetIndex) { + var me = this; + var time = null; + + if (index !== undefined && datasetIndex !== undefined) { + time = me._timestamps.datasets[datasetIndex][index]; + } + + if (time === null) { + time = parse(value, me); + } + + if (time !== null) { + return me.getPixelForOffset(time); + } + }, + + getPixelForTick: function(index) { + var ticks = this.getTicks(); + return index >= 0 && index < ticks.length ? + this.getPixelForOffset(ticks[index].value) : + null; + }, + + getValueForPixel: function(pixel) { + var me = this; + var size = me._horizontal ? me.width : me.height; + var start = me._horizontal ? me.left : me.top; + var pos = (size ? (pixel - start) / size : 0) * (me._offsets.left + 1 + me._offsets.left) - me._offsets.right; + var time = interpolate(me._table, 'pos', pos, 'time'); + + return moment(time); + }, + + /** + * Crude approximation of what the label width might be + * @private + */ + getLabelWidth: function(label) { + var me = this; + var ticksOpts = me.options.ticks; + var tickLabelWidth = me.ctx.measureText(label).width; + var angle = helpers.toRadians(ticksOpts.maxRotation); + var cosRotation = Math.cos(angle); + var sinRotation = Math.sin(angle); + var tickFontSize = helpers.valueOrDefault(ticksOpts.fontSize, defaults.global.defaultFontSize); + + return (tickLabelWidth * cosRotation) + (tickFontSize * sinRotation); + }, + + /** + * @private + */ + getLabelCapacity: function(exampleTime) { + var me = this; + + var formatOverride = me.options.time.displayFormats.millisecond; // Pick the longest format for guestimation + + var exampleLabel = me.tickFormatFunction(moment(exampleTime), 0, [], formatOverride); + var tickLabelWidth = me.getLabelWidth(exampleLabel); + var innerWidth = me.isHorizontal() ? me.width : me.height; + + var capacity = Math.floor(innerWidth / tickLabelWidth); + return capacity > 0 ? capacity : 1; + } + }); + + scaleService.registerScaleType('time', TimeScale, defaultConfig); +}; + +},{"1":1,"26":26,"33":33,"34":34,"46":46}]},{},[7])(7) +}); diff --git a/modules/servers/upCloudVps/templates/error.tpl b/modules/servers/upCloudVps/templates/error.tpl new file mode 100644 index 0000000..7653a44 --- /dev/null +++ b/modules/servers/upCloudVps/templates/error.tpl @@ -0,0 +1,9 @@ +{if $message} +
+{$message} +
+{else} +
+Something went wrong. Please try again later. +
+{/if} diff --git a/modules/servers/upCloudVps/templates/overview.tpl b/modules/servers/upCloudVps/templates/overview.tpl new file mode 100644 index 0000000..bed3531 --- /dev/null +++ b/modules/servers/upCloudVps/templates/overview.tpl @@ -0,0 +1,591 @@ + + + + +
+ + + diff --git a/modules/servers/upCloudVps/upCloudVps.php b/modules/servers/upCloudVps/upCloudVps.php new file mode 100644 index 0000000..76321b2 --- /dev/null +++ b/modules/servers/upCloudVps/upCloudVps.php @@ -0,0 +1,238 @@ + 'upCloud VPS', + 'APIVersion' => '1.3', + 'ServiceSingleSignOnLabel' => 'Login to Panel as User', + 'RequiresServer' => true, + ]; +} + +function upCloudVps_ConfigOptions(array $params) +{ + if (!Capsule::schema()->hasTable('mod_upCloudVps_bandwidth')) { + Capsule::schema()->create('mod_upCloudVps_bandwidth', function ($table) { + $table->integer('serviceId'); + $table->string('IPv4'); + $table->string('IPv6'); + $table->timestamp('created_at')->default(Capsule::raw('CURRENT_TIMESTAMP')); + }); + } + + + $server = Capsule::table('tblservers') + ->join('tblservergroupsrel', 'tblservergroupsrel.serverid', '=', 'tblservers.id') + ->where('tblservergroupsrel.groupid', App::getFromRequest('servergroup')) + ->where('tblservers.disabled', '0') + ->first(); + + $params['serverusername'] = $server->username; + $params['serverpassword'] = decrypt($server->password); + $manager = new configOptions($params); + return $manager->configs(); +} + +function upCloudVps_TestConnection(array $params) +{ + try { + $manager = new upCloudVps($params); + $account = $manager->GetAccountInfo(); + if($account['response_code'] == '200'){ + $success = true; + } else { + $errorMsg = 'Invalid Credentials'; + } +} catch (\Exception $e) { + $success = false; + $errorMsg = $e->getMessage(); + } + return ['success' => $success, 'error' => $errorMsg]; + } + + function upCloudVps_CreateAccount(array $params) + { + if ($params['status'] != 'Pending' && $params['status'] != 'Terminated') { + return 'Cannot create service.'; + } + try { + $vmManager = new vmManager($params); + return $vmManager->create(); + } catch (\Exception $e) { + return $e->getMessage(); + } + } + + function upCloudVps_TerminateAccount(array $params) + { + if ($params['status'] != 'Active' && $params['status'] != 'Suspended') { + return 'Cannot terminate service'; + } + try{ + $vmManager = new vmManager($params); + return $vmManager->terminate(); + } catch (\Exception $e) { + return $e->getMessage(); + } + } + + function upCloudVps_SuspendAccount(array $params) + { + if ($params['status'] == 'Terminated') { + return 'Cannot suspend terminated service'; + } + try { + $vmManager = new vmManager($params); + return $vmManager->stop(); + } catch (\Exception $e) { + return $e->getMessage(); + } + } + + function upCloudVps_UnsuspendAccount(array $params) + { + if ($params['status'] == 'Terminated') { + return 'Cannot unsuspend terminated service'; + } + try { + $vmManager = new vmManager($params); + return $vmManager->start(); + } catch (\Exception $e) { + return $e->getMessage(); + } + } + + function upCloudVps_StopVPS(array $params) + { + try { + $vmManager = new vmManager($params); + return $vmManager->stop(); + } catch (\Exception $e) { + return $e->getMessage(); + } + } + + function upCloudVps_StartVPS(array $params) + { + try { + $vmManager = new vmManager($params); + return $vmManager->start(); + } catch (\Exception $e) { + return $e->getMessage(); + } + } + + function upCloudVps_RebootVPS(array $params) + { + try { + $vmManager = new vmManager($params); + return $vmManager->reboot(); + } catch (\Exception $e) { + return $e->getMessage(); + } + } + + function upCloudVps_AdminCustomButtonArray() + { + return [ + 'Start VPS' => 'StartVPS', + 'Stop VPS' => 'StopVPS', + 'Reboot VPS' => 'RebootVPS', + ]; + } + + function upCloudVps_ReverseDNS( $params ) + { + try { + $params["ip"] = $_POST['ip']; + $params["rdns"] = $_POST['rdns']; + $vmManager = new vmManager($params); + return $vmManager->rdns(); + + } catch (\Exception $e) { + return $e->getMessage(); + } + } + + function upCloudVps_AdminServicesTabFields(array $params) + { + if ($params['status'] != 'Active') { + return []; + } + try { + $adminManager = new adminManager($params); + return $adminManager->adminarea(); + } catch (\Exception $e) { + return ['error' => $e->getMessage()]; + } + } + + function upCloudVps_AdminServicesTabFieldsSave( $params ) + { + try { + if($_REQUEST['act'] == 'rDNS'){ + upCloudVps_ReverseDNS( $params ); + } +} catch (\Exception $e) { + return $e->getMessage(); + } + return 'success'; + } + + function upCloudVps_ChangePackage(array $params) + { + try { + $vmManager = new vmManager($params); + return $vmManager->upgradePlan(); + } catch (\Exception $e) { + return $e->getMessage(); + } + } + + + function upCloudVps_ClientArea(array $params) + { + if ($params['status'] != 'Active') { + return []; + } + try { + Helper::clientAreaPrimarySidebarHook($params); + $action = App::getFromRequest('subaction'); + if (!empty($action)) { + Helper::ajaxAction($params, $action); + } + $clientManager = new clientManager($params); + $vm = $clientManager->getData('details'); + return [ + 'templatefile' => 'templates/overview.tpl', + 'templateVariables' => [ + 'vm' => $vm, + '_LANG' => Helper::getLang(), + ], + ]; + } catch (\Exception $e) { + return ['tabOverviewReplacementTemplate' => 'templates/error.tpl']; + } + } + + function upCloudVps_UsageUpdate(array $params) + { + try { + $manager = new usageUpdate($params); + $manager->usage(); + } catch (\Exception $e) { + return $e->getMessage(); + } + }
+
+ +

{$_LANG.basicdetails}

+

{$_LANG.basicdescription}

+
+ + + + + + + + + + + +
{$_LANG['overviewdetails']}
{$_LANG['Hostname']} {$vm['details']['hostname']}
{$_LANG['IPAddress']} {$vm['details']['ip']}
{$_LANG['username']} {$username}
{$_LANG['password']} + + +
{$_LANG['OS']} {$vm['details']['template']}
{$_LANG['Status']} + {$vm['details']['status']|upper} + +
{$_LANG['Location']} {$vm['details']['location']}
+ +
+ + +
+
+
+ +

{$_LANG['vnc']['title']}

+

{$_LANG['vnc']['description']}

+
+ +
+ + + {if $vm['details']['vnc'] == 'on'} + {$_LANG['vnc']['on']} + {else} + {$_LANG['vnc']['off']} + {/if} + +
+
+ + +
+ +
+
+ + + + + + + + + + +
+ +
+ + + + {if $vm['details']['vnc'] == 'on'} + + + {else} + + + {/if} +
+
+ + + + + + + +
+
+
+
+ +

{$_LANG.network.title}

+

{$_LANG.network.description}

+
+
+ + + + + + + + + + + + + +
{$_LANG["IPAddresses"]}
{$_LANG['Family']}{$_LANG['IPAddress']}{$_LANG['reversePTR']}{$_LANG['Action']}
+
+
+ + + + +
+
+
+
+ +

{$_LANG.server.title}

+

{$_LANG.server.description}

+
+
+ +
+ + + + + + + + + + + + + + + +
+ +
+
+
+ + + +
+
+
+
+ +

{$_LANG.bandwidth.title}

+

{$_LANG.bandwidth.description}

+
+
+
+ + + +
+ + +