YouTube - Version 12.0

Version Description

Download this release

Release Info

Developer embedplus
Plugin Icon 128x128 YouTube
Version 12.0
Comparing to
See all releases

Code changes from version 11.9.2 to 12.0

Files changed (50) hide show
  1. images/icon-hw-appearance.png +0 -0
  2. images/icon-hw-description.png +0 -0
  3. images/icon-hw-performance.png +0 -0
  4. images/icon-hw-placement.png +0 -0
  5. images/icon-hw-revenue.png +0 -0
  6. images/icon-hw-turnon.png +0 -0
  7. images/icon-monetize-dark.svg +28 -0
  8. images/icon-monetize.svg +28 -0
  9. images/icon-monetize_16x18.png +0 -0
  10. images/icon-monetize_16x18bw.png +0 -0
  11. images/icon-player-live.jpg +0 -0
  12. images/icon-player-live.png +0 -0
  13. images/icon-player-money.jpg +0 -0
  14. images/icon-player-money.png +0 -0
  15. images/icon-player-privacy.png +0 -0
  16. images/icon-player-single.png +0 -0
  17. images/icon-playlist-gallery.png +0 -0
  18. images/icon-playlist-self.png +0 -0
  19. images/privacy-eye.png +0 -0
  20. images/ss-vi-dashreports.png +0 -0
  21. images/ss-vi-dashrevenue.png +0 -0
  22. images/ss-vi-wizbutton.png +0 -0
  23. images/vi-demo-1.gif +0 -0
  24. images/vi-demo-2.gif +0 -0
  25. images/vi-mobile-phone.png +0 -0
  26. images/vi-source-billboard.png +0 -0
  27. images/vi-source-bonnier.png +0 -0
  28. images/vi-source-cbc.png +0 -0
  29. images/vi-source-itn.png +0 -0
  30. images/vi-source-nowthis.png +0 -0
  31. images/vi-source-thetelegraph.png +0 -0
  32. images/vi_logo.png +0 -0
  33. images/vi_logo.svg +12 -0
  34. images/vi_logo16x16.png +0 -0
  35. images/vi_logo_bw.png +0 -0
  36. images/vi_logo_bw.svg +12 -0
  37. images/vi_logo_bw16x16.png +0 -0
  38. includes/vi/vi_actions.php +29 -0
  39. includes/vi/vi_admin_menu.php +15 -0
  40. includes/vi/vi_login_complete.php +5 -0
  41. includes/vi/vi_login_success.php +5 -0
  42. includes/vi/vi_login_success_content.php +18 -0
  43. includes/vi/vi_registration_form.php +148 -0
  44. readme.txt +5 -2
  45. scripts/alertify/alertify-defaults.js +87 -0
  46. scripts/alertify/alertify-defaults.min.js +2 -0
  47. scripts/alertify/alertify.js +3595 -0
  48. scripts/alertify/alertify.min.js +3 -0
  49. scripts/chartjs/chart.js +14156 -0
  50. scripts/chartjs/chart.min.js +14135 -0
images/icon-hw-appearance.png ADDED
Binary file
images/icon-hw-description.png ADDED
Binary file
images/icon-hw-performance.png ADDED
Binary file
images/icon-hw-placement.png ADDED
Binary file
images/icon-hw-revenue.png ADDED
Binary file
images/icon-hw-turnon.png ADDED
Binary file
images/icon-monetize-dark.svg ADDED
@@ -0,0 +1,28 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?xml version="1.0" encoding="utf-8"?>
2
+ <!-- Generator: Adobe Illustrator 22.0.1, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
3
+ <svg version="1.1" id="Capa_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
4
+ viewBox="0 0 485.6 485.6" style="enable-background:new 0 0 485.6 485.6;" xml:space="preserve">
5
+ <style type="text/css">
6
+ .st0{fill:#06b6c0;}
7
+ </style>
8
+ <g>
9
+ <g>
10
+ <path class="st0" d="M301.5,368l-0.8-1.3c-6.7-10.5-5.8-23,2.3-32.7c1.2-1.4,2.4-2.8,4-4.5c4.8-4.8,12.4-11.3,23.4-11.3
11
+ c6.7,0,13.3,2.4,20,7.4c10.5,7.7,20.9,15.9,30.9,23.6c2,1.5,3.9,3,5.9,4.5c1.5-1.7,2.9-3.5,4.5-5.1c10.4-11.9,20.6-23.9,30.9-35.8
12
+ c5.6-19.2,8.7-39.5,8.7-60.6c0-119-96.6-215.6-215.6-215.6C96.6,36.8,0,133.4,0,252.4S96.6,468,215.6,468
13
+ c48.3,0,93-15.9,128.9-42.8c-11.5-15-23.2-30-34.6-45C306.7,376.2,304,372,301.5,368z M287,341.6c-10.4,12.7-24,21.2-39.9,25.5
14
+ c-7,1.9-10,5.6-9.6,12.8c0.3,7.1,0,14.2,0,21.2c0,6.3-3.2,9.6-9.4,9.9c-7.5,0.2-15.2,0.2-22.9,0c-6.6-0.1-9.7-3.9-9.9-10.4
15
+ c-0.1-5.1-0.1-10.3-0.1-15.4c-0.1-11.4-0.5-11.8-11.5-13.6c-14-2.2-27.7-5.5-40.6-11.6c-10.1-4.9-11.1-7.4-8.2-18
16
+ c2.2-7.9,4.3-15.8,6.7-23.6c2.8-9.1,5.2-10.2,13.6-5.8c14.2,7.4,29.2,11.5,45,13.5c10.2,1.3,20.1,0.2,29.6-3.8
17
+ c17.5-7.7,20.3-28.1,5.5-40.3c-5.1-4.2-10.8-7.2-16.8-9.9c-15.4-6.7-31.5-12-46.1-20.7c-23.7-14.2-38.7-33.5-36.9-62.3
18
+ c2-32.5,20.3-52.8,50.1-63.6c12.3-4.5,12.3-4.3,12.4-17.2c0-4.3,0-8.7,0-13c0.2-9.7,1.9-11.4,11.5-11.6c3-0.1,6,0,8.9,0
19
+ c20.5,0,20.5,0,20.7,20.5c0.1,14.5,0,14.6,14.5,16.8c11.1,1.7,21.7,5,31.9,9.5c5.7,2.4,7.8,6.5,6,12.4c-2.6,8.8-5,17.8-7.8,26.6
20
+ c-2.7,8.4-5.3,9.5-13.3,5.7c-16-7.8-32.8-11-50.6-10c-4.6,0.2-9.2,0.9-13.5,2.8c-15.3,6.7-17.8,23.6-4.8,34
21
+ c6.6,5.2,14.2,9.1,21.9,12.3c13.6,5.6,27.2,11,40,18.1C304.8,255,315.9,306.4,287,341.6z"/>
22
+ <path class="st0" d="M480.3,302c-4.8-3.9-8.1-5.9-11.1-5.9c-3.4,0-6.4,2.6-11,7.9c-23.2,26.9-29.5,34.3-52.8,61.2
23
+ c-5.2,6-10.4,12-15.8,18.2c-2.7-2.1-5.2-4-7.7-6c-13.7-10.6-27.2-21.3-41-31.6c-3.1-2.3-5.7-3.5-8.1-3.5c-3,0-5.8,1.8-9.2,5.3
24
+ c-1,1-1.8,2-2.7,3.1c-2.5,3-2.9,6.2-0.8,9.5c2.5,3.9,5,7.9,7.8,11.6c18.3,23.8,36.6,47.5,55,71.3c2.2,2.9,4.8,4.4,7.4,4.4
25
+ c2.8,0,5.6-1.7,8-5.2c33.7-48.2,50.3-71.9,83.9-120.1C487.3,314.9,486.9,307.5,480.3,302z"/>
26
+ </g>
27
+ </g>
28
+ </svg>
images/icon-monetize.svg ADDED
@@ -0,0 +1,28 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?xml version="1.0" encoding="utf-8"?>
2
+ <!-- Generator: Adobe Illustrator 22.0.1, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
3
+ <svg version="1.1" id="Capa_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
4
+ viewBox="0 0 485.6 485.6" style="enable-background:new 0 0 485.6 485.6;" xml:space="preserve">
5
+ <style type="text/css">
6
+ .st0{fill:#0ECAD4;}
7
+ </style>
8
+ <g>
9
+ <g>
10
+ <path class="st0" d="M301.5,368l-0.8-1.3c-6.7-10.5-5.8-23,2.3-32.7c1.2-1.4,2.4-2.8,4-4.5c4.8-4.8,12.4-11.3,23.4-11.3
11
+ c6.7,0,13.3,2.4,20,7.4c10.5,7.7,20.9,15.9,30.9,23.6c2,1.5,3.9,3,5.9,4.5c1.5-1.7,2.9-3.5,4.5-5.1c10.4-11.9,20.6-23.9,30.9-35.8
12
+ c5.6-19.2,8.7-39.5,8.7-60.6c0-119-96.6-215.6-215.6-215.6C96.6,36.8,0,133.4,0,252.4S96.6,468,215.6,468
13
+ c48.3,0,93-15.9,128.9-42.8c-11.5-15-23.2-30-34.6-45C306.7,376.2,304,372,301.5,368z M287,341.6c-10.4,12.7-24,21.2-39.9,25.5
14
+ c-7,1.9-10,5.6-9.6,12.8c0.3,7.1,0,14.2,0,21.2c0,6.3-3.2,9.6-9.4,9.9c-7.5,0.2-15.2,0.2-22.9,0c-6.6-0.1-9.7-3.9-9.9-10.4
15
+ c-0.1-5.1-0.1-10.3-0.1-15.4c-0.1-11.4-0.5-11.8-11.5-13.6c-14-2.2-27.7-5.5-40.6-11.6c-10.1-4.9-11.1-7.4-8.2-18
16
+ c2.2-7.9,4.3-15.8,6.7-23.6c2.8-9.1,5.2-10.2,13.6-5.8c14.2,7.4,29.2,11.5,45,13.5c10.2,1.3,20.1,0.2,29.6-3.8
17
+ c17.5-7.7,20.3-28.1,5.5-40.3c-5.1-4.2-10.8-7.2-16.8-9.9c-15.4-6.7-31.5-12-46.1-20.7c-23.7-14.2-38.7-33.5-36.9-62.3
18
+ c2-32.5,20.3-52.8,50.1-63.6c12.3-4.5,12.3-4.3,12.4-17.2c0-4.3,0-8.7,0-13c0.2-9.7,1.9-11.4,11.5-11.6c3-0.1,6,0,8.9,0
19
+ c20.5,0,20.5,0,20.7,20.5c0.1,14.5,0,14.6,14.5,16.8c11.1,1.7,21.7,5,31.9,9.5c5.7,2.4,7.8,6.5,6,12.4c-2.6,8.8-5,17.8-7.8,26.6
20
+ c-2.7,8.4-5.3,9.5-13.3,5.7c-16-7.8-32.8-11-50.6-10c-4.6,0.2-9.2,0.9-13.5,2.8c-15.3,6.7-17.8,23.6-4.8,34
21
+ c6.6,5.2,14.2,9.1,21.9,12.3c13.6,5.6,27.2,11,40,18.1C304.8,255,315.9,306.4,287,341.6z"/>
22
+ <path class="st0" d="M480.3,302c-4.8-3.9-8.1-5.9-11.1-5.9c-3.4,0-6.4,2.6-11,7.9c-23.2,26.9-29.5,34.3-52.8,61.2
23
+ c-5.2,6-10.4,12-15.8,18.2c-2.7-2.1-5.2-4-7.7-6c-13.7-10.6-27.2-21.3-41-31.6c-3.1-2.3-5.7-3.5-8.1-3.5c-3,0-5.8,1.8-9.2,5.3
24
+ c-1,1-1.8,2-2.7,3.1c-2.5,3-2.9,6.2-0.8,9.5c2.5,3.9,5,7.9,7.8,11.6c18.3,23.8,36.6,47.5,55,71.3c2.2,2.9,4.8,4.4,7.4,4.4
25
+ c2.8,0,5.6-1.7,8-5.2c33.7-48.2,50.3-71.9,83.9-120.1C487.3,314.9,486.9,307.5,480.3,302z"/>
26
+ </g>
27
+ </g>
28
+ </svg>
images/icon-monetize_16x18.png ADDED
Binary file
images/icon-monetize_16x18bw.png ADDED
Binary file
images/icon-player-live.jpg ADDED
Binary file
images/icon-player-live.png ADDED
Binary file
images/icon-player-money.jpg ADDED
Binary file
images/icon-player-money.png ADDED
Binary file
images/icon-player-privacy.png ADDED
Binary file
images/icon-player-single.png ADDED
Binary file
images/icon-playlist-gallery.png ADDED
Binary file
images/icon-playlist-self.png ADDED
Binary file
images/privacy-eye.png ADDED
Binary file
images/ss-vi-dashreports.png ADDED
Binary file
images/ss-vi-dashrevenue.png ADDED
Binary file
images/ss-vi-wizbutton.png ADDED
Binary file
images/vi-demo-1.gif ADDED
Binary file
images/vi-demo-2.gif ADDED
Binary file
images/vi-mobile-phone.png ADDED
Binary file
images/vi-source-billboard.png ADDED
Binary file
images/vi-source-bonnier.png ADDED
Binary file
images/vi-source-cbc.png ADDED
Binary file
images/vi-source-itn.png ADDED
Binary file
images/vi-source-nowthis.png ADDED
Binary file
images/vi-source-thetelegraph.png ADDED
Binary file
images/vi_logo.png ADDED
Binary file
images/vi_logo.svg ADDED
@@ -0,0 +1,12 @@
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?xml version="1.0" encoding="UTF-8" standalone="no"?>
2
+ <svg width="66px" height="67px" viewBox="0 0 66 67" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
3
+ <defs></defs>
4
+ <g id="Page-1" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
5
+ <g id="vi_logo" transform="translate(2.000000, 2.000000)">
6
+ <path d="M31.3,62.4 C48.5,62.4 62.4,48.5 62.4,31.2 C62.4,14 48.5,0 31.3,0 C14.1,0.1 0.2,14 0.2,31.2 C0.2,48.4 14.1,62.4 31.3,62.4" id="Fill-1" fill="#FEF200"></path>
7
+ <path d="M31,63 C48.1,63 62,48.9 62,31.5 C62,14.1 48.1,0 31,0 C13.9,0 0,14.1 0,31.5 C0,48.9 13.9,63 31,63 L31,63 L31,63 L31,63 L31,63 Z" id="Stroke-2" stroke="#000000" stroke-width="4"></path>
8
+ <path d="M22,45 L28,45 L36.3,24.5 L30.1,24.5 L27.5,31 C26.7,33.1 25.8,35.5 25,37.5 L25,37.5 C24.3,35.4 23.5,33.2 22.7,31 L20.3,24.4 L13.6,24.4 L22,45 L22,45 L22,45 L22,45 L22,45 Z M38.4,45 L44.7,45 L44.7,24.5 L38.4,24.5 L38.4,45 L38.4,45 L38.4,45 L38.4,45 Z" id="Fill-4" fill="#000000"></path>
9
+ <polygon id="Fill-5" fill="#000000" points="38.4 21.1 44.7 21.1 44.7 14.8 38.4 14.8"></polygon>
10
+ </g>
11
+ </g>
12
+ </svg>
images/vi_logo16x16.png ADDED
Binary file
images/vi_logo_bw.png ADDED
Binary file
images/vi_logo_bw.svg ADDED
@@ -0,0 +1,12 @@
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?xml version="1.0" encoding="UTF-8" standalone="no"?>
2
+ <svg width="66px" height="67px" viewBox="0 0 66 67" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
3
+ <defs></defs>
4
+ <g id="Page-1" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
5
+ <g id="vi_logo" transform="translate(2.000000, 2.000000)">
6
+ <path d="M31.3,62.4 C48.5,62.4 62.4,48.5 62.4,31.2 C62.4,14 48.5,0 31.3,0 C14.1,0.1 0.2,14 0.2,31.2 C0.2,48.4 14.1,62.4 31.3,62.4" id="Fill-1" fill="#FEF200"></path>
7
+ <path d="M31,63 C48.1,63 62,48.9 62,31.5 C62,14.1 48.1,0 31,0 C13.9,0 0,14.1 0,31.5 C0,48.9 13.9,63 31,63 L31,63 L31,63 L31,63 L31,63 Z" id="Stroke-2" stroke="#000000" stroke-width="4"></path>
8
+ <path d="M22,45 L28,45 L36.3,24.5 L30.1,24.5 L27.5,31 C26.7,33.1 25.8,35.5 25,37.5 L25,37.5 C24.3,35.4 23.5,33.2 22.7,31 L20.3,24.4 L13.6,24.4 L22,45 L22,45 L22,45 L22,45 L22,45 Z M38.4,45 L44.7,45 L44.7,24.5 L38.4,24.5 L38.4,45 L38.4,45 L38.4,45 L38.4,45 Z" id="Fill-4" fill="#000000"></path>
9
+ <polygon id="Fill-5" fill="#000000" points="38.4 21.1 44.7 21.1 44.7 14.8 38.4 14.8"></polygon>
10
+ </g>
11
+ </g>
12
+ </svg>
images/vi_logo_bw16x16.png ADDED
Binary file
includes/vi/vi_actions.php ADDED
@@ -0,0 +1,29 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ add_action("wp_ajax_my_embedplus_vi_cache_endpoints_ajax", array(get_class(), 'vi_cache_endpoints_ajax'));
3
+ add_action("wp_ajax_my_embedplus_vi_login_ajax", array(get_class(), 'vi_login_ajax'));
4
+ add_action("wp_ajax_my_embedplus_vi_toggle_ajax", array(get_class(), 'vi_toggle_ajax'));
5
+ add_action("wp_ajax_my_embedplus_vi_hide_feature_ajax", array(get_class(), 'vi_hide_feature_ajax'));
6
+
7
+ add_action('admin_init', array(get_class(), 'vi_adstxt_download'));
8
+
9
+ add_shortcode('embed-vi-ad', array(get_class(), 'vi_js_shortcode'));
10
+
11
+ if ((bool)self::$alloptions[self::$opt_vi_active])
12
+ {
13
+ add_filter('the_content', array(get_class(), 'vi_js_placement'));
14
+ self::wp_insert_vi_gdpr_popup_init();
15
+ }
16
+
17
+ if (self::vi_logged_in())
18
+ {
19
+ add_action("wp_ajax_my_embedplus_vi_logout_ajax", array(get_class(), 'vi_logout_ajax'));
20
+ add_action("wp_ajax_my_embedplus_vi_reports_ajax", array(get_class(), 'vi_reports_ajax'));
21
+
22
+ add_filter('cron_schedules', array(get_class(), 'vi_cron_interval'));
23
+ add_action('ytvi_cron_cache_js_hook', array(get_class(), 'vi_cron_cache_js'));
24
+ if (!wp_next_scheduled('ytvi_cron_cache_js_hook'))
25
+ {
26
+ wp_schedule_event(time(), 'ytvi_fifteen_days', 'ytvi_cron_cache_js_hook');
27
+ //wp_schedule_event(time(), 'ytvi_two_minutes', 'ytvi_cron_cache_js_hook'); // testing
28
+ }
29
+ }
includes/vi/vi_admin_menu.php ADDED
@@ -0,0 +1,15 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ if (self::vi_logged_in())
4
+ {
5
+ self::$admin_page_hooks[] = add_submenu_page('youtube-my-preferences', 'Monetize With vi', '<img style="width: 16px; height: 16px; vertical-align: text-top;" src="' . plugins_url(self::$folder_name . '/images/icon-monetize.svg') . '" />&nbsp;&nbsp;Monetize', 'manage_options', 'youtube-ep-vi', array(get_class(), 'vi_admin_dashboard'));
6
+ }
7
+ else if (!(bool) (self::$alloptions[self::$opt_vi_hide_monetize_tab]))
8
+ {
9
+ $page_parent = null;
10
+ if (filter_input(INPUT_GET, 'page') == 'youtube-ep-vi')
11
+ {
12
+ $page_parent = 'youtube-my-preferences';
13
+ }
14
+ self::$admin_page_hooks[] = add_submenu_page($page_parent, 'Monetize With vi', '<img style="width: 16px; height: 16px; vertical-align: text-top;" src="' . plugins_url(self::$folder_name . '/images/icon-monetize.svg') . '" />&nbsp;&nbsp;Monetize? <sup>new</sup>', 'manage_options', 'youtube-ep-vi', array(get_class(), 'vi_admin_dashboard_pre'));
15
+ }
includes/vi/vi_login_complete.php ADDED
@@ -0,0 +1,5 @@
 
 
 
 
 
1
+ <div class="ytvi-wrap">
2
+ <div class="ytvi-login-complete">
3
+ <?php include_once(EPYTVI_INCLUDES_PATH . 'vi_login_success_content.php'); ?>
4
+ </div>
5
+ </div>
includes/vi/vi_login_success.php ADDED
@@ -0,0 +1,5 @@
 
 
 
 
 
1
+ <div class="ytvi-wrap vi-demo-col-content">
2
+ <div class="ytvi-login-success">
3
+ <?php include_once(EPYTVI_INCLUDES_PATH . 'vi_login_success_content.php'); ?>
4
+ </div>
5
+ </div>
includes/vi/vi_login_success_content.php ADDED
@@ -0,0 +1,18 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <h1>Hooray!</h1>
2
+ <p class="ytvi-login-success-message"></p>
3
+ <a class="button-primary vi-logged-in-goto" target="_blank" href="<?php echo admin_url('admin.php?page=youtube-ep-vi'); ?>">
4
+ <?php
5
+ if (!self::vi_script_setup_done())
6
+ {
7
+ ?>
8
+ Click here to complete setup &raquo;
9
+ <?php
10
+ }
11
+ else
12
+ {
13
+ ?>
14
+ You're now ready to monetize &raquo;
15
+ <?php
16
+ }
17
+ ?>
18
+ </a>
includes/vi/vi_registration_form.php ADDED
@@ -0,0 +1,148 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <div class="vi-demo-col-phone">
2
+ <div class="vi-demo-mobile-caption-list">
3
+ <div class="vi-demo-mobile-caption vi-demo-caption-1 demo-hide">
4
+ <p>
5
+ A game blogger posts Nintendo Switch game reviews and monetizes them using vi. The player combines a funny ad about Switch's chipmaker with an interview about Switch game quality.
6
+ </p>
7
+ </div>
8
+ <div class="vi-demo-mobile-caption vi-demo-caption-2">
9
+ <p>
10
+ A food blogger posts a tasty pasta recipe that's monetized using vi. The player intelligently combines a food promotion followed by directions for a complementary salad into <strong>one</strong> ad unit.
11
+ </p>
12
+ </div>
13
+ </div>
14
+ <div class="vi-demo-mobile">
15
+ <div class="vi-demo-mobile-ratio">
16
+ <img class="vi-demo-screen-1 vi-demo-screen" src="<?php echo plugins_url(self::$folder_name . '/images/vi-demo-1.gif') ?>"/>
17
+ <img class="vi-demo-screen-2 vi-demo-screen" src="<?php echo plugins_url(self::$folder_name . '/images/vi-demo-2.gif') ?>"/>
18
+ </div>
19
+ </div>
20
+
21
+ <p class="vi-ad-source-row">
22
+ <img class="vi-ad-source" src="<?php echo plugins_url(self::$folder_name . '/images/vi-source-billboard.png') ?>"/>
23
+ <img class="vi-ad-source" src="<?php echo plugins_url(self::$folder_name . '/images/vi-source-nowthis.png') ?>"/>
24
+ <img class="vi-ad-source" src="<?php echo plugins_url(self::$folder_name . '/images/vi-source-bonnier.png') ?>"/>
25
+ <img class="vi-ad-source" src="<?php echo plugins_url(self::$folder_name . '/images/vi-source-cbc.png') ?>"/>
26
+ <img class="vi-ad-source" src="<?php echo plugins_url(self::$folder_name . '/images/vi-source-thetelegraph.png') ?>"/>
27
+ <img class="vi-ad-source" src="<?php echo plugins_url(self::$folder_name . '/images/vi-source-itn.png') ?>"/>
28
+ </p>
29
+
30
+ </div>
31
+ <div class="vi-demo-col-content">
32
+ <div class="vi-demo">
33
+ <p class="vi-demo-lede">
34
+ You now have the option to make money embedding quality video ads with this plugin. The ads that you will embed are privacy/GDPR friendly,
35
+ delivered by <img class="vi-logo-text" alt="vi" src="<?php echo plugins_url(self::$folder_name . '/images/vi_logo.svg'); ?>">
36
+ <a href="https://www.vi.ai/publisher-video-monetization/?aid=WP_embedplus&utm_source=Wordpress&utm_medium=WP_embedplus" target="_blank">video intelligence</a>,
37
+ and completely separate from your YouTube embeds.
38
+ </p>
39
+ <p>
40
+ Instead of an ordinary video ad, vi ads are so effective because they wrap an ad with additional useful video content
41
+ that <strong>automatically matches your site's topics and attract your visitors' attention. </strong>
42
+ It's like free related content that you're paid to embed. Plus, you'll get <strong>increased visitor time-on-site and high CPMs from vi.</strong>
43
+ </p>
44
+ <p>
45
+ Check out the demos on the right. Below, sign up for free in minutes.
46
+ </p>
47
+ </div>
48
+ <div class="ytvi-wrap">
49
+ <div class="ytvi-step ytvi-step-1">
50
+ <div class="ytvi-step-1--form">
51
+ <div class="side-signup ytprefs-ajax-form">
52
+ <h1>Sign up with vi.ai</h1>
53
+ <h2>Join 30,000+ publishers</h2>
54
+ <p class="description">Where should we send your welcome and revenue info?</p>
55
+ <p>
56
+ <input class="textinput regular-text ytvi-register-email" type="text" placeholder="Your email" />
57
+ </p>
58
+ <p class="description">
59
+ <label>
60
+ <input type="checkbox" class="ytvi-step-1--confirm"/>
61
+ I understand that vi will create my account using my email, my domain, and EmbedPlus as the referral.
62
+ </label>
63
+ </p>
64
+ <p>
65
+ <input disabled class="button-primary ytvi-step-1--submit-register ytprefs-ajax-form--submit" type="button" value="Next &raquo;"/>
66
+ <a class="vi-forgot-pw" href="https://www.vi.ai/legals/?aid=WP_embedplus&utm_source=Wordpress&utm_medium=WP_embedplus" target="_blank">vi.ai Terms & Privacy &raquo;</a>
67
+ </p>
68
+ </div>
69
+ <div class="side-login ytprefs-ajax-form">
70
+ <h1>Log in to vi.ai</h1>
71
+ <h2>Start embedding quality ads</h2>
72
+ <p class="description">Already signed up? Login here using the info from your welcome email.</p>
73
+ <p>
74
+ <input class="textinput regular-text ytvi-login-email" type="text" placeholder="Your email" />
75
+ </p>
76
+ <p>
77
+ <input class="textinput regular-text ytvi-password" type="password" placeholder="Password" />
78
+ </p>
79
+ <p>
80
+ <input class="button-primary ytvi-step-1--submit-login ytprefs-ajax-form--submit" type="button" value="Log In &raquo;">
81
+ <a class="vi-forgot-pw" href="https://dashboard.vi.ai/resetPassword/" target="_blank">Forgot Password?</a>
82
+ </p>
83
+ </div>
84
+ <div class="vi-contact-support">
85
+ <p class="center"><em>Need help signing up or signing in? Contact support at <strong><a href="mailto:ext@embedplus.com">ext@embedplus.com</a></strong></em></p>
86
+ </div>
87
+ </div>
88
+ <p class="box-vi-not-interested">
89
+ Not interested? You can hide this by <button class="button button-small vi-cover-prompt-no" type="button">clicking here</button> or checking <a class="vi-not-interested" target="_top" href="<?php echo admin_url('admin.php?page=youtube-my-preferences#vi_hide_monetize_tab') ?>"><em>Hide "Monetize" Feature</em></a> found in the YouTube Settings "Defaults" tab.
90
+ </p>
91
+ </div>
92
+ <div class="ytvi-step ytvi-step-2-loading">
93
+ <p class="ytvi-loading--message">
94
+ <img src="<?php echo EPYT_BASE_URL . 'images/ajax-loader.gif' ?>"> Loading sign up form...
95
+ </p>
96
+ </div>
97
+ <div class="ytvi-step ytvi-step-2">
98
+ <h3>Step 2 of 2 - Complete Registration</h3>
99
+ <div class="ytvi-registration">
100
+ <div class="ytvi-step-2-msg">
101
+ <ol>
102
+ <li><strong>Fill out</strong> the below</li>
103
+ <li><strong>Check your email</strong> for a confirmation link</li>
104
+ <li><strong>Come back</strong> and
105
+ <?php
106
+ $curr_screen = get_current_screen();
107
+ echo strpos($curr_screen->id, 'youtube-ep-vi') !== false ? 'refresh this page' : '<a target="_blank" href="' . admin_url('admin.php?page=youtube-ep-vi') . '">click here</a>'
108
+ ?> to login
109
+ </li>
110
+ </ol>
111
+ </div>
112
+ <iframe frameborder="0"></iframe>
113
+ <p class="center">
114
+ <input class="button-secondary ytvi-registration--cancel" type="button" value="&laquo; Go Back"/>
115
+ </p>
116
+ </div>
117
+ </div>
118
+ <div class="ytvi-step ytvi-login-loading">
119
+ <p class="ytvi-loading--message">
120
+ <img src="<?php echo EPYT_BASE_URL . 'images/ajax-loader.gif' ?>"> Logging you in...
121
+ </p>
122
+ </div>
123
+
124
+ </div>
125
+ </div>
126
+ <?php
127
+ if (!self::vi_cover_prompt_yes())
128
+ {
129
+ ?>
130
+ <div class="clearboth vi-cover-clear"></div>
131
+ <div class="vi-cover-prompt">
132
+ <h1>
133
+ Hey! We have a new optional feature to help you monetize using video.
134
+ <br>
135
+ Are you interested in learning more about it?
136
+ </h1>
137
+ <p class="vi-cover-prompt-buttons">
138
+ <button type="button" class="button-primary vi-cover-prompt-yes">Yes, tell me more.</button><span></span><button type="button" class="button-secondary vi-cover-prompt-no">No, hide this feature.</button>
139
+ </p>
140
+ <svg version="1.1" xmlns="http://www.w3.org/2000/svg" class="viblurfilter-svg">
141
+ <defs>
142
+ <filter id="viblurfilter">
143
+ <feGaussianBlur stdDeviation="8" />
144
+ </filter>
145
+ </defs>
146
+ </svg>
147
+ </div>
148
+ <?php } ?>
readme.txt CHANGED
@@ -4,7 +4,7 @@ Plugin Name: YouTube Embed
4
  Tags: youtube gallery, video gallery, youtube channel, youtube live, live stream
5
  Requires at least: 3.6.1
6
  Tested up to: 4.9
7
- Stable tag: 11.9.2
8
  License: GPLv3 or later
9
 
10
  YouTube Embed WordPress Plugin. Embed a responsive video, YouTube channel gallery, playlist gallery, or YouTube.com live stream (with GDPR options)
@@ -13,7 +13,7 @@ YouTube Embed WordPress Plugin. Embed a responsive video, YouTube channel galler
13
 
14
  **Your WordPress YouTube embed, YouTube gallery (channel and playlist), and even YouTube live stream can be customized in a wide variety of ways with this plugin. Here are a few recently added features:**
15
 
16
- * Improved GDPR compliance options: YouTube no cookie, YouTube API restrictions, customizable GDPR consent message
17
  * YouTube gallery capability (channel and playlist) – The ability to make playlist and channel embeds have a gallery layout. By default, the plugin can generate a grid-based [responsive playlist or channel gallery >>](https://www.embedplus.com/responsive-youtube-playlist-channel-gallery-for-wordpress.aspx). Your visitors can browse through pages of video thumbnails and choose from videos that are pulled from an entire YouTube channel or playlist.
18
  * YouTube gallery auto continuous play - embed a playlist or channel gallery and allow it to play one video after the next without requiring viewers to click a thumbnail
19
  * YouTube Live Stream - Given a link to a YouTube channel, the plugin wizard automatically finds a livestream if one is active in that channel and generates the embed code for you. On the settings page, you can also set defaults of what to automatically display if a live stream is not active at a given moment. For example, you can have your site display a gallery of a channel's entire video library so that users can have something to watch in the meantime. We hope it's a time saver.
@@ -148,6 +148,9 @@ You can also start and end each individual video at particular times. Like the a
148
 
149
  == Changelog ==
150
 
 
 
 
151
  = WordPress YouTube Embed 11.9.2 =
152
  * Makes the GDPR consent message display more compatible with other plugins (fixes content filter)
153
 
4
  Tags: youtube gallery, video gallery, youtube channel, youtube live, live stream
5
  Requires at least: 3.6.1
6
  Tested up to: 4.9
7
+ Stable tag: 12.0
8
  License: GPLv3 or later
9
 
10
  YouTube Embed WordPress Plugin. Embed a responsive video, YouTube channel gallery, playlist gallery, or YouTube.com live stream (with GDPR options)
13
 
14
  **Your WordPress YouTube embed, YouTube gallery (channel and playlist), and even YouTube live stream can be customized in a wide variety of ways with this plugin. Here are a few recently added features:**
15
 
16
+ * Privacy and Consent: Improved privacy and GDPR compliance options like YouTube no cookie, YouTube API restrictions, and a customizable GDPR consent message
17
  * YouTube gallery capability (channel and playlist) – The ability to make playlist and channel embeds have a gallery layout. By default, the plugin can generate a grid-based [responsive playlist or channel gallery >>](https://www.embedplus.com/responsive-youtube-playlist-channel-gallery-for-wordpress.aspx). Your visitors can browse through pages of video thumbnails and choose from videos that are pulled from an entire YouTube channel or playlist.
18
  * YouTube gallery auto continuous play - embed a playlist or channel gallery and allow it to play one video after the next without requiring viewers to click a thumbnail
19
  * YouTube Live Stream - Given a link to a YouTube channel, the plugin wizard automatically finds a livestream if one is active in that channel and generates the embed code for you. On the settings page, you can also set defaults of what to automatically display if a live stream is not active at a given moment. For example, you can have your site display a gallery of a channel's entire video library so that users can have something to watch in the meantime. We hope it's a time saver.
148
 
149
  == Changelog ==
150
 
151
+ = WordPress YouTube Embed 12.0 =
152
+ * Improves the admin interface, and includes a new optional feature for users that want to monetize their sites through contextual video from vi.ai.
153
+
154
  = WordPress YouTube Embed 11.9.2 =
155
  * Makes the GDPR consent message display more compatible with other plugins (fixes content filter)
156
 
scripts/alertify/alertify-defaults.js ADDED
@@ -0,0 +1,87 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ alertify.defaults.maintainFocus = false;
2
+ alertify.defaults.glossary.title = '&nbsp;';
3
+ alertify.defaults.transition = 'zoom';
4
+ //alertify.defaults.preventBodyShift = true;
5
+
6
+ alertify.YoutubeDialog || alertify.dialog('YoutubeDialog', function ()
7
+ {
8
+ var iframe;
9
+ return {
10
+ // dialog constructor function, this will be called when the user calls alertify.YoutubeDialog(videoId)
11
+ main: function (videoId)
12
+ {
13
+ //set the videoId setting and return current instance for chaining.
14
+ return this.set({
15
+ 'videoId': videoId
16
+ });
17
+ },
18
+ // we only want to override two options (padding and overflow).
19
+ setup: function ()
20
+ {
21
+ return {
22
+ options: {
23
+ //disable both padding and overflow control.
24
+ padding: !1,
25
+ overflow: !1,
26
+ }
27
+ };
28
+ },
29
+ // This will be called once the DOM is ready and will never be invoked again.
30
+ // Here we create the iframe to embed the video.
31
+ build: function ()
32
+ {
33
+ // create the iframe element
34
+ iframe = document.createElement('iframe');
35
+ iframe.frameBorder = "no";
36
+ iframe.width = "100%";
37
+ iframe.height = "100%";
38
+ // add it to the dialog
39
+ this.elements.content.appendChild(iframe);
40
+
41
+ //give the dialog initial height (half the screen height).
42
+ this.elements.body.style.minHeight = screen.height * .5 + 'px';
43
+ },
44
+ // dialog custom settings
45
+ settings: {
46
+ videoId: undefined
47
+ },
48
+ // listen and respond to changes in dialog settings.
49
+ settingUpdated: function (key, oldValue, newValue)
50
+ {
51
+ switch (key)
52
+ {
53
+ case 'videoId':
54
+ iframe.src = "https://www.youtube.com/embed/" + newValue + "?enablejsapi=1";
55
+ break;
56
+ }
57
+ },
58
+ // listen to internal dialog events.
59
+ hooks: {
60
+ // triggered when the dialog is closed, this is seperate from user defined onclose
61
+ onclose: function ()
62
+ {
63
+ iframe.contentWindow.postMessage('{"event":"command","func":"pauseVideo","args":""}', '*');
64
+ },
65
+ // triggered when a dialog option gets update.
66
+ // warning! this will not be triggered for settings updates.
67
+ onupdate: function (option, oldValue, newValue)
68
+ {
69
+ switch (option)
70
+ {
71
+ case 'resizable':
72
+ if (newValue)
73
+ {
74
+ this.elements.content.removeAttribute('style');
75
+ iframe && iframe.removeAttribute('style');
76
+ }
77
+ else
78
+ {
79
+ this.elements.content.style.minHeight = 'inherit';
80
+ iframe && (iframe.style.minHeight = 'inherit');
81
+ }
82
+ break;
83
+ }
84
+ }
85
+ }
86
+ };
87
+ });
scripts/alertify/alertify-defaults.min.js ADDED
@@ -0,0 +1,2 @@
 
 
1
+
2
+ alertify.defaults.maintainFocus=false;alertify.defaults.glossary.title="&nbsp;";alertify.defaults.transition="zoom";alertify.YoutubeDialog||alertify.dialog("YoutubeDialog",function(){var a;return{main:function(b){return this.set({videoId:b})},setup:function(){return{options:{padding:!1,overflow:!1,}}},build:function(){a=document.createElement("iframe");a.frameBorder="no";a.width="100%";a.height="100%";this.elements.content.appendChild(a);this.elements.body.style.minHeight=screen.height*0.5+"px"},settings:{videoId:undefined},settingUpdated:function(c,b,d){switch(c){case"videoId":a.src="https://www.youtube.com/embed/"+d+"?enablejsapi=1";break}},hooks:{onclose:function(){a.contentWindow.postMessage('{"event":"command","func":"pauseVideo","args":""}',"*")},onupdate:function(c,b,d){switch(c){case"resizable":if(d){this.elements.content.removeAttribute("style");a&&a.removeAttribute("style")}else{this.elements.content.style.minHeight="inherit";a&&(a.style.minHeight="inherit")}break}}}}});
scripts/alertify/alertify.js ADDED
@@ -0,0 +1,3595 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ /**
2
+ * alertifyjs 1.11.0 http://alertifyjs.com
3
+ * AlertifyJS is a javascript framework for developing pretty browser dialogs and notifications.
4
+ * Copyright 2017 Mohammad Younes <Mohammad@alertifyjs.com> (http://alertifyjs.com)
5
+ * Licensed under GPL 3 <https://opensource.org/licenses/gpl-3.0>*/
6
+ ( function ( window ) {
7
+ 'use strict';
8
+
9
+ /**
10
+ * Keys enum
11
+ * @type {Object}
12
+ */
13
+ var keys = {
14
+ ENTER: 13,
15
+ ESC: 27,
16
+ F1: 112,
17
+ F12: 123,
18
+ LEFT: 37,
19
+ RIGHT: 39
20
+ };
21
+ /**
22
+ * Default options
23
+ * @type {Object}
24
+ */
25
+ var defaults = {
26
+ autoReset:true,
27
+ basic:false,
28
+ closable:true,
29
+ closableByDimmer:true,
30
+ frameless:false,
31
+ maintainFocus:true, //global default not per instance, applies to all dialogs
32
+ maximizable:true,
33
+ modal:true,
34
+ movable:true,
35
+ moveBounded:false,
36
+ overflow:true,
37
+ padding: true,
38
+ pinnable:true,
39
+ pinned:true,
40
+ preventBodyShift:false, //global default not per instance, applies to all dialogs
41
+ resizable:true,
42
+ startMaximized:false,
43
+ transition:'pulse',
44
+ notifier:{
45
+ delay:5,
46
+ position:'bottom-right',
47
+ closeButton:false
48
+ },
49
+ glossary:{
50
+ title:'AlertifyJS',
51
+ ok: 'OK',
52
+ cancel: 'Cancel',
53
+ acccpt: 'Accept',
54
+ deny: 'Deny',
55
+ confirm: 'Confirm',
56
+ decline: 'Decline',
57
+ close: 'Close',
58
+ maximize: 'Maximize',
59
+ restore: 'Restore',
60
+ },
61
+ theme:{
62
+ input:'ajs-input',
63
+ ok:'ajs-ok',
64
+ cancel:'ajs-cancel',
65
+ }
66
+ };
67
+
68
+ //holds open dialogs instances
69
+ var openDialogs = [];
70
+
71
+ /**
72
+ * [Helper] Adds the specified class(es) to the element.
73
+ *
74
+ * @element {node} The element
75
+ * @className {string} One or more space-separated classes to be added to the class attribute of the element.
76
+ *
77
+ * @return {undefined}
78
+ */
79
+ function addClass(element,classNames){
80
+ element.className += ' ' + classNames;
81
+ }
82
+
83
+ /**
84
+ * [Helper] Removes the specified class(es) from the element.
85
+ *
86
+ * @element {node} The element
87
+ * @className {string} One or more space-separated classes to be removed from the class attribute of the element.
88
+ *
89
+ * @return {undefined}
90
+ */
91
+ function removeClass(element, classNames) {
92
+ var original = element.className.split(' ');
93
+ var toBeRemoved = classNames.split(' ');
94
+ for (var x = 0; x < toBeRemoved.length; x += 1) {
95
+ var index = original.indexOf(toBeRemoved[x]);
96
+ if (index > -1){
97
+ original.splice(index,1);
98
+ }
99
+ }
100
+ element.className = original.join(' ');
101
+ }
102
+
103
+ /**
104
+ * [Helper] Checks if the document is RTL
105
+ *
106
+ * @return {Boolean} True if the document is RTL, false otherwise.
107
+ */
108
+ function isRightToLeft(){
109
+ return window.getComputedStyle(document.body).direction === 'rtl';
110
+ }
111
+ /**
112
+ * [Helper] Get the document current scrollTop
113
+ *
114
+ * @return {Number} current document scrollTop value
115
+ */
116
+ function getScrollTop(){
117
+ return ((document.documentElement && document.documentElement.scrollTop) || document.body.scrollTop);
118
+ }
119
+
120
+ /**
121
+ * [Helper] Get the document current scrollLeft
122
+ *
123
+ * @return {Number} current document scrollLeft value
124
+ */
125
+ function getScrollLeft(){
126
+ return ((document.documentElement && document.documentElement.scrollLeft) || document.body.scrollLeft);
127
+ }
128
+
129
+ /**
130
+ * Helper: clear contents
131
+ *
132
+ */
133
+ function clearContents(element){
134
+ while (element.lastChild) {
135
+ element.removeChild(element.lastChild);
136
+ }
137
+ }
138
+ /**
139
+ * Extends a given prototype by merging properties from base into sub.
140
+ *
141
+ * @sub {Object} sub The prototype being overwritten.
142
+ * @base {Object} base The prototype being written.
143
+ *
144
+ * @return {Object} The extended prototype.
145
+ */
146
+ function copy(src) {
147
+ if(null === src){
148
+ return src;
149
+ }
150
+ var cpy;
151
+ if(Array.isArray(src)){
152
+ cpy = [];
153
+ for(var x=0;x<src.length;x+=1){
154
+ cpy.push(copy(src[x]));
155
+ }
156
+ return cpy;
157
+ }
158
+
159
+ if(src instanceof Date){
160
+ return new Date(src.getTime());
161
+ }
162
+
163
+ if(src instanceof RegExp){
164
+ cpy = new RegExp(src.source);
165
+ cpy.global = src.global;
166
+ cpy.ignoreCase = src.ignoreCase;
167
+ cpy.multiline = src.multiline;
168
+ cpy.lastIndex = src.lastIndex;
169
+ return cpy;
170
+ }
171
+
172
+ if(typeof src === 'object'){
173
+ cpy = {};
174
+ // copy dialog pototype over definition.
175
+ for (var prop in src) {
176
+ if (src.hasOwnProperty(prop)) {
177
+ cpy[prop] = copy(src[prop]);
178
+ }
179
+ }
180
+ return cpy;
181
+ }
182
+ return src;
183
+ }
184
+ /**
185
+ * Helper: destruct the dialog
186
+ *
187
+ */
188
+ function destruct(instance, initialize){
189
+ //delete the dom and it's references.
190
+ var root = instance.elements.root;
191
+ root.parentNode.removeChild(root);
192
+ delete instance.elements;
193
+ //copy back initial settings.
194
+ instance.settings = copy(instance.__settings);
195
+ //re-reference init function.
196
+ instance.__init = initialize;
197
+ //delete __internal variable to allow re-initialization.
198
+ delete instance.__internal;
199
+ }
200
+
201
+ /**
202
+ * Use a closure to return proper event listener method. Try to use
203
+ * `addEventListener` by default but fallback to `attachEvent` for
204
+ * unsupported browser. The closure simply ensures that the test doesn't
205
+ * happen every time the method is called.
206
+ *
207
+ * @param {Node} el Node element
208
+ * @param {String} event Event type
209
+ * @param {Function} fn Callback of event
210
+ * @return {Function}
211
+ */
212
+ var on = (function () {
213
+ if (document.addEventListener) {
214
+ return function (el, event, fn, useCapture) {
215
+ el.addEventListener(event, fn, useCapture === true);
216
+ };
217
+ } else if (document.attachEvent) {
218
+ return function (el, event, fn) {
219
+ el.attachEvent('on' + event, fn);
220
+ };
221
+ }
222
+ }());
223
+
224
+ /**
225
+ * Use a closure to return proper event listener method. Try to use
226
+ * `removeEventListener` by default but fallback to `detachEvent` for
227
+ * unsupported browser. The closure simply ensures that the test doesn't
228
+ * happen every time the method is called.
229
+ *
230
+ * @param {Node} el Node element
231
+ * @param {String} event Event type
232
+ * @param {Function} fn Callback of event
233
+ * @return {Function}
234
+ */
235
+ var off = (function () {
236
+ if (document.removeEventListener) {
237
+ return function (el, event, fn, useCapture) {
238
+ el.removeEventListener(event, fn, useCapture === true);
239
+ };
240
+ } else if (document.detachEvent) {
241
+ return function (el, event, fn) {
242
+ el.detachEvent('on' + event, fn);
243
+ };
244
+ }
245
+ }());
246
+
247
+ /**
248
+ * Prevent default event from firing
249
+ *
250
+ * @param {Event} event Event object
251
+ * @return {undefined}
252
+
253
+ function prevent ( event ) {
254
+ if ( event ) {
255
+ if ( event.preventDefault ) {
256
+ event.preventDefault();
257
+ } else {
258
+ event.returnValue = false;
259
+ }
260
+ }
261
+ }
262
+ */
263
+ var transition = (function () {
264
+ var t, type;
265
+ var supported = false;
266
+ var transitions = {
267
+ 'animation' : 'animationend',
268
+ 'OAnimation' : 'oAnimationEnd oanimationend',
269
+ 'msAnimation' : 'MSAnimationEnd',
270
+ 'MozAnimation' : 'animationend',
271
+ 'WebkitAnimation' : 'webkitAnimationEnd'
272
+ };
273
+
274
+ for (t in transitions) {
275
+ if (document.documentElement.style[t] !== undefined) {
276
+ type = transitions[t];
277
+ supported = true;
278
+ break;
279
+ }
280
+ }
281
+
282
+ return {
283
+ type: type,
284
+ supported: supported
285
+ };
286
+ }());
287
+
288
+ /**
289
+ * Creates event handler delegate that sends the instance as last argument.
290
+ *
291
+ * @return {Function} a function wrapper which sends the instance as last argument.
292
+ */
293
+ function delegate(context, method) {
294
+ return function () {
295
+ if (arguments.length > 0) {
296
+ var args = [];
297
+ for (var x = 0; x < arguments.length; x += 1) {
298
+ args.push(arguments[x]);
299
+ }
300
+ args.push(context);
301
+ return method.apply(context, args);
302
+ }
303
+ return method.apply(context, [null, context]);
304
+ };
305
+ }
306
+ /**
307
+ * Helper for creating a dialog close event.
308
+ *
309
+ * @return {object}
310
+ */
311
+ function createCloseEvent(index, button) {
312
+ return {
313
+ index: index,
314
+ button: button,
315
+ cancel: false
316
+ };
317
+ }
318
+ /**
319
+ * Helper for dispatching events.
320
+ *
321
+ * @param {string} evenType The type of the event to disptach.
322
+ * @param {object} instance The dialog instance disptaching the event.
323
+ *
324
+ * @return {any} The result of the invoked function.
325
+ */
326
+ function dispatchEvent(eventType, instance) {
327
+ if ( typeof instance.get(eventType) === 'function' ) {
328
+ return instance.get(eventType).call(instance);
329
+ }
330
+ }
331
+
332
+
333
+ /**
334
+ * Super class for all dialogs
335
+ *
336
+ * @return {Object} base dialog prototype
337
+ */
338
+ var dialog = (function () {
339
+ var //holds the list of used keys.
340
+ usedKeys = [],
341
+ //dummy variable, used to trigger dom reflow.
342
+ reflow = null,
343
+ //condition for detecting safari
344
+ isSafari = window.navigator.userAgent.indexOf('Safari') > -1 && window.navigator.userAgent.indexOf('Chrome') < 0,
345
+ //dialog building blocks
346
+ templates = {
347
+ dimmer:'<div class="ajs-dimmer"></div>',
348
+ /*tab index required to fire click event before body focus*/
349
+ modal: '<div class="ajs-modal" tabindex="0"></div>',
350
+ dialog: '<div class="ajs-dialog" tabindex="0"></div>',
351
+ reset: '<button class="ajs-reset"></button>',
352
+ commands: '<div class="ajs-commands"><button class="ajs-pin"></button><button class="ajs-maximize"></button><button class="ajs-close"></button></div>',
353
+ header: '<div class="ajs-header"></div>',
354
+ body: '<div class="ajs-body"></div>',
355
+ content: '<div class="ajs-content"></div>',
356
+ footer: '<div class="ajs-footer"></div>',
357
+ buttons: { primary: '<div class="ajs-primary ajs-buttons"></div>', auxiliary: '<div class="ajs-auxiliary ajs-buttons"></div>' },
358
+ button: '<button class="ajs-button"></button>',
359
+ resizeHandle: '<div class="ajs-handle"></div>',
360
+ },
361
+ //common class names
362
+ classes = {
363
+ animationIn: 'ajs-in',
364
+ animationOut: 'ajs-out',
365
+ base: 'alertify',
366
+ basic:'ajs-basic',
367
+ capture: 'ajs-capture',
368
+ closable:'ajs-closable',
369
+ fixed: 'ajs-fixed',
370
+ frameless:'ajs-frameless',
371
+ hidden: 'ajs-hidden',
372
+ maximize: 'ajs-maximize',
373
+ maximized: 'ajs-maximized',
374
+ maximizable:'ajs-maximizable',
375
+ modeless: 'ajs-modeless',
376
+ movable: 'ajs-movable',
377
+ noSelection: 'ajs-no-selection',
378
+ noOverflow: 'ajs-no-overflow',
379
+ noPadding:'ajs-no-padding',
380
+ pin:'ajs-pin',
381
+ pinnable:'ajs-pinnable',
382
+ prefix: 'ajs-',
383
+ resizable: 'ajs-resizable',
384
+ restore: 'ajs-restore',
385
+ shake:'ajs-shake',
386
+ unpinned:'ajs-unpinned',
387
+ };
388
+
389
+ /**
390
+ * Helper: initializes the dialog instance
391
+ *
392
+ * @return {Number} The total count of currently open modals.
393
+ */
394
+ function initialize(instance){
395
+
396
+ if(!instance.__internal){
397
+
398
+ //no need to expose init after this.
399
+ delete instance.__init;
400
+
401
+ //keep a copy of initial dialog settings
402
+ if(!instance.__settings){
403
+ instance.__settings = copy(instance.settings);
404
+ }
405
+ //in case the script was included before body.
406
+ //after first dialog gets initialized, it won't be null anymore!
407
+ if(null === reflow){
408
+ // set tabindex attribute on body element this allows script to give it
409
+ // focus after the dialog is closed
410
+ document.body.setAttribute( 'tabindex', '0' );
411
+ }
412
+
413
+ //get dialog buttons/focus setup
414
+ var setup;
415
+ if(typeof instance.setup === 'function'){
416
+ setup = instance.setup();
417
+ setup.options = setup.options || {};
418
+ setup.focus = setup.focus || {};
419
+ }else{
420
+ setup = {
421
+ buttons:[],
422
+ focus:{
423
+ element:null,
424
+ select:false
425
+ },
426
+ options:{
427
+ }
428
+ };
429
+ }
430
+
431
+ //initialize hooks object.
432
+ if(typeof instance.hooks !== 'object'){
433
+ instance.hooks = {};
434
+ }
435
+
436
+ //copy buttons defintion
437
+ var buttonsDefinition = [];
438
+ if(Array.isArray(setup.buttons)){
439
+ for(var b=0;b<setup.buttons.length;b+=1){
440
+ var ref = setup.buttons[b],
441
+ cpy = {};
442
+ for (var i in ref) {
443
+ if (ref.hasOwnProperty(i)) {
444
+ cpy[i] = ref[i];
445
+ }
446
+ }
447
+ buttonsDefinition.push(cpy);
448
+ }
449
+ }
450
+
451
+ var internal = instance.__internal = {
452
+ /**
453
+ * Flag holding the open state of the dialog
454
+ *
455
+ * @type {Boolean}
456
+ */
457
+ isOpen:false,
458
+ /**
459
+ * Active element is the element that will receive focus after
460
+ * closing the dialog. It defaults as the body tag, but gets updated
461
+ * to the last focused element before the dialog was opened.
462
+ *
463
+ * @type {Node}
464
+ */
465
+ activeElement:document.body,
466
+ timerIn:undefined,
467
+ timerOut:undefined,
468
+ buttons: buttonsDefinition,
469
+ focus: setup.focus,
470
+ options: {
471
+ title: undefined,
472
+ modal: undefined,
473
+ basic:undefined,
474
+ frameless:undefined,
475
+ pinned: undefined,
476
+ movable: undefined,
477
+ moveBounded:undefined,
478
+ resizable: undefined,
479
+ autoReset: undefined,
480
+ closable: undefined,
481
+ closableByDimmer: undefined,
482
+ maximizable: undefined,
483
+ startMaximized: undefined,
484
+ pinnable: undefined,
485
+ transition: undefined,
486
+ padding:undefined,
487
+ overflow:undefined,
488
+ onshow:undefined,
489
+ onclosing:undefined,
490
+ onclose:undefined,
491
+ onfocus:undefined,
492
+ onmove:undefined,
493
+ onmoved:undefined,
494
+ onresize:undefined,
495
+ onresized:undefined,
496
+ onmaximize:undefined,
497
+ onmaximized:undefined,
498
+ onrestore:undefined,
499
+ onrestored:undefined
500
+ },
501
+ resetHandler:undefined,
502
+ beginMoveHandler:undefined,
503
+ beginResizeHandler:undefined,
504
+ bringToFrontHandler:undefined,
505
+ modalClickHandler:undefined,
506
+ buttonsClickHandler:undefined,
507
+ commandsClickHandler:undefined,
508
+ transitionInHandler:undefined,
509
+ transitionOutHandler:undefined,
510
+ destroy:undefined
511
+ };
512
+
513
+ var elements = {};
514
+ //root node
515
+ elements.root = document.createElement('div');
516
+
517
+ elements.root.className = classes.base + ' ' + classes.hidden + ' ';
518
+
519
+ elements.root.innerHTML = templates.dimmer + templates.modal;
520
+
521
+ //dimmer
522
+ elements.dimmer = elements.root.firstChild;
523
+
524
+ //dialog
525
+ elements.modal = elements.root.lastChild;
526
+ elements.modal.innerHTML = templates.dialog;
527
+ elements.dialog = elements.modal.firstChild;
528
+ elements.dialog.innerHTML = templates.reset + templates.commands + templates.header + templates.body + templates.footer + templates.resizeHandle + templates.reset;
529
+
530
+ //reset links
531
+ elements.reset = [];
532
+ elements.reset.push(elements.dialog.firstChild);
533
+ elements.reset.push(elements.dialog.lastChild);
534
+
535
+ //commands
536
+ elements.commands = {};
537
+ elements.commands.container = elements.reset[0].nextSibling;
538
+ elements.commands.pin = elements.commands.container.firstChild;
539
+ elements.commands.maximize = elements.commands.pin.nextSibling;
540
+ elements.commands.close = elements.commands.maximize.nextSibling;
541
+
542
+ //header
543
+ elements.header = elements.commands.container.nextSibling;
544
+
545
+ //body
546
+ elements.body = elements.header.nextSibling;
547
+ elements.body.innerHTML = templates.content;
548
+ elements.content = elements.body.firstChild;
549
+
550
+ //footer
551
+ elements.footer = elements.body.nextSibling;
552
+ elements.footer.innerHTML = templates.buttons.auxiliary + templates.buttons.primary;
553
+
554
+ //resize handle
555
+ elements.resizeHandle = elements.footer.nextSibling;
556
+
557
+ //buttons
558
+ elements.buttons = {};
559
+ elements.buttons.auxiliary = elements.footer.firstChild;
560
+ elements.buttons.primary = elements.buttons.auxiliary.nextSibling;
561
+ elements.buttons.primary.innerHTML = templates.button;
562
+ elements.buttonTemplate = elements.buttons.primary.firstChild;
563
+ //remove button template
564
+ elements.buttons.primary.removeChild(elements.buttonTemplate);
565
+
566
+ for(var x=0; x < instance.__internal.buttons.length; x+=1) {
567
+ var button = instance.__internal.buttons[x];
568
+
569
+ // add to the list of used keys.
570
+ if(usedKeys.indexOf(button.key) < 0){
571
+ usedKeys.push(button.key);
572
+ }
573
+
574
+ button.element = elements.buttonTemplate.cloneNode();
575
+ button.element.innerHTML = button.text;
576
+ if(typeof button.className === 'string' && button.className !== ''){
577
+ addClass(button.element, button.className);
578
+ }
579
+ for(var key in button.attrs){
580
+ if(key !== 'className' && button.attrs.hasOwnProperty(key)){
581
+ button.element.setAttribute(key, button.attrs[key]);
582
+ }
583
+ }
584
+ if(button.scope === 'auxiliary'){
585
+ elements.buttons.auxiliary.appendChild(button.element);
586
+ }else{
587
+ elements.buttons.primary.appendChild(button.element);
588
+ }
589
+ }
590
+ //make elements pubic
591
+ instance.elements = elements;
592
+
593
+ //save event handlers delegates
594
+ internal.resetHandler = delegate(instance, onReset);
595
+ internal.beginMoveHandler = delegate(instance, beginMove);
596
+ internal.beginResizeHandler = delegate(instance, beginResize);
597
+ internal.bringToFrontHandler = delegate(instance, bringToFront);
598
+ internal.modalClickHandler = delegate(instance, modalClickHandler);
599
+ internal.buttonsClickHandler = delegate(instance, buttonsClickHandler);
600
+ internal.commandsClickHandler = delegate(instance, commandsClickHandler);
601
+ internal.transitionInHandler = delegate(instance, handleTransitionInEvent);
602
+ internal.transitionOutHandler = delegate(instance, handleTransitionOutEvent);
603
+
604
+ //settings
605
+ for(var opKey in internal.options){
606
+ if(setup.options[opKey] !== undefined){
607
+ // if found in user options
608
+ instance.set(opKey, setup.options[opKey]);
609
+ }else if(alertify.defaults.hasOwnProperty(opKey)) {
610
+ // else if found in defaults options
611
+ instance.set(opKey, alertify.defaults[opKey]);
612
+ }else if(opKey === 'title' ) {
613
+ // else if title key, use alertify.defaults.glossary
614
+ instance.set(opKey, alertify.defaults.glossary[opKey]);
615
+ }
616
+ }
617
+
618
+ // allow dom customization
619
+ if(typeof instance.build === 'function'){
620
+ instance.build();
621
+ }
622
+ }
623
+
624
+ //add to the end of the DOM tree.
625
+ document.body.appendChild(instance.elements.root);
626
+ }
627
+
628
+ /**
629
+ * Helper: maintains scroll position
630
+ *
631
+ */
632
+ var scrollX, scrollY;
633
+ function saveScrollPosition(){
634
+ scrollX = getScrollLeft();
635
+ scrollY = getScrollTop();
636
+ }
637
+ function restoreScrollPosition(){
638
+ window.scrollTo(scrollX, scrollY);
639
+ }
640
+
641
+ /**
642
+ * Helper: adds/removes no-overflow class from body
643
+ *
644
+ */
645
+ function ensureNoOverflow(){
646
+ var requiresNoOverflow = 0;
647
+ for(var x=0;x<openDialogs.length;x+=1){
648
+ var instance = openDialogs[x];
649
+ if(instance.isModal() || instance.isMaximized()){
650
+ requiresNoOverflow+=1;
651
+ }
652
+ }
653
+ if(requiresNoOverflow === 0 && document.body.className.indexOf(classes.noOverflow) >= 0){
654
+ //last open modal or last maximized one
655
+ removeClass(document.body, classes.noOverflow);
656
+ preventBodyShift(false);
657
+ }else if(requiresNoOverflow > 0 && document.body.className.indexOf(classes.noOverflow) < 0){
658
+ //first open modal or first maximized one
659
+ preventBodyShift(true);
660
+ addClass(document.body, classes.noOverflow);
661
+ }
662
+ }
663
+ var top = '', topScroll = 0;
664
+ /**
665
+ * Helper: prevents body shift.
666
+ *
667
+ */
668
+ function preventBodyShift(add){
669
+ if(alertify.defaults.preventBodyShift && document.documentElement.scrollHeight > document.documentElement.clientHeight){
670
+ if(add ){//&& openDialogs[openDialogs.length-1].elements.dialog.clientHeight <= document.documentElement.clientHeight){
671
+ topScroll = scrollY;
672
+ top = window.getComputedStyle(document.body).top;
673
+ addClass(document.body, classes.fixed);
674
+ document.body.style.top = -scrollY + 'px';
675
+ } else {
676
+ scrollY = topScroll;
677
+ document.body.style.top = top;
678
+ removeClass(document.body, classes.fixed);
679
+ restoreScrollPosition();
680
+ }
681
+ }
682
+ }
683
+
684
+ /**
685
+ * Sets the name of the transition used to show/hide the dialog
686
+ *
687
+ * @param {Object} instance The dilog instance.
688
+ *
689
+ */
690
+ function updateTransition(instance, value, oldValue){
691
+ if(typeof oldValue === 'string'){
692
+ removeClass(instance.elements.root,classes.prefix + oldValue);
693
+ }
694
+ addClass(instance.elements.root, classes.prefix + value);
695
+ reflow = instance.elements.root.offsetWidth;
696
+ }
697
+
698
+ /**
699
+ * Toggles the dialog display mode
700
+ *
701
+ * @param {Object} instance The dilog instance.
702
+ *
703
+ * @return {undefined}
704
+ */
705
+ function updateDisplayMode(instance){
706
+ if(instance.get('modal')){
707
+
708
+ //make modal
709
+ removeClass(instance.elements.root, classes.modeless);
710
+
711
+ //only if open
712
+ if(instance.isOpen()){
713
+ unbindModelessEvents(instance);
714
+
715
+ //in case a pinned modless dialog was made modal while open.
716
+ updateAbsPositionFix(instance);
717
+
718
+ ensureNoOverflow();
719
+ }
720
+ }else{
721
+ //make modelss
722
+ addClass(instance.elements.root, classes.modeless);
723
+
724
+ //only if open
725
+ if(instance.isOpen()){
726
+ bindModelessEvents(instance);
727
+
728
+ //in case pin/unpin was called while a modal is open
729
+ updateAbsPositionFix(instance);
730
+
731
+ ensureNoOverflow();
732
+ }
733
+ }
734
+ }
735
+
736
+ /**
737
+ * Toggles the dialog basic view mode
738
+ *
739
+ * @param {Object} instance The dilog instance.
740
+ *
741
+ * @return {undefined}
742
+ */
743
+ function updateBasicMode(instance){
744
+ if (instance.get('basic')) {
745
+ // add class
746
+ addClass(instance.elements.root, classes.basic);
747
+ } else {
748
+ // remove class
749
+ removeClass(instance.elements.root, classes.basic);
750
+ }
751
+ }
752
+
753
+ /**
754
+ * Toggles the dialog frameless view mode
755
+ *
756
+ * @param {Object} instance The dilog instance.
757
+ *
758
+ * @return {undefined}
759
+ */
760
+ function updateFramelessMode(instance){
761
+ if (instance.get('frameless')) {
762
+ // add class
763
+ addClass(instance.elements.root, classes.frameless);
764
+ } else {
765
+ // remove class
766
+ removeClass(instance.elements.root, classes.frameless);
767
+ }
768
+ }
769
+
770
+ /**
771
+ * Helper: Brings the modeless dialog to front, attached to modeless dialogs.
772
+ *
773
+ * @param {Event} event Focus event
774
+ * @param {Object} instance The dilog instance.
775
+ *
776
+ * @return {undefined}
777
+ */
778
+ function bringToFront(event, instance){
779
+
780
+ // Do not bring to front if preceeded by an open modal
781
+ var index = openDialogs.indexOf(instance);
782
+ for(var x=index+1;x<openDialogs.length;x+=1){
783
+ if(openDialogs[x].isModal()){
784
+ return;
785
+ }
786
+ }
787
+
788
+ // Bring to front by making it the last child.
789
+ if(document.body.lastChild !== instance.elements.root){
790
+ document.body.appendChild(instance.elements.root);
791
+ //also make sure its at the end of the list
792
+ openDialogs.splice(openDialogs.indexOf(instance),1);
793
+ openDialogs.push(instance);
794
+ setFocus(instance);
795
+ }
796
+
797
+ return false;
798
+ }
799
+
800
+ /**
801
+ * Helper: reflects dialogs options updates
802
+ *
803
+ * @param {Object} instance The dilog instance.
804
+ * @param {String} option The updated option name.
805
+ *
806
+ * @return {undefined}
807
+ */
808
+ function optionUpdated(instance, option, oldValue, newValue){
809
+ switch(option){
810
+ case 'title':
811
+ instance.setHeader(newValue);
812
+ break;
813
+ case 'modal':
814
+ updateDisplayMode(instance);
815
+ break;
816
+ case 'basic':
817
+ updateBasicMode(instance);
818
+ break;
819
+ case 'frameless':
820
+ updateFramelessMode(instance);
821
+ break;
822
+ case 'pinned':
823
+ updatePinned(instance);
824
+ break;
825
+ case 'closable':
826
+ updateClosable(instance);
827
+ break;
828
+ case 'maximizable':
829
+ updateMaximizable(instance);
830
+ break;
831
+ case 'pinnable':
832
+ updatePinnable(instance);
833
+ break;
834
+ case 'movable':
835
+ updateMovable(instance);
836
+ break;
837
+ case 'resizable':
838
+ updateResizable(instance);
839
+ break;
840
+ case 'transition':
841
+ updateTransition(instance,newValue, oldValue);
842
+ break;
843
+ case 'padding':
844
+ if(newValue){
845
+ removeClass(instance.elements.root, classes.noPadding);
846
+ }else if(instance.elements.root.className.indexOf(classes.noPadding) < 0){
847
+ addClass(instance.elements.root, classes.noPadding);
848
+ }
849
+ break;
850
+ case 'overflow':
851
+ if(newValue){
852
+ removeClass(instance.elements.root, classes.noOverflow);
853
+ }else if(instance.elements.root.className.indexOf(classes.noOverflow) < 0){
854
+ addClass(instance.elements.root, classes.noOverflow);
855
+ }
856
+ break;
857
+ case 'transition':
858
+ updateTransition(instance,newValue, oldValue);
859
+ break;
860
+ }
861
+
862
+ // internal on option updated event
863
+ if(typeof instance.hooks.onupdate === 'function'){
864
+ instance.hooks.onupdate.call(instance, option, oldValue, newValue);
865
+ }
866
+ }
867
+
868
+ /**
869
+ * Helper: reflects dialogs options updates
870
+ *
871
+ * @param {Object} instance The dilog instance.
872
+ * @param {Object} obj The object to set/get a value on/from.
873
+ * @param {Function} callback The callback function to call if the key was found.
874
+ * @param {String|Object} key A string specifying a propery name or a collection of key value pairs.
875
+ * @param {Object} value Optional, the value associated with the key (in case it was a string).
876
+ * @param {String} option The updated option name.
877
+ *
878
+ * @return {Object} result object
879
+ * The result objects has an 'op' property, indicating of this is a SET or GET operation.
880
+ * GET:
881
+ * - found: a flag indicating if the key was found or not.
882
+ * - value: the property value.
883
+ * SET:
884
+ * - items: a list of key value pairs of the properties being set.
885
+ * each contains:
886
+ * - found: a flag indicating if the key was found or not.
887
+ * - key: the property key.
888
+ * - value: the property value.
889
+ */
890
+ function update(instance, obj, callback, key, value){
891
+ var result = {op:undefined, items: [] };
892
+ if(typeof value === 'undefined' && typeof key === 'string') {
893
+ //get
894
+ result.op = 'get';
895
+ if(obj.hasOwnProperty(key)){
896
+ result.found = true;
897
+ result.value = obj[key];
898
+ }else{
899
+ result.found = false;
900
+ result.value = undefined;
901
+ }
902
+ }
903
+ else
904
+ {
905
+ var old;
906
+ //set
907
+ result.op = 'set';
908
+ if(typeof key === 'object'){
909
+ //set multiple
910
+ var args = key;
911
+ for (var prop in args) {
912
+ if (obj.hasOwnProperty(prop)) {
913
+ if(obj[prop] !== args[prop]){
914
+ old = obj[prop];
915
+ obj[prop] = args[prop];
916
+ callback.call(instance,prop, old, args[prop]);
917
+ }
918
+ result.items.push({ 'key': prop, 'value': args[prop], 'found':true});
919
+ }else{
920
+ result.items.push({ 'key': prop, 'value': args[prop], 'found':false});
921
+ }
922
+ }
923
+ } else if (typeof key === 'string'){
924
+ //set single
925
+ if (obj.hasOwnProperty(key)) {
926
+ if(obj[key] !== value){
927
+ old = obj[key];
928
+ obj[key] = value;
929
+ callback.call(instance,key, old, value);
930
+ }
931
+ result.items.push({'key': key, 'value': value , 'found':true});
932
+
933
+ }else{
934
+ result.items.push({'key': key, 'value': value , 'found':false});
935
+ }
936
+ } else {
937
+ //invalid params
938
+ throw new Error('args must be a string or object');
939
+ }
940
+ }
941
+ return result;
942
+ }
943
+
944
+
945
+ /**
946
+ * Triggers a close event.
947
+ *
948
+ * @param {Object} instance The dilog instance.
949
+ *
950
+ * @return {undefined}
951
+ */
952
+ function triggerClose(instance) {
953
+ var found;
954
+ triggerCallback(instance, function (button) {
955
+ return found = (button.invokeOnClose === true);
956
+ });
957
+ //none of the buttons registered as onclose callback
958
+ //close the dialog
959
+ if (!found && instance.isOpen()) {
960
+ instance.close();
961
+ }
962
+ }
963
+
964
+ /**
965
+ * Dialogs commands event handler, attached to the dialog commands element.
966
+ *
967
+ * @param {Event} event DOM event object.
968
+ * @param {Object} instance The dilog instance.
969
+ *
970
+ * @return {undefined}
971
+ */
972
+ function commandsClickHandler(event, instance) {
973
+ var target = event.srcElement || event.target;
974
+ switch (target) {
975
+ case instance.elements.commands.pin:
976
+ if (!instance.isPinned()) {
977
+ pin(instance);
978
+ } else {
979
+ unpin(instance);
980
+ }
981
+ break;
982
+ case instance.elements.commands.maximize:
983
+ if (!instance.isMaximized()) {
984
+ maximize(instance);
985
+ } else {
986
+ restore(instance);
987
+ }
988
+ break;
989
+ case instance.elements.commands.close:
990
+ triggerClose(instance);
991
+ break;
992
+ }
993
+ return false;
994
+ }
995
+
996
+ /**
997
+ * Helper: pins the modeless dialog.
998
+ *
999
+ * @param {Object} instance The dialog instance.
1000
+ *
1001
+ * @return {undefined}
1002
+ */
1003
+ function pin(instance) {
1004
+ //pin the dialog
1005
+ instance.set('pinned', true);
1006
+ }
1007
+
1008
+ /**
1009
+ * Helper: unpins the modeless dialog.
1010
+ *
1011
+ * @param {Object} instance The dilog instance.
1012
+ *
1013
+ * @return {undefined}
1014
+ */
1015
+ function unpin(instance) {
1016
+ //unpin the dialog
1017
+ instance.set('pinned', false);
1018
+ }
1019
+
1020
+
1021
+ /**
1022
+ * Helper: enlarges the dialog to fill the entire screen.
1023
+ *
1024
+ * @param {Object} instance The dilog instance.
1025
+ *
1026
+ * @return {undefined}
1027
+ */
1028
+ function maximize(instance) {
1029
+ // allow custom `onmaximize` method
1030
+ dispatchEvent('onmaximize', instance);
1031
+ //maximize the dialog
1032
+ addClass(instance.elements.root, classes.maximized);
1033
+ if (instance.isOpen()) {
1034
+ ensureNoOverflow();
1035
+ }
1036
+ // allow custom `onmaximized` method
1037
+ dispatchEvent('onmaximized', instance);
1038
+ }
1039
+
1040
+ /**
1041
+ * Helper: returns the dialog to its former size.
1042
+ *
1043
+ * @param {Object} instance The dilog instance.
1044
+ *
1045
+ * @return {undefined}
1046
+ */
1047
+ function restore(instance) {
1048
+ // allow custom `onrestore` method
1049
+ dispatchEvent('onrestore', instance);
1050
+ //maximize the dialog
1051
+ removeClass(instance.elements.root, classes.maximized);
1052
+ if (instance.isOpen()) {
1053
+ ensureNoOverflow();
1054
+ }
1055
+ // allow custom `onrestored` method
1056
+ dispatchEvent('onrestored', instance);
1057
+ }
1058
+
1059
+ /**
1060
+ * Show or hide the maximize box.
1061
+ *
1062
+ * @param {Object} instance The dilog instance.
1063
+ * @param {Boolean} on True to add the behavior, removes it otherwise.
1064
+ *
1065
+ * @return {undefined}
1066
+ */
1067
+ function updatePinnable(instance) {
1068
+ if (instance.get('pinnable')) {
1069
+ // add class
1070
+ addClass(instance.elements.root, classes.pinnable);
1071
+ } else {
1072
+ // remove class
1073
+ removeClass(instance.elements.root, classes.pinnable);
1074
+ }
1075
+ }
1076
+
1077
+ /**
1078
+ * Helper: Fixes the absolutly positioned modal div position.
1079
+ *
1080
+ * @param {Object} instance The dialog instance.
1081
+ *
1082
+ * @return {undefined}
1083
+ */
1084
+ function addAbsPositionFix(instance) {
1085
+ var scrollLeft = getScrollLeft();
1086
+ instance.elements.modal.style.marginTop = getScrollTop() + 'px';
1087
+ instance.elements.modal.style.marginLeft = scrollLeft + 'px';
1088
+ instance.elements.modal.style.marginRight = (-scrollLeft) + 'px';
1089
+ }
1090
+
1091
+ /**
1092
+ * Helper: Removes the absolutly positioned modal div position fix.
1093
+ *
1094
+ * @param {Object} instance The dialog instance.
1095
+ *
1096
+ * @return {undefined}
1097
+ */
1098
+ function removeAbsPositionFix(instance) {
1099
+ var marginTop = parseInt(instance.elements.modal.style.marginTop, 10);
1100
+ var marginLeft = parseInt(instance.elements.modal.style.marginLeft, 10);
1101
+ instance.elements.modal.style.marginTop = '';
1102
+ instance.elements.modal.style.marginLeft = '';
1103
+ instance.elements.modal.style.marginRight = '';
1104
+
1105
+ if (instance.isOpen()) {
1106
+ var top = 0,
1107
+ left = 0
1108
+ ;
1109
+ if (instance.elements.dialog.style.top !== '') {
1110
+ top = parseInt(instance.elements.dialog.style.top, 10);
1111
+ }
1112
+ instance.elements.dialog.style.top = (top + (marginTop - getScrollTop())) + 'px';
1113
+
1114
+ if (instance.elements.dialog.style.left !== '') {
1115
+ left = parseInt(instance.elements.dialog.style.left, 10);
1116
+ }
1117
+ instance.elements.dialog.style.left = (left + (marginLeft - getScrollLeft())) + 'px';
1118
+ }
1119
+ }
1120
+ /**
1121
+ * Helper: Adds/Removes the absolutly positioned modal div position fix based on its pinned setting.
1122
+ *
1123
+ * @param {Object} instance The dialog instance.
1124
+ *
1125
+ * @return {undefined}
1126
+ */
1127
+ function updateAbsPositionFix(instance) {
1128
+ // if modeless and unpinned add fix
1129
+ if (!instance.get('modal') && !instance.get('pinned')) {
1130
+ addAbsPositionFix(instance);
1131
+ } else {
1132
+ removeAbsPositionFix(instance);
1133
+ }
1134
+ }
1135
+ /**
1136
+ * Toggles the dialog position lock | modeless only.
1137
+ *
1138
+ * @param {Object} instance The dilog instance.
1139
+ * @param {Boolean} on True to make it modal, false otherwise.
1140
+ *
1141
+ * @return {undefined}
1142
+ */
1143
+ function updatePinned(instance) {
1144
+ if (instance.get('pinned')) {
1145
+ removeClass(instance.elements.root, classes.unpinned);
1146
+ if (instance.isOpen()) {
1147
+ removeAbsPositionFix(instance);
1148
+ }
1149
+ } else {
1150
+ addClass(instance.elements.root, classes.unpinned);
1151
+ if (instance.isOpen() && !instance.isModal()) {
1152
+ addAbsPositionFix(instance);
1153
+ }
1154
+ }
1155
+ }
1156
+
1157
+ /**
1158
+ * Show or hide the maximize box.
1159
+ *
1160
+ * @param {Object} instance The dilog instance.
1161
+ * @param {Boolean} on True to add the behavior, removes it otherwise.
1162
+ *
1163
+ * @return {undefined}
1164
+ */
1165
+ function updateMaximizable(instance) {
1166
+ if (instance.get('maximizable')) {
1167
+ // add class
1168
+ addClass(instance.elements.root, classes.maximizable);
1169
+ } else {
1170
+ // remove class
1171
+ removeClass(instance.elements.root, classes.maximizable);
1172
+ }
1173
+ }
1174
+
1175
+ /**
1176
+ * Show or hide the close box.
1177
+ *
1178
+ * @param {Object} instance The dilog instance.
1179
+ * @param {Boolean} on True to add the behavior, removes it otherwise.
1180
+ *
1181
+ * @return {undefined}
1182
+ */
1183
+ function updateClosable(instance) {
1184
+ if (instance.get('closable')) {
1185
+ // add class
1186
+ addClass(instance.elements.root, classes.closable);
1187
+ bindClosableEvents(instance);
1188
+ } else {
1189
+ // remove class
1190
+ removeClass(instance.elements.root, classes.closable);
1191
+ unbindClosableEvents(instance);
1192
+ }
1193
+ }
1194
+
1195
+ // flag to cancel click event if already handled by end resize event (the mousedown, mousemove, mouseup sequence fires a click event.).
1196
+ var cancelClick = false;
1197
+
1198
+ /**
1199
+ * Helper: closes the modal dialog when clicking the modal
1200
+ *
1201
+ * @param {Event} event DOM event object.
1202
+ * @param {Object} instance The dilog instance.
1203
+ *
1204
+ * @return {undefined}
1205
+ */
1206
+ function modalClickHandler(event, instance) {
1207
+ var target = event.srcElement || event.target;
1208
+ if (!cancelClick && target === instance.elements.modal && instance.get('closableByDimmer') === true) {
1209
+ triggerClose(instance);
1210
+ }
1211
+ cancelClick = false;
1212
+ return false;
1213
+ }
1214
+
1215
+ // flag to cancel keyup event if already handled by click event (pressing Enter on a focusted button).
1216
+ var cancelKeyup = false;
1217
+ /**
1218
+ * Helper: triggers a button callback
1219
+ *
1220
+ * @param {Object} The dilog instance.
1221
+ * @param {Function} Callback to check which button triggered the event.
1222
+ *
1223
+ * @return {undefined}
1224
+ */
1225
+ function triggerCallback(instance, check) {
1226
+ for (var idx = 0; idx < instance.__internal.buttons.length; idx += 1) {
1227
+ var button = instance.__internal.buttons[idx];
1228
+ if (!button.element.disabled && check(button)) {
1229
+ var closeEvent = createCloseEvent(idx, button);
1230
+ if (typeof instance.callback === 'function') {
1231
+ instance.callback.apply(instance, [closeEvent]);
1232
+ }
1233
+ //close the dialog only if not canceled.
1234
+ if (closeEvent.cancel === false) {
1235
+ instance.close();
1236
+ }
1237
+ break;
1238
+ }
1239
+ }
1240
+ }
1241
+
1242
+ /**
1243
+ * Clicks event handler, attached to the dialog footer.
1244
+ *
1245
+ * @param {Event} DOM event object.
1246
+ * @param {Object} The dilog instance.
1247
+ *
1248
+ * @return {undefined}
1249
+ */
1250
+ function buttonsClickHandler(event, instance) {
1251
+ var target = event.srcElement || event.target;
1252
+ triggerCallback(instance, function (button) {
1253
+ // if this button caused the click, cancel keyup event
1254
+ return button.element === target && (cancelKeyup = true);
1255
+ });
1256
+ }
1257
+
1258
+ /**
1259
+ * Keyup event handler, attached to the document.body
1260
+ *
1261
+ * @param {Event} DOM event object.
1262
+ * @param {Object} The dilog instance.
1263
+ *
1264
+ * @return {undefined}
1265
+ */
1266
+ function keyupHandler(event) {
1267
+ //hitting enter while button has focus will trigger keyup too.
1268
+ //ignore if handled by clickHandler
1269
+ if (cancelKeyup) {
1270
+ cancelKeyup = false;
1271
+ return;
1272
+ }
1273
+ var instance = openDialogs[openDialogs.length - 1];
1274
+ var keyCode = event.keyCode;
1275
+ if (instance.__internal.buttons.length === 0 && keyCode === keys.ESC && instance.get('closable') === true) {
1276
+ triggerClose(instance);
1277
+ return false;
1278
+ }else if (usedKeys.indexOf(keyCode) > -1) {
1279
+ triggerCallback(instance, function (button) {
1280
+ return button.key === keyCode;
1281
+ });
1282
+ return false;
1283
+ }
1284
+ }
1285
+ /**
1286
+ * Keydown event handler, attached to the document.body
1287
+ *
1288
+ * @param {Event} DOM event object.
1289
+ * @param {Object} The dilog instance.
1290
+ *
1291
+ * @return {undefined}
1292
+ */
1293
+ function keydownHandler(event) {
1294
+ var instance = openDialogs[openDialogs.length - 1];
1295
+ var keyCode = event.keyCode;
1296
+ if (keyCode === keys.LEFT || keyCode === keys.RIGHT) {
1297
+ var buttons = instance.__internal.buttons;
1298
+ for (var x = 0; x < buttons.length; x += 1) {
1299
+ if (document.activeElement === buttons[x].element) {
1300
+ switch (keyCode) {
1301
+ case keys.LEFT:
1302
+ buttons[(x || buttons.length) - 1].element.focus();
1303
+ return;
1304
+ case keys.RIGHT:
1305
+ buttons[(x + 1) % buttons.length].element.focus();
1306
+ return;
1307
+ }
1308
+ }
1309
+ }
1310
+ }else if (keyCode < keys.F12 + 1 && keyCode > keys.F1 - 1 && usedKeys.indexOf(keyCode) > -1) {
1311
+ event.preventDefault();
1312
+ event.stopPropagation();
1313
+ triggerCallback(instance, function (button) {
1314
+ return button.key === keyCode;
1315
+ });
1316
+ return false;
1317
+ }
1318
+ }
1319
+
1320
+
1321
+ /**
1322
+ * Sets focus to proper dialog element
1323
+ *
1324
+ * @param {Object} instance The dilog instance.
1325
+ * @param {Node} [resetTarget=undefined] DOM element to reset focus to.
1326
+ *
1327
+ * @return {undefined}
1328
+ */
1329
+ function setFocus(instance, resetTarget) {
1330
+ // reset target has already been determined.
1331
+ if (resetTarget) {
1332
+ resetTarget.focus();
1333
+ } else {
1334
+ // current instance focus settings
1335
+ var focus = instance.__internal.focus;
1336
+ // the focus element.
1337
+ var element = focus.element;
1338
+
1339
+ switch (typeof focus.element) {
1340
+ // a number means a button index
1341
+ case 'number':
1342
+ if (instance.__internal.buttons.length > focus.element) {
1343
+ //in basic view, skip focusing the buttons.
1344
+ if (instance.get('basic') === true) {
1345
+ element = instance.elements.reset[0];
1346
+ } else {
1347
+ element = instance.__internal.buttons[focus.element].element;
1348
+ }
1349
+ }
1350
+ break;
1351
+ // a string means querySelector to select from dialog body contents.
1352
+ case 'string':
1353
+ element = instance.elements.body.querySelector(focus.element);
1354
+ break;
1355
+ // a function should return the focus element.
1356
+ case 'function':
1357
+ element = focus.element.call(instance);
1358
+ break;
1359
+ }
1360
+
1361
+ // if no focus element, default to first reset element.
1362
+ if ((typeof element === 'undefined' || element === null) && instance.__internal.buttons.length === 0) {
1363
+ element = instance.elements.reset[0];
1364
+ }
1365
+ // focus
1366
+ if (element && element.focus) {
1367
+ element.focus();
1368
+ // if selectable
1369
+ if (focus.select && element.select) {
1370
+ element.select();
1371
+ }
1372
+ }
1373
+ }
1374
+ }
1375
+
1376
+ /**
1377
+ * Focus event handler, attached to document.body and dialogs own reset links.
1378
+ * handles the focus for modal dialogs only.
1379
+ *
1380
+ * @param {Event} event DOM focus event object.
1381
+ * @param {Object} instance The dilog instance.
1382
+ *
1383
+ * @return {undefined}
1384
+ */
1385
+ function onReset(event, instance) {
1386
+
1387
+ // should work on last modal if triggered from document.body
1388
+ if (!instance) {
1389
+ for (var x = openDialogs.length - 1; x > -1; x -= 1) {
1390
+ if (openDialogs[x].isModal()) {
1391
+ instance = openDialogs[x];
1392
+ break;
1393
+ }
1394
+ }
1395
+ }
1396
+ // if modal
1397
+ if (instance && instance.isModal()) {
1398
+ // determine reset target to enable forward/backward tab cycle.
1399
+ var resetTarget, target = event.srcElement || event.target;
1400
+ var lastResetElement = target === instance.elements.reset[1] || (instance.__internal.buttons.length === 0 && target === document.body);
1401
+
1402
+ // if last reset link, then go to maximize or close
1403
+ if (lastResetElement) {
1404
+ if (instance.get('maximizable')) {
1405
+ resetTarget = instance.elements.commands.maximize;
1406
+ } else if (instance.get('closable')) {
1407
+ resetTarget = instance.elements.commands.close;
1408
+ }
1409
+ }
1410
+ // if no reset target found, try finding the best button
1411
+ if (resetTarget === undefined) {
1412
+ if (typeof instance.__internal.focus.element === 'number') {
1413
+ // button focus element, go to first available button
1414
+ if (target === instance.elements.reset[0]) {
1415
+ resetTarget = instance.elements.buttons.auxiliary.firstChild || instance.elements.buttons.primary.firstChild;
1416
+ } else if (lastResetElement) {
1417
+ //restart the cycle by going to first reset link
1418
+ resetTarget = instance.elements.reset[0];
1419
+ }
1420
+ } else {
1421
+ // will reach here when tapping backwards, so go to last child
1422
+ // The focus element SHOULD NOT be a button (logically!).
1423
+ if (target === instance.elements.reset[0]) {
1424
+ resetTarget = instance.elements.buttons.primary.lastChild || instance.elements.buttons.auxiliary.lastChild;
1425
+ }
1426
+ }
1427
+ }
1428
+ // focus
1429
+ setFocus(instance, resetTarget);
1430
+ }
1431
+ }
1432
+ /**
1433
+ * Transition in transitionend event handler.
1434
+ *
1435
+ * @param {Event} TransitionEnd event object.
1436
+ * @param {Object} The dilog instance.
1437
+ *
1438
+ * @return {undefined}
1439
+ */
1440
+ function handleTransitionInEvent(event, instance) {
1441
+ // clear the timer
1442
+ clearTimeout(instance.__internal.timerIn);
1443
+
1444
+ // once transition is complete, set focus
1445
+ setFocus(instance);
1446
+
1447
+ //restore scroll to prevent document jump
1448
+ restoreScrollPosition();
1449
+
1450
+ // allow handling key up after transition ended.
1451
+ cancelKeyup = false;
1452
+
1453
+ // allow custom `onfocus` method
1454
+ dispatchEvent('onfocus', instance);
1455
+
1456
+ // unbind the event
1457
+ off(instance.elements.dialog, transition.type, instance.__internal.transitionInHandler);
1458
+
1459
+ removeClass(instance.elements.root, classes.animationIn);
1460
+ }
1461
+
1462
+ /**
1463
+ * Transition out transitionend event handler.
1464
+ *
1465
+ * @param {Event} TransitionEnd event object.
1466
+ * @param {Object} The dilog instance.
1467
+ *
1468
+ * @return {undefined}
1469
+ */
1470
+ function handleTransitionOutEvent(event, instance) {
1471
+ // clear the timer
1472
+ clearTimeout(instance.__internal.timerOut);
1473
+ // unbind the event
1474
+ off(instance.elements.dialog, transition.type, instance.__internal.transitionOutHandler);
1475
+
1476
+ // reset move updates
1477
+ resetMove(instance);
1478
+ // reset resize updates
1479
+ resetResize(instance);
1480
+
1481
+ // restore if maximized
1482
+ if (instance.isMaximized() && !instance.get('startMaximized')) {
1483
+ restore(instance);
1484
+ }
1485
+
1486
+ // return focus to the last active element
1487
+ if (alertify.defaults.maintainFocus && instance.__internal.activeElement) {
1488
+ instance.__internal.activeElement.focus();
1489
+ instance.__internal.activeElement = null;
1490
+ }
1491
+
1492
+ //destory the instance
1493
+ if (typeof instance.__internal.destroy === 'function') {
1494
+ instance.__internal.destroy.apply(instance);
1495
+ }
1496
+ }
1497
+ /* Controls moving a dialog around */
1498
+ //holde the current moving instance
1499
+ var movable = null,
1500
+ //holds the current X offset when move starts
1501
+ offsetX = 0,
1502
+ //holds the current Y offset when move starts
1503
+ offsetY = 0,
1504
+ xProp = 'pageX',
1505
+ yProp = 'pageY',
1506
+ bounds = null,
1507
+ refreshTop = false,
1508
+ moveDelegate = null
1509
+ ;
1510
+
1511
+ /**
1512
+ * Helper: sets the element top/left coordinates
1513
+ *
1514
+ * @param {Event} event DOM event object.
1515
+ * @param {Node} element The element being moved.
1516
+ *
1517
+ * @return {undefined}
1518
+ */
1519
+ function moveElement(event, element) {
1520
+ var left = (event[xProp] - offsetX),
1521
+ top = (event[yProp] - offsetY);
1522
+
1523
+ if(refreshTop){
1524
+ top -= document.body.scrollTop;
1525
+ }
1526
+
1527
+ element.style.left = left + 'px';
1528
+ element.style.top = top + 'px';
1529
+
1530
+ }
1531
+ /**
1532
+ * Helper: sets the element top/left coordinates within screen bounds
1533
+ *
1534
+ * @param {Event} event DOM event object.
1535
+ * @param {Node} element The element being moved.
1536
+ *
1537
+ * @return {undefined}
1538
+ */
1539
+ function moveElementBounded(event, element) {
1540
+ var left = (event[xProp] - offsetX),
1541
+ top = (event[yProp] - offsetY);
1542
+
1543
+ if(refreshTop){
1544
+ top -= document.body.scrollTop;
1545
+ }
1546
+
1547
+ element.style.left = Math.min(bounds.maxLeft, Math.max(bounds.minLeft, left)) + 'px';
1548
+ if(refreshTop){
1549
+ element.style.top = Math.min(bounds.maxTop, Math.max(bounds.minTop, top)) + 'px';
1550
+ }else{
1551
+ element.style.top = Math.max(bounds.minTop, top) + 'px';
1552
+ }
1553
+ }
1554
+
1555
+
1556
+ /**
1557
+ * Triggers the start of a move event, attached to the header element mouse down event.
1558
+ * Adds no-selection class to the body, disabling selection while moving.
1559
+ *
1560
+ * @param {Event} event DOM event object.
1561
+ * @param {Object} instance The dilog instance.
1562
+ *
1563
+ * @return {Boolean} false
1564
+ */
1565
+ function beginMove(event, instance) {
1566
+ if (resizable === null && !instance.isMaximized() && instance.get('movable')) {
1567
+ var eventSrc, left=0, top=0;
1568
+ if (event.type === 'touchstart') {
1569
+ event.preventDefault();
1570
+ eventSrc = event.targetTouches[0];
1571
+ xProp = 'clientX';
1572
+ yProp = 'clientY';
1573
+ } else if (event.button === 0) {
1574
+ eventSrc = event;
1575
+ }
1576
+
1577
+ if (eventSrc) {
1578
+
1579
+ var element = instance.elements.dialog;
1580
+ addClass(element, classes.capture);
1581
+
1582
+ if (element.style.left) {
1583
+ left = parseInt(element.style.left, 10);
1584
+ }
1585
+
1586
+ if (element.style.top) {
1587
+ top = parseInt(element.style.top, 10);
1588
+ }
1589
+
1590
+ offsetX = eventSrc[xProp] - left;
1591
+ offsetY = eventSrc[yProp] - top;
1592
+
1593
+ if(instance.isModal()){
1594
+ offsetY += instance.elements.modal.scrollTop;
1595
+ }else if(instance.isPinned()){
1596
+ offsetY -= document.body.scrollTop;
1597
+ }
1598
+
1599
+ if(instance.get('moveBounded')){
1600
+ var current = element,
1601
+ offsetLeft = -left,
1602
+ offsetTop = -top;
1603
+
1604
+ //calc offset
1605
+ do {
1606
+ offsetLeft += current.offsetLeft;
1607
+ offsetTop += current.offsetTop;
1608
+ } while (current = current.offsetParent);
1609
+
1610
+ bounds = {
1611
+ maxLeft : offsetLeft,
1612
+ minLeft : -offsetLeft,
1613
+ maxTop : document.documentElement.clientHeight - element.clientHeight - offsetTop,
1614
+ minTop : -offsetTop
1615
+ };
1616
+ moveDelegate = moveElementBounded;
1617
+ }else{
1618
+ bounds = null;
1619
+ moveDelegate = moveElement;
1620
+ }
1621
+
1622
+ // allow custom `onmove` method
1623
+ dispatchEvent('onmove', instance);
1624
+
1625
+ refreshTop = !instance.isModal() && instance.isPinned();
1626
+ movable = instance;
1627
+ moveDelegate(eventSrc, element);
1628
+ addClass(document.body, classes.noSelection);
1629
+ return false;
1630
+ }
1631
+ }
1632
+ }
1633
+
1634
+ /**
1635
+ * The actual move handler, attached to document.body mousemove event.
1636
+ *
1637
+ * @param {Event} event DOM event object.
1638
+ *
1639
+ * @return {undefined}
1640
+ */
1641
+ function move(event) {
1642
+ if (movable) {
1643
+ var eventSrc;
1644
+ if (event.type === 'touchmove') {
1645
+ event.preventDefault();
1646
+ eventSrc = event.targetTouches[0];
1647
+ } else if (event.button === 0) {
1648
+ eventSrc = event;
1649
+ }
1650
+ if (eventSrc) {
1651
+ moveDelegate(eventSrc, movable.elements.dialog);
1652
+ }
1653
+ }
1654
+ }
1655
+
1656
+ /**
1657
+ * Triggers the end of a move event, attached to document.body mouseup event.
1658
+ * Removes no-selection class from document.body, allowing selection.
1659
+ *
1660
+ * @return {undefined}
1661
+ */
1662
+ function endMove() {
1663
+ if (movable) {
1664
+ var instance = movable;
1665
+ movable = bounds = null;
1666
+ removeClass(document.body, classes.noSelection);
1667
+ removeClass(instance.elements.dialog, classes.capture);
1668
+ // allow custom `onmoved` method
1669
+ dispatchEvent('onmoved', instance);
1670
+ }
1671
+ }
1672
+
1673
+ /**
1674
+ * Resets any changes made by moving the element to its original state,
1675
+ *
1676
+ * @param {Object} instance The dilog instance.
1677
+ *
1678
+ * @return {undefined}
1679
+ */
1680
+ function resetMove(instance) {
1681
+ movable = null;
1682
+ var element = instance.elements.dialog;
1683
+ element.style.left = element.style.top = '';
1684
+ }
1685
+
1686
+ /**
1687
+ * Updates the dialog move behavior.
1688
+ *
1689
+ * @param {Object} instance The dilog instance.
1690
+ * @param {Boolean} on True to add the behavior, removes it otherwise.
1691
+ *
1692
+ * @return {undefined}
1693
+ */
1694
+ function updateMovable(instance) {
1695
+ if (instance.get('movable')) {
1696
+ // add class
1697
+ addClass(instance.elements.root, classes.movable);
1698
+ if (instance.isOpen()) {
1699
+ bindMovableEvents(instance);
1700
+ }
1701
+ } else {
1702
+
1703
+ //reset
1704
+ resetMove(instance);
1705
+ // remove class
1706
+ removeClass(instance.elements.root, classes.movable);
1707
+ if (instance.isOpen()) {
1708
+ unbindMovableEvents(instance);
1709
+ }
1710
+ }
1711
+ }
1712
+
1713
+ /* Controls moving a dialog around */
1714
+ //holde the current instance being resized
1715
+ var resizable = null,
1716
+ //holds the staring left offset when resize starts.
1717
+ startingLeft = Number.Nan,
1718
+ //holds the staring width when resize starts.
1719
+ startingWidth = 0,
1720
+ //holds the initial width when resized for the first time.
1721
+ minWidth = 0,
1722
+ //holds the offset of the resize handle.
1723
+ handleOffset = 0
1724
+ ;
1725
+
1726
+ /**
1727
+ * Helper: sets the element width/height and updates left coordinate if neccessary.
1728
+ *
1729
+ * @param {Event} event DOM mousemove event object.
1730
+ * @param {Node} element The element being moved.
1731
+ * @param {Boolean} pinned A flag indicating if the element being resized is pinned to the screen.
1732
+ *
1733
+ * @return {undefined}
1734
+ */
1735
+ function resizeElement(event, element, pageRelative) {
1736
+
1737
+ //calculate offsets from 0,0
1738
+ var current = element;
1739
+ var offsetLeft = 0;
1740
+ var offsetTop = 0;
1741
+ do {
1742
+ offsetLeft += current.offsetLeft;
1743
+ offsetTop += current.offsetTop;
1744
+ } while (current = current.offsetParent);
1745
+
1746
+ // determine X,Y coordinates.
1747
+ var X, Y;
1748
+ if (pageRelative === true) {
1749
+ X = event.pageX;
1750
+ Y = event.pageY;
1751
+ } else {
1752
+ X = event.clientX;
1753
+ Y = event.clientY;
1754
+ }
1755
+ // rtl handling
1756
+ var isRTL = isRightToLeft();
1757
+ if (isRTL) {
1758
+ // reverse X
1759
+ X = document.body.offsetWidth - X;
1760
+ // if has a starting left, calculate offsetRight
1761
+ if (!isNaN(startingLeft)) {
1762
+ offsetLeft = document.body.offsetWidth - offsetLeft - element.offsetWidth;
1763
+ }
1764
+ }
1765
+
1766
+ // set width/height
1767
+ element.style.height = (Y - offsetTop + handleOffset) + 'px';
1768
+ element.style.width = (X - offsetLeft + handleOffset) + 'px';
1769
+
1770
+ // if the element being resized has a starting left, maintain it.
1771
+ // the dialog is centered, divide by half the offset to maintain the margins.
1772
+ if (!isNaN(startingLeft)) {
1773
+ var diff = Math.abs(element.offsetWidth - startingWidth) * 0.5;
1774
+ if (isRTL) {
1775
+ //negate the diff, why?
1776
+ //when growing it should decrease left
1777
+ //when shrinking it should increase left
1778
+ diff *= -1;
1779
+ }
1780
+ if (element.offsetWidth > startingWidth) {
1781
+ //growing
1782
+ element.style.left = (startingLeft + diff) + 'px';
1783
+ } else if (element.offsetWidth >= minWidth) {
1784
+ //shrinking
1785
+ element.style.left = (startingLeft - diff) + 'px';
1786
+ }
1787
+ }
1788
+ }
1789
+
1790
+ /**
1791
+ * Triggers the start of a resize event, attached to the resize handle element mouse down event.
1792
+ * Adds no-selection class to the body, disabling selection while moving.
1793
+ *
1794
+ * @param {Event} event DOM event object.
1795
+ * @param {Object} instance The dilog instance.
1796
+ *
1797
+ * @return {Boolean} false
1798
+ */
1799
+ function beginResize(event, instance) {
1800
+ if (!instance.isMaximized()) {
1801
+ var eventSrc;
1802
+ if (event.type === 'touchstart') {
1803
+ event.preventDefault();
1804
+ eventSrc = event.targetTouches[0];
1805
+ } else if (event.button === 0) {
1806
+ eventSrc = event;
1807
+ }
1808
+ if (eventSrc) {
1809
+ // allow custom `onresize` method
1810
+ dispatchEvent('onresize', instance);
1811
+
1812
+ resizable = instance;
1813
+ handleOffset = instance.elements.resizeHandle.offsetHeight / 2;
1814
+ var element = instance.elements.dialog;
1815
+ addClass(element, classes.capture);
1816
+ startingLeft = parseInt(element.style.left, 10);
1817
+ element.style.height = element.offsetHeight + 'px';
1818
+ element.style.minHeight = instance.elements.header.offsetHeight + instance.elements.footer.offsetHeight + 'px';
1819
+ element.style.width = (startingWidth = element.offsetWidth) + 'px';
1820
+
1821
+ if (element.style.maxWidth !== 'none') {
1822
+ element.style.minWidth = (minWidth = element.offsetWidth) + 'px';
1823
+ }
1824
+ element.style.maxWidth = 'none';
1825
+ addClass(document.body, classes.noSelection);
1826
+ return false;
1827
+ }
1828
+ }
1829
+ }
1830
+
1831
+ /**
1832
+ * The actual resize handler, attached to document.body mousemove event.
1833
+ *
1834
+ * @param {Event} event DOM event object.
1835
+ *
1836
+ * @return {undefined}
1837
+ */
1838
+ function resize(event) {
1839
+ if (resizable) {
1840
+ var eventSrc;
1841
+ if (event.type === 'touchmove') {
1842
+ event.preventDefault();
1843
+ eventSrc = event.targetTouches[0];
1844
+ } else if (event.button === 0) {
1845
+ eventSrc = event;
1846
+ }
1847
+ if (eventSrc) {
1848
+ resizeElement(eventSrc, resizable.elements.dialog, !resizable.get('modal') && !resizable.get('pinned'));
1849
+ }
1850
+ }
1851
+ }
1852
+
1853
+ /**
1854
+ * Triggers the end of a resize event, attached to document.body mouseup event.
1855
+ * Removes no-selection class from document.body, allowing selection.
1856
+ *
1857
+ * @return {undefined}
1858
+ */
1859
+ function endResize() {
1860
+ if (resizable) {
1861
+ var instance = resizable;
1862
+ resizable = null;
1863
+ removeClass(document.body, classes.noSelection);
1864
+ removeClass(instance.elements.dialog, classes.capture);
1865
+ cancelClick = true;
1866
+ // allow custom `onresized` method
1867
+ dispatchEvent('onresized', instance);
1868
+ }
1869
+ }
1870
+
1871
+ /**
1872
+ * Resets any changes made by resizing the element to its original state.
1873
+ *
1874
+ * @param {Object} instance The dilog instance.
1875
+ *
1876
+ * @return {undefined}
1877
+ */
1878
+ function resetResize(instance) {
1879
+ resizable = null;
1880
+ var element = instance.elements.dialog;
1881
+ if (element.style.maxWidth === 'none') {
1882
+ //clear inline styles.
1883
+ element.style.maxWidth = element.style.minWidth = element.style.width = element.style.height = element.style.minHeight = element.style.left = '';
1884
+ //reset variables.
1885
+ startingLeft = Number.Nan;
1886
+ startingWidth = minWidth = handleOffset = 0;
1887
+ }
1888
+ }
1889
+
1890
+
1891
+ /**
1892
+ * Updates the dialog move behavior.
1893
+ *
1894
+ * @param {Object} instance The dilog instance.
1895
+ * @param {Boolean} on True to add the behavior, removes it otherwise.
1896
+ *
1897
+ * @return {undefined}
1898
+ */
1899
+ function updateResizable(instance) {
1900
+ if (instance.get('resizable')) {
1901
+ // add class
1902
+ addClass(instance.elements.root, classes.resizable);
1903
+ if (instance.isOpen()) {
1904
+ bindResizableEvents(instance);
1905
+ }
1906
+ } else {
1907
+ //reset
1908
+ resetResize(instance);
1909
+ // remove class
1910
+ removeClass(instance.elements.root, classes.resizable);
1911
+ if (instance.isOpen()) {
1912
+ unbindResizableEvents(instance);
1913
+ }
1914
+ }
1915
+ }
1916
+
1917
+ /**
1918
+ * Reset move/resize on window resize.
1919
+ *
1920
+ * @param {Event} event window resize event object.
1921
+ *
1922
+ * @return {undefined}
1923
+ */
1924
+ function windowResize(/*event*/) {
1925
+ for (var x = 0; x < openDialogs.length; x += 1) {
1926
+ var instance = openDialogs[x];
1927
+ if (instance.get('autoReset')) {
1928
+ resetMove(instance);
1929
+ resetResize(instance);
1930
+ }
1931
+ }
1932
+ }
1933
+ /**
1934
+ * Bind dialogs events
1935
+ *
1936
+ * @param {Object} instance The dilog instance.
1937
+ *
1938
+ * @return {undefined}
1939
+ */
1940
+ function bindEvents(instance) {
1941
+ // if first dialog, hook global handlers
1942
+ if (openDialogs.length === 1) {
1943
+ //global
1944
+ on(window, 'resize', windowResize);
1945
+ on(document.body, 'keyup', keyupHandler);
1946
+ on(document.body, 'keydown', keydownHandler);
1947
+ on(document.body, 'focus', onReset);
1948
+
1949
+ //move
1950
+ on(document.documentElement, 'mousemove', move);
1951
+ on(document.documentElement, 'touchmove', move);
1952
+ on(document.documentElement, 'mouseup', endMove);
1953
+ on(document.documentElement, 'touchend', endMove);
1954
+ //resize
1955
+ on(document.documentElement, 'mousemove', resize);
1956
+ on(document.documentElement, 'touchmove', resize);
1957
+ on(document.documentElement, 'mouseup', endResize);
1958
+ on(document.documentElement, 'touchend', endResize);
1959
+ }
1960
+
1961
+ // common events
1962
+ on(instance.elements.commands.container, 'click', instance.__internal.commandsClickHandler);
1963
+ on(instance.elements.footer, 'click', instance.__internal.buttonsClickHandler);
1964
+ on(instance.elements.reset[0], 'focus', instance.__internal.resetHandler);
1965
+ on(instance.elements.reset[1], 'focus', instance.__internal.resetHandler);
1966
+
1967
+ //prevent handling key up when dialog is being opened by a key stroke.
1968
+ cancelKeyup = true;
1969
+ // hook in transition handler
1970
+ on(instance.elements.dialog, transition.type, instance.__internal.transitionInHandler);
1971
+
1972
+ // modelss only events
1973
+ if (!instance.get('modal')) {
1974
+ bindModelessEvents(instance);
1975
+ }
1976
+
1977
+ // resizable
1978
+ if (instance.get('resizable')) {
1979
+ bindResizableEvents(instance);
1980
+ }
1981
+
1982
+ // movable
1983
+ if (instance.get('movable')) {
1984
+ bindMovableEvents(instance);
1985
+ }
1986
+ }
1987
+
1988
+ /**
1989
+ * Unbind dialogs events
1990
+ *
1991
+ * @param {Object} instance The dilog instance.
1992
+ *
1993
+ * @return {undefined}
1994
+ */
1995
+ function unbindEvents(instance) {
1996
+ // if last dialog, remove global handlers
1997
+ if (openDialogs.length === 1) {
1998
+ //global
1999
+ off(window, 'resize', windowResize);
2000
+ off(document.body, 'keyup', keyupHandler);
2001
+ off(document.body, 'keydown', keydownHandler);
2002
+ off(document.body, 'focus', onReset);
2003
+ //move
2004
+ off(document.documentElement, 'mousemove', move);
2005
+ off(document.documentElement, 'mouseup', endMove);
2006
+ //resize
2007
+ off(document.documentElement, 'mousemove', resize);
2008
+ off(document.documentElement, 'mouseup', endResize);
2009
+ }
2010
+
2011
+ // common events
2012
+ off(instance.elements.commands.container, 'click', instance.__internal.commandsClickHandler);
2013
+ off(instance.elements.footer, 'click', instance.__internal.buttonsClickHandler);
2014
+ off(instance.elements.reset[0], 'focus', instance.__internal.resetHandler);
2015
+ off(instance.elements.reset[1], 'focus', instance.__internal.resetHandler);
2016
+
2017
+ // hook out transition handler
2018
+ on(instance.elements.dialog, transition.type, instance.__internal.transitionOutHandler);
2019
+
2020
+ // modelss only events
2021
+ if (!instance.get('modal')) {
2022
+ unbindModelessEvents(instance);
2023
+ }
2024
+
2025
+ // movable
2026
+ if (instance.get('movable')) {
2027
+ unbindMovableEvents(instance);
2028
+ }
2029
+
2030
+ // resizable
2031
+ if (instance.get('resizable')) {
2032
+ unbindResizableEvents(instance);
2033
+ }
2034
+
2035
+ }
2036
+
2037
+ /**
2038
+ * Bind modeless specific events
2039
+ *
2040
+ * @param {Object} instance The dilog instance.
2041
+ *
2042
+ * @return {undefined}
2043
+ */
2044
+ function bindModelessEvents(instance) {
2045
+ on(instance.elements.dialog, 'focus', instance.__internal.bringToFrontHandler, true);
2046
+ }
2047
+
2048
+ /**
2049
+ * Unbind modeless specific events
2050
+ *
2051
+ * @param {Object} instance The dilog instance.
2052
+ *
2053
+ * @return {undefined}
2054
+ */
2055
+ function unbindModelessEvents(instance) {
2056
+ off(instance.elements.dialog, 'focus', instance.__internal.bringToFrontHandler, true);
2057
+ }
2058
+
2059
+
2060
+
2061
+ /**
2062
+ * Bind movable specific events
2063
+ *
2064
+ * @param {Object} instance The dilog instance.
2065
+ *
2066
+ * @return {undefined}
2067
+ */
2068
+ function bindMovableEvents(instance) {
2069
+ on(instance.elements.header, 'mousedown', instance.__internal.beginMoveHandler);
2070
+ on(instance.elements.header, 'touchstart', instance.__internal.beginMoveHandler);
2071
+ }
2072
+
2073
+ /**
2074
+ * Unbind movable specific events
2075
+ *
2076
+ * @param {Object} instance The dilog instance.
2077
+ *
2078
+ * @return {undefined}
2079
+ */
2080
+ function unbindMovableEvents(instance) {
2081
+ off(instance.elements.header, 'mousedown', instance.__internal.beginMoveHandler);
2082
+ off(instance.elements.header, 'touchstart', instance.__internal.beginMoveHandler);
2083
+ }
2084
+
2085
+
2086
+
2087
+ /**
2088
+ * Bind resizable specific events
2089
+ *
2090
+ * @param {Object} instance The dilog instance.
2091
+ *
2092
+ * @return {undefined}
2093
+ */
2094
+ function bindResizableEvents(instance) {
2095
+ on(instance.elements.resizeHandle, 'mousedown', instance.__internal.beginResizeHandler);
2096
+ on(instance.elements.resizeHandle, 'touchstart', instance.__internal.beginResizeHandler);
2097
+ }
2098
+
2099
+ /**
2100
+ * Unbind resizable specific events
2101
+ *
2102
+ * @param {Object} instance The dilog instance.
2103
+ *
2104
+ * @return {undefined}
2105
+ */
2106
+ function unbindResizableEvents(instance) {
2107
+ off(instance.elements.resizeHandle, 'mousedown', instance.__internal.beginResizeHandler);
2108
+ off(instance.elements.resizeHandle, 'touchstart', instance.__internal.beginResizeHandler);
2109
+ }
2110
+
2111
+ /**
2112
+ * Bind closable events
2113
+ *
2114
+ * @param {Object} instance The dilog instance.
2115
+ *
2116
+ * @return {undefined}
2117
+ */
2118
+ function bindClosableEvents(instance) {
2119
+ on(instance.elements.modal, 'click', instance.__internal.modalClickHandler);
2120
+ }
2121
+
2122
+ /**
2123
+ * Unbind closable specific events
2124
+ *
2125
+ * @param {Object} instance The dilog instance.
2126
+ *
2127
+ * @return {undefined}
2128
+ */
2129
+ function unbindClosableEvents(instance) {
2130
+ off(instance.elements.modal, 'click', instance.__internal.modalClickHandler);
2131
+ }
2132
+ // dialog API
2133
+ return {
2134
+ __init:initialize,
2135
+ /**
2136
+ * Check if dialog is currently open
2137
+ *
2138
+ * @return {Boolean}
2139
+ */
2140
+ isOpen: function () {
2141
+ return this.__internal.isOpen;
2142
+ },
2143
+ isModal: function (){
2144
+ return this.elements.root.className.indexOf(classes.modeless) < 0;
2145
+ },
2146
+ isMaximized:function(){
2147
+ return this.elements.root.className.indexOf(classes.maximized) > -1;
2148
+ },
2149
+ isPinned:function(){
2150
+ return this.elements.root.className.indexOf(classes.unpinned) < 0;
2151
+ },
2152
+ maximize:function(){
2153
+ if(!this.isMaximized()){
2154
+ maximize(this);
2155
+ }
2156
+ return this;
2157
+ },
2158
+ restore:function(){
2159
+ if(this.isMaximized()){
2160
+ restore(this);
2161
+ }
2162
+ return this;
2163
+ },
2164
+ pin:function(){
2165
+ if(!this.isPinned()){
2166
+ pin(this);
2167
+ }
2168
+ return this;
2169
+ },
2170
+ unpin:function(){
2171
+ if(this.isPinned()){
2172
+ unpin(this);
2173
+ }
2174
+ return this;
2175
+ },
2176
+ bringToFront:function(){
2177
+ bringToFront(null, this);
2178
+ return this;
2179
+ },
2180
+ /**
2181
+ * Move the dialog to a specific x/y coordinates
2182
+ *
2183
+ * @param {Number} x The new dialog x coordinate in pixels.
2184
+ * @param {Number} y The new dialog y coordinate in pixels.
2185
+ *
2186
+ * @return {Object} The dialog instance.
2187
+ */
2188
+ moveTo:function(x,y){
2189
+ if(!isNaN(x) && !isNaN(y)){
2190
+ // allow custom `onmove` method
2191
+ dispatchEvent('onmove', this);
2192
+
2193
+ var element = this.elements.dialog,
2194
+ current = element,
2195
+ offsetLeft = 0,
2196
+ offsetTop = 0;
2197
+
2198
+ //subtract existing left,top
2199
+ if (element.style.left) {
2200
+ offsetLeft -= parseInt(element.style.left, 10);
2201
+ }
2202
+ if (element.style.top) {
2203
+ offsetTop -= parseInt(element.style.top, 10);
2204
+ }
2205
+ //calc offset
2206
+ do {
2207
+ offsetLeft += current.offsetLeft;
2208
+ offsetTop += current.offsetTop;
2209
+ } while (current = current.offsetParent);
2210
+
2211
+ //calc left, top
2212
+ var left = (x - offsetLeft);
2213
+ var top = (y - offsetTop);
2214
+
2215
+ //// rtl handling
2216
+ if (isRightToLeft()) {
2217
+ left *= -1;
2218
+ }
2219
+
2220
+ element.style.left = left + 'px';
2221
+ element.style.top = top + 'px';
2222
+
2223
+ // allow custom `onmoved` method
2224
+ dispatchEvent('onmoved', this);
2225
+ }
2226
+ return this;
2227
+ },
2228
+ /**
2229
+ * Resize the dialog to a specific width/height (the dialog must be 'resizable').
2230
+ * The dialog can be resized to:
2231
+ * A minimum width equal to the initial display width
2232
+ * A minimum height equal to the sum of header/footer heights.
2233
+ *
2234
+ *
2235
+ * @param {Number or String} width The new dialog width in pixels or in percent.
2236
+ * @param {Number or String} height The new dialog height in pixels or in percent.
2237
+ *
2238
+ * @return {Object} The dialog instance.
2239
+ */
2240
+ resizeTo:function(width,height){
2241
+ var w = parseFloat(width),
2242
+ h = parseFloat(height),
2243
+ regex = /(\d*\.\d+|\d+)%/
2244
+ ;
2245
+
2246
+ if(!isNaN(w) && !isNaN(h) && this.get('resizable') === true){
2247
+
2248
+ // allow custom `onresize` method
2249
+ dispatchEvent('onresize', this);
2250
+
2251
+ if(('' + width).match(regex)){
2252
+ w = w / 100 * document.documentElement.clientWidth ;
2253
+ }
2254
+
2255
+ if(('' + height).match(regex)){
2256
+ h = h / 100 * document.documentElement.clientHeight;
2257
+ }
2258
+
2259
+ var element = this.elements.dialog;
2260
+ if (element.style.maxWidth !== 'none') {
2261
+ element.style.minWidth = (minWidth = element.offsetWidth) + 'px';
2262
+ }
2263
+ element.style.maxWidth = 'none';
2264
+ element.style.minHeight = this.elements.header.offsetHeight + this.elements.footer.offsetHeight + 'px';
2265
+ element.style.width = w + 'px';
2266
+ element.style.height = h + 'px';
2267
+
2268
+ // allow custom `onresized` method
2269
+ dispatchEvent('onresized', this);
2270
+ }
2271
+ return this;
2272
+ },
2273
+ /**
2274
+ * Gets or Sets dialog settings/options
2275
+ *
2276
+ * @param {String|Object} key A string specifying a propery name or a collection of key/value pairs.
2277
+ * @param {Object} value Optional, the value associated with the key (in case it was a string).
2278
+ *
2279
+ * @return {undefined}
2280
+ */
2281
+ setting : function (key, value) {
2282
+ var self = this;
2283
+ var result = update(this, this.__internal.options, function(k,o,n){ optionUpdated(self,k,o,n); }, key, value);
2284
+ if(result.op === 'get'){
2285
+ if(result.found){
2286
+ return result.value;
2287
+ }else if(typeof this.settings !== 'undefined'){
2288
+ return update(this, this.settings, this.settingUpdated || function(){}, key, value).value;
2289
+ }else{
2290
+ return undefined;
2291
+ }
2292
+ }else if(result.op === 'set'){
2293
+ if(result.items.length > 0){
2294
+ var callback = this.settingUpdated || function(){};
2295
+ for(var x=0;x<result.items.length;x+=1){
2296
+ var item = result.items[x];
2297
+ if(!item.found && typeof this.settings !== 'undefined'){
2298
+ update(this, this.settings, callback, item.key, item.value);
2299
+ }
2300
+ }
2301
+ }
2302
+ return this;
2303
+ }
2304
+ },
2305
+ /**
2306
+ * [Alias] Sets dialog settings/options
2307
+ */
2308
+ set:function(key, value){
2309
+ this.setting(key,value);
2310
+ return this;
2311
+ },
2312
+ /**
2313
+ * [Alias] Gets dialog settings/options
2314
+ */
2315
+ get:function(key){
2316
+ return this.setting(key);
2317
+ },
2318
+ /**
2319
+ * Sets dialog header
2320
+ * @content {string or element}
2321
+ *
2322
+ * @return {undefined}
2323
+ */
2324
+ setHeader:function(content){
2325
+ if(typeof content === 'string'){
2326
+ clearContents(this.elements.header);
2327
+ this.elements.header.innerHTML = content;
2328
+ }else if (content instanceof window.HTMLElement && this.elements.header.firstChild !== content){
2329
+ clearContents(this.elements.header);
2330
+ this.elements.header.appendChild(content);
2331
+ }
2332
+ return this;
2333
+ },
2334
+ /**
2335
+ * Sets dialog contents
2336
+ * @content {string or element}
2337
+ *
2338
+ * @return {undefined}
2339
+ */
2340
+ setContent:function(content){
2341
+ if(typeof content === 'string'){
2342
+ clearContents(this.elements.content);
2343
+ this.elements.content.innerHTML = content;
2344
+ }else if (content instanceof window.HTMLElement && this.elements.content.firstChild !== content){
2345
+ clearContents(this.elements.content);
2346
+ this.elements.content.appendChild(content);
2347
+ }
2348
+ return this;
2349
+ },
2350
+ /**
2351
+ * Show the dialog as modal
2352
+ *
2353
+ * @return {Object} the dialog instance.
2354
+ */
2355
+ showModal: function(className){
2356
+ return this.show(true, className);
2357
+ },
2358
+ /**
2359
+ * Show the dialog
2360
+ *
2361
+ * @return {Object} the dialog instance.
2362
+ */
2363
+ show: function (modal, className) {
2364
+
2365
+ // ensure initialization
2366
+ initialize(this);
2367
+
2368
+ if ( !this.__internal.isOpen ) {
2369
+
2370
+ // add to open dialogs
2371
+ this.__internal.isOpen = true;
2372
+ openDialogs.push(this);
2373
+
2374
+ // save last focused element
2375
+ if(alertify.defaults.maintainFocus){
2376
+ this.__internal.activeElement = document.activeElement;
2377
+ }
2378
+
2379
+ //allow custom dom manipulation updates before showing the dialog.
2380
+ if(typeof this.prepare === 'function'){
2381
+ this.prepare();
2382
+ }
2383
+
2384
+ bindEvents(this);
2385
+
2386
+ if(modal !== undefined){
2387
+ this.set('modal', modal);
2388
+ }
2389
+
2390
+ //save scroll to prevent document jump
2391
+ saveScrollPosition();
2392
+
2393
+ ensureNoOverflow();
2394
+
2395
+ // allow custom dialog class on show
2396
+ if(typeof className === 'string' && className !== ''){
2397
+ this.__internal.className = className;
2398
+ addClass(this.elements.root, className);
2399
+ }
2400
+
2401
+ // maximize if start maximized
2402
+ if ( this.get('startMaximized')) {
2403
+ this.maximize();
2404
+ }else if(this.isMaximized()){
2405
+ restore(this);
2406
+ }
2407
+
2408
+ updateAbsPositionFix(this);
2409
+
2410
+ removeClass(this.elements.root, classes.animationOut);
2411
+ addClass(this.elements.root, classes.animationIn);
2412
+
2413
+ // set 1s fallback in case transition event doesn't fire
2414
+ clearTimeout( this.__internal.timerIn);
2415
+ this.__internal.timerIn = setTimeout( this.__internal.transitionInHandler, transition.supported ? 1000 : 100 );
2416
+
2417
+ if(isSafari){
2418
+ // force desktop safari reflow
2419
+ var root = this.elements.root;
2420
+ root.style.display = 'none';
2421
+ setTimeout(function(){root.style.display = 'block';}, 0);
2422
+ }
2423
+
2424
+ //reflow
2425
+ reflow = this.elements.root.offsetWidth;
2426
+
2427
+ // show dialog
2428
+ removeClass(this.elements.root, classes.hidden);
2429
+
2430
+ // internal on show event
2431
+ if(typeof this.hooks.onshow === 'function'){
2432
+ this.hooks.onshow.call(this);
2433
+ }
2434
+
2435
+ // allow custom `onshow` method
2436
+ dispatchEvent('onshow', this);
2437
+
2438
+ }else{
2439
+ // reset move updates
2440
+ resetMove(this);
2441
+ // reset resize updates
2442
+ resetResize(this);
2443
+ // shake the dialog to indicate its already open
2444
+ addClass(this.elements.dialog, classes.shake);
2445
+ var self = this;
2446
+ setTimeout(function(){
2447
+ removeClass(self.elements.dialog, classes.shake);
2448
+ },200);
2449
+ }
2450
+ return this;
2451
+ },
2452
+ /**
2453
+ * Close the dialog
2454
+ *
2455
+ * @return {Object} The dialog instance
2456
+ */
2457
+ close: function () {
2458
+ if (this.__internal.isOpen ) {
2459
+ // custom `onclosing` event
2460
+ if(dispatchEvent('onclosing', this) !== false){
2461
+
2462
+ unbindEvents(this);
2463
+
2464
+ removeClass(this.elements.root, classes.animationIn);
2465
+ addClass(this.elements.root, classes.animationOut);
2466
+
2467
+ // set 1s fallback in case transition event doesn't fire
2468
+ clearTimeout( this.__internal.timerOut );
2469
+ this.__internal.timerOut = setTimeout( this.__internal.transitionOutHandler, transition.supported ? 1000 : 100 );
2470
+ // hide dialog
2471
+ addClass(this.elements.root, classes.hidden);
2472
+ //reflow
2473
+ reflow = this.elements.modal.offsetWidth;
2474
+
2475
+ // remove custom dialog class on hide
2476
+ if (typeof this.__internal.className !== 'undefined' && this.__internal.className !== '') {
2477
+ removeClass(this.elements.root, this.__internal.className);
2478
+ }
2479
+
2480
+ // internal on close event
2481
+ if(typeof this.hooks.onclose === 'function'){
2482
+ this.hooks.onclose.call(this);
2483
+ }
2484
+
2485
+ // allow custom `onclose` method
2486
+ dispatchEvent('onclose', this);
2487
+
2488
+ //remove from open dialogs
2489
+ openDialogs.splice(openDialogs.indexOf(this),1);
2490
+ this.__internal.isOpen = false;
2491
+
2492
+ ensureNoOverflow();
2493
+ }
2494
+
2495
+ }
2496
+ return this;
2497
+ },
2498
+ /**
2499
+ * Close all open dialogs except this.
2500
+ *
2501
+ * @return {undefined}
2502
+ */
2503
+ closeOthers:function(){
2504
+ alertify.closeAll(this);
2505
+ return this;
2506
+ },
2507
+ /**
2508
+ * Destroys this dialog instance
2509
+ *
2510
+ * @return {undefined}
2511
+ */
2512
+ destroy:function(){
2513
+ if (this.__internal.isOpen ) {
2514
+ //mark dialog for destruction, this will be called on tranistionOut event.
2515
+ this.__internal.destroy = function(){
2516
+ destruct(this, initialize);
2517
+ };
2518
+ //close the dialog to unbind all events.
2519
+ this.close();
2520
+ }else{
2521
+ destruct(this, initialize);
2522
+ }
2523
+ return this;
2524
+ },
2525
+ };
2526
+ } () );
2527
+ var notifier = (function () {
2528
+ var reflow,
2529
+ element,
2530
+ openInstances = [],
2531
+ classes = {
2532
+ base: 'alertify-notifier',
2533
+ message: 'ajs-message',
2534
+ top: 'ajs-top',
2535
+ right: 'ajs-right',
2536
+ bottom: 'ajs-bottom',
2537
+ left: 'ajs-left',
2538
+ center: 'ajs-center',
2539
+ visible: 'ajs-visible',
2540
+ hidden: 'ajs-hidden',
2541
+ close: 'ajs-close'
2542
+ };
2543
+ /**
2544
+ * Helper: initializes the notifier instance
2545
+ *
2546
+ */
2547
+ function initialize(instance) {
2548
+
2549
+ if (!instance.__internal) {
2550
+ instance.__internal = {
2551
+ position: alertify.defaults.notifier.position,
2552
+ delay: alertify.defaults.notifier.delay,
2553
+ };
2554
+
2555
+ element = document.createElement('DIV');
2556
+
2557
+ updatePosition(instance);
2558
+ }
2559
+
2560
+ //add to DOM tree.
2561
+ if (element.parentNode !== document.body) {
2562
+ document.body.appendChild(element);
2563
+ }
2564
+ }
2565
+
2566
+ function pushInstance(instance) {
2567
+ instance.__internal.pushed = true;
2568
+ openInstances.push(instance);
2569
+ }
2570
+ function popInstance(instance) {
2571
+ openInstances.splice(openInstances.indexOf(instance), 1);
2572
+ instance.__internal.pushed = false;
2573
+ }
2574
+ /**
2575
+ * Helper: update the notifier instance position
2576
+ *
2577
+ */
2578
+ function updatePosition(instance) {
2579
+ element.className = classes.base;
2580
+ switch (instance.__internal.position) {
2581
+ case 'top-right':
2582
+ addClass(element, classes.top + ' ' + classes.right);
2583
+ break;
2584
+ case 'top-left':
2585
+ addClass(element, classes.top + ' ' + classes.left);
2586
+ break;
2587
+ case 'top-center':
2588
+ addClass(element, classes.top + ' ' + classes.center);
2589
+ break;
2590
+ case 'bottom-left':
2591
+ addClass(element, classes.bottom + ' ' + classes.left);
2592
+ break;
2593
+ case 'bottom-center':
2594
+ addClass(element, classes.bottom + ' ' + classes.center);
2595
+ break;
2596
+
2597
+ default:
2598
+ case 'bottom-right':
2599
+ addClass(element, classes.bottom + ' ' + classes.right);
2600
+ break;
2601
+ }
2602
+ }
2603
+
2604
+ /**
2605
+ * creates a new notification message
2606
+ *
2607
+ * @param {DOMElement} message The notifier message element
2608
+ * @param {Number} wait Time (in ms) to wait before the message is dismissed, a value of 0 means keep open till clicked.
2609
+ * @param {Function} callback A callback function to be invoked when the message is dismissed.
2610
+ *
2611
+ * @return {undefined}
2612
+ */
2613
+ function create(div, callback) {
2614
+
2615
+ function clickDelegate(event, instance) {
2616
+ if(!instance.__internal.closeButton || event.target.getAttribute('data-close') === 'true'){
2617
+ instance.dismiss(true);
2618
+ }
2619
+ }
2620
+
2621
+ function transitionDone(event, instance) {
2622
+ // unbind event
2623
+ off(instance.element, transition.type, transitionDone);
2624
+ // remove the message
2625
+ element.removeChild(instance.element);
2626
+ }
2627
+
2628
+ function initialize(instance) {
2629
+ if (!instance.__internal) {
2630
+ instance.__internal = {
2631
+ pushed: false,
2632
+ delay : undefined,
2633
+ timer: undefined,
2634
+ clickHandler: undefined,
2635
+ transitionEndHandler: undefined,
2636
+ transitionTimeout: undefined
2637
+ };
2638
+ instance.__internal.clickHandler = delegate(instance, clickDelegate);
2639
+ instance.__internal.transitionEndHandler = delegate(instance, transitionDone);
2640
+ }
2641
+ return instance;
2642
+ }
2643
+ function clearTimers(instance) {
2644
+ clearTimeout(instance.__internal.timer);
2645
+ clearTimeout(instance.__internal.transitionTimeout);
2646
+ }
2647
+ return initialize({
2648
+ /* notification DOM element*/
2649
+ element: div,
2650
+ /*
2651
+ * Pushes a notification message
2652
+ * @param {string or DOMElement} content The notification message content
2653
+ * @param {Number} wait The time (in seconds) to wait before the message is dismissed, a value of 0 means keep open till clicked.
2654
+ *
2655
+ */
2656
+ push: function (_content, _wait) {
2657
+ if (!this.__internal.pushed) {
2658
+
2659
+ pushInstance(this);
2660
+ clearTimers(this);
2661
+
2662
+ var content, wait;
2663
+ switch (arguments.length) {
2664
+ case 0:
2665
+ wait = this.__internal.delay;
2666
+ break;
2667
+ case 1:
2668
+ if (typeof (_content) === 'number') {
2669
+ wait = _content;
2670
+ } else {
2671
+ content = _content;
2672
+ wait = this.__internal.delay;
2673
+ }
2674
+ break;
2675
+ case 2:
2676
+ content = _content;
2677
+ wait = _wait;
2678
+ break;
2679
+ }
2680
+ this.__internal.closeButton = alertify.defaults.notifier.closeButton;
2681
+ // set contents
2682
+ if (typeof content !== 'undefined') {
2683
+ this.setContent(content);
2684
+ }
2685
+ // append or insert
2686
+ if (notifier.__internal.position.indexOf('top') < 0) {
2687
+ element.appendChild(this.element);
2688
+ } else {
2689
+ element.insertBefore(this.element, element.firstChild);
2690
+ }
2691
+ reflow = this.element.offsetWidth;
2692
+ addClass(this.element, classes.visible);
2693
+ // attach click event
2694
+ on(this.element, 'click', this.__internal.clickHandler);
2695
+ return this.delay(wait);
2696
+ }
2697
+ return this;
2698
+ },
2699
+ /*
2700
+ * {Function} callback function to be invoked before dismissing the notification message.
2701
+ * Remarks: A return value === 'false' will cancel the dismissal
2702
+ *
2703
+ */
2704
+ ondismiss: function () { },
2705
+ /*
2706
+ * {Function} callback function to be invoked when the message is dismissed.
2707
+ *
2708
+ */
2709
+ callback: callback,
2710
+ /*
2711
+ * Dismisses the notification message
2712
+ * @param {Boolean} clicked A flag indicating if the dismissal was caused by a click.
2713
+ *
2714
+ */
2715
+ dismiss: function (clicked) {
2716
+ if (this.__internal.pushed) {
2717
+ clearTimers(this);
2718
+ if (!(typeof this.ondismiss === 'function' && this.ondismiss.call(this) === false)) {
2719
+ //detach click event
2720
+ off(this.element, 'click', this.__internal.clickHandler);
2721
+ // ensure element exists
2722
+ if (typeof this.element !== 'undefined' && this.element.parentNode === element) {
2723
+ //transition end or fallback
2724
+ this.__internal.transitionTimeout = setTimeout(this.__internal.transitionEndHandler, transition.supported ? 1000 : 100);
2725
+ removeClass(this.element, classes.visible);
2726
+
2727
+ // custom callback on dismiss
2728
+ if (typeof this.callback === 'function') {
2729
+ this.callback.call(this, clicked);
2730
+ }
2731
+ }
2732
+ popInstance(this);
2733
+ }
2734
+ }
2735
+ return this;
2736
+ },
2737
+ /*
2738
+ * Delays the notification message dismissal
2739
+ * @param {Number} wait The time (in seconds) to wait before the message is dismissed, a value of 0 means keep open till clicked.
2740
+ *
2741
+ */
2742
+ delay: function (wait) {
2743
+ clearTimers(this);
2744
+ this.__internal.delay = typeof wait !== 'undefined' && !isNaN(+wait) ? +wait : notifier.__internal.delay;
2745
+ if (this.__internal.delay > 0) {
2746
+ var self = this;
2747
+ this.__internal.timer = setTimeout(function () { self.dismiss(); }, this.__internal.delay * 1000);
2748
+ }
2749
+ return this;
2750
+ },
2751
+ /*
2752
+ * Sets the notification message contents
2753
+ * @param {string or DOMElement} content The notification message content
2754
+ *
2755
+ */
2756
+ setContent: function (content) {
2757
+ if (typeof content === 'string') {
2758
+ clearContents(this.element);
2759
+ this.element.innerHTML = content;
2760
+ } else if (content instanceof window.HTMLElement && this.element.firstChild !== content) {
2761
+ clearContents(this.element);
2762
+ this.element.appendChild(content);
2763
+ }
2764
+ if(this.__internal.closeButton){
2765
+ var close = document.createElement('span');
2766
+ addClass(close, classes.close);
2767
+ close.setAttribute('data-close', true);
2768
+ this.element.appendChild(close);
2769
+ }
2770
+ return this;
2771
+ },
2772
+ /*
2773
+ * Dismisses all open notifications except this.
2774
+ *
2775
+ */
2776
+ dismissOthers: function () {
2777
+ notifier.dismissAll(this);
2778
+ return this;
2779
+ }
2780
+ });
2781
+ }
2782
+
2783
+ //notifier api
2784
+ return {
2785
+ /**
2786
+ * Gets or Sets notifier settings.
2787
+ *
2788
+ * @param {string} key The setting name
2789
+ * @param {Variant} value The setting value.
2790
+ *
2791
+ * @return {Object} if the called as a setter, return the notifier instance.
2792
+ */
2793
+ setting: function (key, value) {
2794
+ //ensure init
2795
+ initialize(this);
2796
+
2797
+ if (typeof value === 'undefined') {
2798
+ //get
2799
+ return this.__internal[key];
2800
+ } else {
2801
+ //set
2802
+ switch (key) {
2803
+ case 'position':
2804
+ this.__internal.position = value;
2805
+ updatePosition(this);
2806
+ break;
2807
+ case 'delay':
2808
+ this.__internal.delay = value;
2809
+ break;
2810
+ }
2811
+ }
2812
+ return this;
2813
+ },
2814
+ /**
2815
+ * [Alias] Sets dialog settings/options
2816
+ */
2817
+ set:function(key,value){
2818
+ this.setting(key,value);
2819
+ return this;
2820
+ },
2821
+ /**
2822
+ * [Alias] Gets dialog settings/options
2823
+ */
2824
+ get:function(key){
2825
+ return this.setting(key);
2826
+ },
2827
+ /**
2828
+ * Creates a new notification message
2829
+ *
2830
+ * @param {string} type The type of notification message (simply a CSS class name 'ajs-{type}' to be added).
2831
+ * @param {Function} callback A callback function to be invoked when the message is dismissed.
2832
+ *
2833
+ * @return {undefined}
2834
+ */
2835
+ create: function (type, callback) {
2836
+ //ensure notifier init
2837
+ initialize(this);
2838
+ //create new notification message
2839
+ var div = document.createElement('div');
2840
+ div.className = classes.message + ((typeof type === 'string' && type !== '') ? ' ajs-' + type : '');
2841
+ return create(div, callback);
2842
+ },
2843
+ /**
2844
+ * Dismisses all open notifications.
2845
+ *
2846
+ * @param {Object} excpet [optional] The notification object to exclude from dismissal.
2847
+ *
2848
+ */
2849
+ dismissAll: function (except) {
2850
+ var clone = openInstances.slice(0);
2851
+ for (var x = 0; x < clone.length; x += 1) {
2852
+ var instance = clone[x];
2853
+ if (except === undefined || except !== instance) {
2854
+ instance.dismiss();
2855
+ }
2856
+ }
2857
+ }
2858
+ };
2859
+ })();
2860
+
2861
+ /**
2862
+ * Alertify public API
2863
+ * This contains everything that is exposed through the alertify object.
2864
+ *
2865
+ * @return {Object}
2866
+ */
2867
+ function Alertify() {
2868
+
2869
+ // holds a references of created dialogs
2870
+ var dialogs = {};
2871
+
2872
+ /**
2873
+ * Extends a given prototype by merging properties from base into sub.
2874
+ *
2875
+ * @sub {Object} sub The prototype being overwritten.
2876
+ * @base {Object} base The prototype being written.
2877
+ *
2878
+ * @return {Object} The extended prototype.
2879
+ */
2880
+ function extend(sub, base) {
2881
+ // copy dialog pototype over definition.
2882
+ for (var prop in base) {
2883
+ if (base.hasOwnProperty(prop)) {
2884
+ sub[prop] = base[prop];
2885
+ }
2886
+ }
2887
+ return sub;
2888
+ }
2889
+
2890
+
2891
+ /**
2892
+ * Helper: returns a dialog instance from saved dialogs.
2893
+ * and initializes the dialog if its not already initialized.
2894
+ *
2895
+ * @name {String} name The dialog name.
2896
+ *
2897
+ * @return {Object} The dialog instance.
2898
+ */
2899
+ function get_dialog(name) {
2900
+ var dialog = dialogs[name].dialog;
2901
+ //initialize the dialog if its not already initialized.
2902
+ if (dialog && typeof dialog.__init === 'function') {
2903
+ dialog.__init(dialog);
2904
+ }
2905
+ return dialog;
2906
+ }
2907
+
2908
+ /**
2909
+ * Helper: registers a new dialog definition.
2910
+ *
2911
+ * @name {String} name The dialog name.
2912
+ * @Factory {Function} Factory a function resposible for creating dialog prototype.
2913
+ * @transient {Boolean} transient True to create a new dialog instance each time the dialog is invoked, false otherwise.
2914
+ * @base {String} base the name of another dialog to inherit from.
2915
+ *
2916
+ * @return {Object} The dialog definition.
2917
+ */
2918
+ function register(name, Factory, transient, base) {
2919
+ var definition = {
2920
+ dialog: null,
2921
+ factory: Factory
2922
+ };
2923
+
2924
+ //if this is based on an existing dialog, create a new definition
2925
+ //by applying the new protoype over the existing one.
2926
+ if (base !== undefined) {
2927
+ definition.factory = function () {
2928
+ return extend(new dialogs[base].factory(), new Factory());
2929
+ };
2930
+ }
2931
+
2932
+ if (!transient) {
2933
+ //create a new definition based on dialog
2934
+ definition.dialog = extend(new definition.factory(), dialog);
2935
+ }
2936
+ return dialogs[name] = definition;
2937
+ }
2938
+
2939
+ return {
2940
+ /**
2941
+ * Alertify defaults
2942
+ *
2943
+ * @type {Object}
2944
+ */
2945
+ defaults: defaults,
2946
+ /**
2947
+ * Dialogs factory
2948
+ *
2949
+ * @param {string} Dialog name.
2950
+ * @param {Function} A Dialog factory function.
2951
+ * @param {Boolean} Indicates whether to create a singleton or transient dialog.
2952
+ * @param {String} The name of the base type to inherit from.
2953
+ */
2954
+ dialog: function (name, Factory, transient, base) {
2955
+
2956
+ // get request, create a new instance and return it.
2957
+ if (typeof Factory !== 'function') {
2958
+ return get_dialog(name);
2959
+ }
2960
+
2961
+ if (this.hasOwnProperty(name)) {
2962
+ throw new Error('alertify.dialog: name already exists');
2963
+ }
2964
+
2965
+ // register the dialog
2966
+ var definition = register(name, Factory, transient, base);
2967
+
2968
+ if (transient) {
2969
+
2970
+ // make it public
2971
+ this[name] = function () {
2972
+ //if passed with no params, consider it a get request
2973
+ if (arguments.length === 0) {
2974
+ return definition.dialog;
2975
+ } else {
2976
+ var instance = extend(new definition.factory(), dialog);
2977
+ //ensure init
2978
+ if (instance && typeof instance.__init === 'function') {
2979
+ instance.__init(instance);
2980
+ }
2981
+ instance['main'].apply(instance, arguments);
2982
+ return instance['show'].apply(instance);
2983
+ }
2984
+ };
2985
+ } else {
2986
+ // make it public
2987
+ this[name] = function () {
2988
+ //ensure init
2989
+ if (definition.dialog && typeof definition.dialog.__init === 'function') {
2990
+ definition.dialog.__init(definition.dialog);
2991
+ }
2992
+ //if passed with no params, consider it a get request
2993
+ if (arguments.length === 0) {
2994
+ return definition.dialog;
2995
+ } else {
2996
+ var dialog = definition.dialog;
2997
+ dialog['main'].apply(definition.dialog, arguments);
2998
+ return dialog['show'].apply(definition.dialog);
2999
+ }
3000
+ };
3001
+ }
3002
+ },
3003
+ /**
3004
+ * Close all open dialogs.
3005
+ *
3006
+ * @param {Object} excpet [optional] The dialog object to exclude from closing.
3007
+ *
3008
+ * @return {undefined}
3009
+ */
3010
+ closeAll: function (except) {
3011
+ var clone = openDialogs.slice(0);
3012
+ for (var x = 0; x < clone.length; x += 1) {
3013
+ var instance = clone[x];
3014
+ if (except === undefined || except !== instance) {
3015
+ instance.close();
3016
+ }
3017
+ }
3018
+ },
3019
+ /**
3020
+ * Gets or Sets dialog settings/options. if the dialog is transient, this call does nothing.
3021
+ *
3022
+ * @param {string} name The dialog name.
3023
+ * @param {String|Object} key A string specifying a propery name or a collection of key/value pairs.
3024
+ * @param {Variant} value Optional, the value associated with the key (in case it was a string).
3025
+ *
3026
+ * @return {undefined}
3027
+ */
3028
+ setting: function (name, key, value) {
3029
+
3030
+ if (name === 'notifier') {
3031
+ return notifier.setting(key, value);
3032
+ }
3033
+
3034
+ var dialog = get_dialog(name);
3035
+ if (dialog) {
3036
+ return dialog.setting(key, value);
3037
+ }
3038
+ },
3039
+ /**
3040
+ * [Alias] Sets dialog settings/options
3041
+ */
3042
+ set: function(name,key,value){
3043
+ return this.setting(name, key,value);
3044
+ },
3045
+ /**
3046
+ * [Alias] Gets dialog settings/options
3047
+ */
3048
+ get: function(name, key){
3049
+ return this.setting(name, key);
3050
+ },
3051
+ /**
3052
+ * Creates a new notification message.
3053
+ * If a type is passed, a class name "ajs-{type}" will be added.
3054
+ * This allows for custom look and feel for various types of notifications.
3055
+ *
3056
+ * @param {String | DOMElement} [message=undefined] Message text
3057
+ * @param {String} [type=''] Type of log message
3058
+ * @param {String} [wait=''] Time (in seconds) to wait before auto-close
3059
+ * @param {Function} [callback=undefined] A callback function to be invoked when the log is closed.
3060
+ *
3061
+ * @return {Object} Notification object.
3062
+ */
3063
+ notify: function (message, type, wait, callback) {
3064
+ return notifier.create(type, callback).push(message, wait);
3065
+ },
3066
+ /**
3067
+ * Creates a new notification message.
3068
+ *
3069
+ * @param {String} [message=undefined] Message text
3070
+ * @param {String} [wait=''] Time (in seconds) to wait before auto-close
3071
+ * @param {Function} [callback=undefined] A callback function to be invoked when the log is closed.
3072
+ *
3073
+ * @return {Object} Notification object.
3074
+ */
3075
+ message: function (message, wait, callback) {
3076
+ return notifier.create(null, callback).push(message, wait);
3077
+ },
3078
+ /**
3079
+ * Creates a new notification message of type 'success'.
3080
+ *
3081
+ * @param {String} [message=undefined] Message text
3082
+ * @param {String} [wait=''] Time (in seconds) to wait before auto-close
3083
+ * @param {Function} [callback=undefined] A callback function to be invoked when the log is closed.
3084
+ *
3085
+ * @return {Object} Notification object.
3086
+ */
3087
+ success: function (message, wait, callback) {
3088
+ return notifier.create('success', callback).push(message, wait);
3089
+ },
3090
+ /**
3091
+ * Creates a new notification message of type 'error'.
3092
+ *
3093
+ * @param {String} [message=undefined] Message text
3094
+ * @param {String} [wait=''] Time (in seconds) to wait before auto-close
3095
+ * @param {Function} [callback=undefined] A callback function to be invoked when the log is closed.
3096
+ *
3097
+ * @return {Object} Notification object.
3098
+ */
3099
+ error: function (message, wait, callback) {
3100
+ return notifier.create('error', callback).push(message, wait);
3101
+ },
3102
+ /**
3103
+ * Creates a new notification message of type 'warning'.
3104
+ *
3105
+ * @param {String} [message=undefined] Message text
3106
+ * @param {String} [wait=''] Time (in seconds) to wait before auto-close
3107
+ * @param {Function} [callback=undefined] A callback function to be invoked when the log is closed.
3108
+ *
3109
+ * @return {Object} Notification object.
3110
+ */
3111
+ warning: function (message, wait, callback) {
3112
+ return notifier.create('warning', callback).push(message, wait);
3113
+ },
3114
+ /**
3115
+ * Dismisses all open notifications
3116
+ *
3117
+ * @return {undefined}
3118
+ */
3119
+ dismissAll: function () {
3120
+ notifier.dismissAll();
3121
+ }
3122
+ };
3123
+ }
3124
+ var alertify = new Alertify();
3125
+
3126
+ /**
3127
+ * Alert dialog definition
3128
+ *
3129
+ * invoked by:
3130
+ * alertify.alert(message);
3131
+ * alertify.alert(title, message);
3132
+ * alertify.alert(message, onok);
3133
+ * alertify.alert(title, message, onok);
3134
+ */
3135
+ alertify.dialog('alert', function () {
3136
+ return {
3137
+ main: function (_title, _message, _onok) {
3138
+ var title, message, onok;
3139
+ switch (arguments.length) {
3140
+ case 1:
3141
+ message = _title;
3142
+ break;
3143
+ case 2:
3144
+ if (typeof _message === 'function') {
3145
+ message = _title;
3146
+ onok = _message;
3147
+ } else {
3148
+ title = _title;
3149
+ message = _message;
3150
+ }
3151
+ break;
3152
+ case 3:
3153
+ title = _title;
3154
+ message = _message;
3155
+ onok = _onok;
3156
+ break;
3157
+ }
3158
+ this.set('title', title);
3159
+ this.set('message', message);
3160
+ this.set('onok', onok);
3161
+ return this;
3162
+ },
3163
+ setup: function () {
3164
+ return {
3165
+ buttons: [
3166
+ {
3167
+ text: alertify.defaults.glossary.ok,
3168
+ key: keys.ESC,
3169
+ invokeOnClose: true,
3170
+ className: alertify.defaults.theme.ok,
3171
+ }
3172
+ ],
3173
+ focus: {
3174
+ element: 0,
3175
+ select: false
3176
+ },
3177
+ options: {
3178
+ maximizable: false,
3179
+ resizable: false
3180
+ }
3181
+ };
3182
+ },
3183
+ build: function () {
3184
+ // nothing
3185
+ },
3186
+ prepare: function () {
3187
+ //nothing
3188
+ },
3189
+ setMessage: function (message) {
3190
+ this.setContent(message);
3191
+ },
3192
+ settings: {
3193
+ message: undefined,
3194
+ onok: undefined,
3195
+ label: undefined,
3196
+ },
3197
+ settingUpdated: function (key, oldValue, newValue) {
3198
+ switch (key) {
3199
+ case 'message':
3200
+ this.setMessage(newValue);
3201
+ break;
3202
+ case 'label':
3203
+ if (this.__internal.buttons[0].element) {
3204
+ this.__internal.buttons[0].element.innerHTML = newValue;
3205
+ }
3206
+ break;
3207
+ }
3208
+ },
3209
+ callback: function (closeEvent) {
3210
+ if (typeof this.get('onok') === 'function') {
3211
+ var returnValue = this.get('onok').call(this, closeEvent);
3212
+ if (typeof returnValue !== 'undefined') {
3213
+ closeEvent.cancel = !returnValue;
3214
+ }
3215
+ }
3216
+ }
3217
+ };
3218
+ });
3219
+ /**
3220
+ * Confirm dialog object
3221
+ *
3222
+ * alertify.confirm(message);
3223
+ * alertify.confirm(message, onok);
3224
+ * alertify.confirm(message, onok, oncancel);
3225
+ * alertify.confirm(title, message, onok, oncancel);
3226
+ */
3227
+ alertify.dialog('confirm', function () {
3228
+
3229
+ var autoConfirm = {
3230
+ timer: null,
3231
+ index: null,
3232
+ text: null,
3233
+ duration: null,
3234
+ task: function (event, self) {
3235
+ if (self.isOpen()) {
3236
+ self.__internal.buttons[autoConfirm.index].element.innerHTML = autoConfirm.text + ' (&#8207;' + autoConfirm.duration + '&#8207;) ';
3237
+ autoConfirm.duration -= 1;
3238
+ if (autoConfirm.duration === -1) {
3239
+ clearAutoConfirm(self);
3240
+ var button = self.__internal.buttons[autoConfirm.index];
3241
+ var closeEvent = createCloseEvent(autoConfirm.index, button);
3242
+
3243
+ if (typeof self.callback === 'function') {
3244
+ self.callback.apply(self, [closeEvent]);
3245
+ }
3246
+ //close the dialog.
3247
+ if (closeEvent.close !== false) {
3248
+ self.close();
3249
+ }
3250
+ }
3251
+ } else {
3252
+ clearAutoConfirm(self);
3253
+ }
3254
+ }
3255
+ };
3256
+
3257
+ function clearAutoConfirm(self) {
3258
+ if (autoConfirm.timer !== null) {
3259
+ clearInterval(autoConfirm.timer);
3260
+ autoConfirm.timer = null;
3261
+ self.__internal.buttons[autoConfirm.index].element.innerHTML = autoConfirm.text;
3262
+ }
3263
+ }
3264
+
3265
+ function startAutoConfirm(self, index, duration) {
3266
+ clearAutoConfirm(self);
3267
+ autoConfirm.duration = duration;
3268
+ autoConfirm.index = index;
3269
+ autoConfirm.text = self.__internal.buttons[index].element.innerHTML;
3270
+ autoConfirm.timer = setInterval(delegate(self, autoConfirm.task), 1000);
3271
+ autoConfirm.task(null, self);
3272
+ }
3273
+
3274
+
3275
+ return {
3276
+ main: function (_title, _message, _onok, _oncancel) {
3277
+ var title, message, onok, oncancel;
3278
+ switch (arguments.length) {
3279
+ case 1:
3280
+ message = _title;
3281
+ break;
3282
+ case 2:
3283
+ message = _title;
3284
+ onok = _message;
3285
+ break;
3286
+ case 3:
3287
+ message = _title;
3288
+ onok = _message;
3289
+ oncancel = _onok;
3290
+ break;
3291
+ case 4:
3292
+ title = _title;
3293
+ message = _message;
3294
+ onok = _onok;
3295
+ oncancel = _oncancel;
3296
+ break;
3297
+ }
3298
+ this.set('title', title);
3299
+ this.set('message', message);
3300
+ this.set('onok', onok);
3301
+ this.set('oncancel', oncancel);
3302
+ return this;
3303
+ },
3304
+ setup: function () {
3305
+ return {
3306
+ buttons: [
3307
+ {
3308
+ text: alertify.defaults.glossary.ok,
3309
+ key: keys.ENTER,
3310
+ className: alertify.defaults.theme.ok,
3311
+ },
3312
+ {
3313
+ text: alertify.defaults.glossary.cancel,
3314
+ key: keys.ESC,
3315
+ invokeOnClose: true,
3316
+ className: alertify.defaults.theme.cancel,
3317
+ }
3318
+ ],
3319
+ focus: {
3320
+ element: 0,
3321
+ select: false
3322
+ },
3323
+ options: {
3324
+ maximizable: false,
3325
+ resizable: false
3326
+ }
3327
+ };
3328
+ },
3329
+ build: function () {
3330
+ //nothing
3331
+ },
3332
+ prepare: function () {
3333
+ //nothing
3334
+ },
3335
+ setMessage: function (message) {
3336
+ this.setContent(message);
3337
+ },
3338
+ settings: {
3339
+ message: null,
3340
+ labels: null,
3341
+ onok: null,
3342
+ oncancel: null,
3343
+ defaultFocus: null,
3344
+ reverseButtons: null,
3345
+ },
3346
+ settingUpdated: function (key, oldValue, newValue) {
3347
+ switch (key) {
3348
+ case 'message':
3349
+ this.setMessage(newValue);
3350
+ break;
3351
+ case 'labels':
3352
+ if ('ok' in newValue && this.__internal.buttons[0].element) {
3353
+ this.__internal.buttons[0].text = newValue.ok;
3354
+ this.__internal.buttons[0].element.innerHTML = newValue.ok;
3355
+ }
3356
+ if ('cancel' in newValue && this.__internal.buttons[1].element) {
3357
+ this.__internal.buttons[1].text = newValue.cancel;
3358
+ this.__internal.buttons[1].element.innerHTML = newValue.cancel;
3359
+ }
3360
+ break;
3361
+ case 'reverseButtons':
3362
+ if (newValue === true) {
3363
+ this.elements.buttons.primary.appendChild(this.__internal.buttons[0].element);
3364
+ } else {
3365
+ this.elements.buttons.primary.appendChild(this.__internal.buttons[1].element);
3366
+ }
3367
+ break;
3368
+ case 'defaultFocus':
3369
+ this.__internal.focus.element = newValue === 'ok' ? 0 : 1;
3370
+ break;
3371
+ }
3372
+ },
3373
+ callback: function (closeEvent) {
3374
+ clearAutoConfirm(this);
3375
+ var returnValue;
3376
+ switch (closeEvent.index) {
3377
+ case 0:
3378
+ if (typeof this.get('onok') === 'function') {
3379
+ returnValue = this.get('onok').call(this, closeEvent);
3380
+ if (typeof returnValue !== 'undefined') {
3381
+ closeEvent.cancel = !returnValue;
3382
+ }
3383
+ }
3384
+ break;
3385
+ case 1:
3386
+ if (typeof this.get('oncancel') === 'function') {
3387
+ returnValue = this.get('oncancel').call(this, closeEvent);
3388
+ if (typeof returnValue !== 'undefined') {
3389
+ closeEvent.cancel = !returnValue;
3390
+ }
3391
+ }
3392
+ break;
3393
+ }
3394
+ },
3395
+ autoOk: function (duration) {
3396
+ startAutoConfirm(this, 0, duration);
3397
+ return this;
3398
+ },
3399
+ autoCancel: function (duration) {
3400
+ startAutoConfirm(this, 1, duration);
3401
+ return this;
3402
+ }
3403
+ };
3404
+ });
3405
+ /**
3406
+ * Prompt dialog object
3407
+ *
3408
+ * invoked by:
3409
+ * alertify.prompt(message);
3410
+ * alertify.prompt(message, value);
3411
+ * alertify.prompt(message, value, onok);
3412
+ * alertify.prompt(message, value, onok, oncancel);
3413
+ * alertify.prompt(title, message, value, onok, oncancel);
3414
+ */
3415
+ alertify.dialog('prompt', function () {
3416
+ var input = document.createElement('INPUT');
3417
+ var p = document.createElement('P');
3418
+ return {
3419
+ main: function (_title, _message, _value, _onok, _oncancel) {
3420
+ var title, message, value, onok, oncancel;
3421
+ switch (arguments.length) {
3422
+ case 1:
3423
+ message = _title;
3424
+ break;
3425
+ case 2:
3426
+ message = _title;
3427
+ value = _message;
3428
+ break;
3429
+ case 3:
3430
+ message = _title;
3431
+ value = _message;
3432
+ onok = _value;
3433
+ break;
3434
+ case 4:
3435
+ message = _title;
3436
+ value = _message;
3437
+ onok = _value;
3438
+ oncancel = _onok;
3439
+ break;
3440
+ case 5:
3441
+ title = _title;
3442
+ message = _message;
3443
+ value = _value;
3444
+ onok = _onok;
3445
+ oncancel = _oncancel;
3446
+ break;
3447
+ }
3448
+ this.set('title', title);
3449
+ this.set('message', message);
3450
+ this.set('value', value);
3451
+ this.set('onok', onok);
3452
+ this.set('oncancel', oncancel);
3453
+ return this;
3454
+ },
3455
+ setup: function () {
3456
+ return {
3457
+ buttons: [
3458
+ {
3459
+ text: alertify.defaults.glossary.ok,
3460
+ key: keys.ENTER,
3461
+ className: alertify.defaults.theme.ok,
3462
+ },
3463
+ {
3464
+ text: alertify.defaults.glossary.cancel,
3465
+ key: keys.ESC,
3466
+ invokeOnClose: true,
3467
+ className: alertify.defaults.theme.cancel,
3468
+ }
3469
+ ],
3470
+ focus: {
3471
+ element: input,
3472
+ select: true
3473
+ },
3474
+ options: {
3475
+ maximizable: false,
3476
+ resizable: false
3477
+ }
3478
+ };
3479
+ },
3480
+ build: function () {
3481
+ input.className = alertify.defaults.theme.input;
3482
+ input.setAttribute('type', 'text');
3483
+ input.value = this.get('value');
3484
+ this.elements.content.appendChild(p);
3485
+ this.elements.content.appendChild(input);
3486
+ },
3487
+ prepare: function () {
3488
+ //nothing
3489
+ },
3490
+ setMessage: function (message) {
3491
+ if (typeof message === 'string') {
3492
+ clearContents(p);
3493
+ p.innerHTML = message;
3494
+ } else if (message instanceof window.HTMLElement && p.firstChild !== message) {
3495
+ clearContents(p);
3496
+ p.appendChild(message);
3497
+ }
3498
+ },
3499
+ settings: {
3500
+ message: undefined,
3501
+ labels: undefined,
3502
+ onok: undefined,
3503
+ oncancel: undefined,
3504
+ value: '',
3505
+ type:'text',
3506
+ reverseButtons: undefined,
3507
+ },
3508
+ settingUpdated: function (key, oldValue, newValue) {
3509
+ switch (key) {
3510
+ case 'message':
3511
+ this.setMessage(newValue);
3512
+ break;
3513
+ case 'value':
3514
+ input.value = newValue;
3515
+ break;
3516
+ case 'type':
3517
+ switch (newValue) {
3518
+ case 'text':
3519
+ case 'color':
3520
+ case 'date':
3521
+ case 'datetime-local':
3522
+ case 'email':
3523
+ case 'month':
3524
+ case 'number':
3525
+ case 'password':
3526
+ case 'search':
3527
+ case 'tel':
3528
+ case 'time':
3529
+ case 'week':
3530
+ input.type = newValue;
3531
+ break;
3532
+ default:
3533
+ input.type = 'text';
3534
+ break;
3535
+ }
3536
+ break;
3537
+ case 'labels':
3538
+ if (newValue.ok && this.__internal.buttons[0].element) {
3539
+ this.__internal.buttons[0].element.innerHTML = newValue.ok;
3540
+ }
3541
+ if (newValue.cancel && this.__internal.buttons[1].element) {
3542
+ this.__internal.buttons[1].element.innerHTML = newValue.cancel;
3543
+ }
3544
+ break;
3545
+ case 'reverseButtons':
3546
+ if (newValue === true) {
3547
+ this.elements.buttons.primary.appendChild(this.__internal.buttons[0].element);
3548
+ } else {
3549
+ this.elements.buttons.primary.appendChild(this.__internal.buttons[1].element);
3550
+ }
3551
+ break;
3552
+ }
3553
+ },
3554
+ callback: function (closeEvent) {
3555
+ var returnValue;
3556
+ switch (closeEvent.index) {
3557
+ case 0:
3558
+ this.settings.value = input.value;
3559
+ if (typeof this.get('onok') === 'function') {
3560
+ returnValue = this.get('onok').call(this, closeEvent, this.settings.value);
3561
+ if (typeof returnValue !== 'undefined') {
3562
+ closeEvent.cancel = !returnValue;
3563
+ }
3564
+ }
3565
+ break;
3566
+ case 1:
3567
+ if (typeof this.get('oncancel') === 'function') {
3568
+ returnValue = this.get('oncancel').call(this, closeEvent);
3569
+ if (typeof returnValue !== 'undefined') {
3570
+ closeEvent.cancel = !returnValue;
3571
+ }
3572
+ }
3573
+ if(!closeEvent.cancel){
3574
+ input.value = this.settings.value;
3575
+ }
3576
+ break;
3577
+ }
3578
+ }
3579
+ };
3580
+ });
3581
+
3582
+ // CommonJS
3583
+ if ( typeof module === 'object' && typeof module.exports === 'object' ) {
3584
+ module.exports = alertify;
3585
+ // AMD
3586
+ } else if ( typeof define === 'function' && define.amd) {
3587
+ define( [], function () {
3588
+ return alertify;
3589
+ } );
3590
+ // window
3591
+ } else if ( !window.alertify ) {
3592
+ window.alertify = alertify;
3593
+ }
3594
+
3595
+ } ( typeof window !== 'undefined' ? window : this ) );
scripts/alertify/alertify.min.js ADDED
@@ -0,0 +1,3 @@
 
 
 
1
+ /*! alertifyjs - v1.11.0 - Mohammad Younes <Mohammad@alertifyjs.com> (http://alertifyjs.com) */
2
+ !function(a){"use strict";function b(a,b){a.className+=" "+b}function c(a,b){for(var c=a.className.split(" "),d=b.split(" "),e=0;e<d.length;e+=1){var f=c.indexOf(d[e]);f>-1&&c.splice(f,1)}a.className=c.join(" ")}function d(){return"rtl"===a.getComputedStyle(document.body).direction}function e(){return document.documentElement&&document.documentElement.scrollTop||document.body.scrollTop}function f(){return document.documentElement&&document.documentElement.scrollLeft||document.body.scrollLeft}function g(a){for(;a.lastChild;)a.removeChild(a.lastChild)}function h(a){if(null===a)return a;var b;if(Array.isArray(a)){b=[];for(var c=0;c<a.length;c+=1)b.push(h(a[c]));return b}if(a instanceof Date)return new Date(a.getTime());if(a instanceof RegExp)return b=new RegExp(a.source),b.global=a.global,b.ignoreCase=a.ignoreCase,b.multiline=a.multiline,b.lastIndex=a.lastIndex,b;if("object"==typeof a){b={};for(var d in a)a.hasOwnProperty(d)&&(b[d]=h(a[d]));return b}return a}function i(a,b){var c=a.elements.root;c.parentNode.removeChild(c),delete a.elements,a.settings=h(a.__settings),a.__init=b,delete a.__internal}function j(a,b){return function(){if(arguments.length>0){for(var c=[],d=0;d<arguments.length;d+=1)c.push(arguments[d]);return c.push(a),b.apply(a,c)}return b.apply(a,[null,a])}}function k(a,b){return{index:a,button:b,cancel:!1}}function l(a,b){if("function"==typeof b.get(a))return b.get(a).call(b)}function m(){function a(a,b){for(var c in b)b.hasOwnProperty(c)&&(a[c]=b[c]);return a}function b(a){var b=d[a].dialog;return b&&"function"==typeof b.__init&&b.__init(b),b}function c(b,c,e,f){var g={dialog:null,factory:c};return void 0!==f&&(g.factory=function(){return a(new d[f].factory,new c)}),e||(g.dialog=a(new g.factory,t)),d[b]=g}var d={};return{defaults:o,dialog:function(d,e,f,g){if("function"!=typeof e)return b(d);if(this.hasOwnProperty(d))throw new Error("alertify.dialog: name already exists");var h=c(d,e,f,g);this[d]=f?function(){if(0===arguments.length)return h.dialog;var b=a(new h.factory,t);return b&&"function"==typeof b.__init&&b.__init(b),b.main.apply(b,arguments),b.show.apply(b)}:function(){if(h.dialog&&"function"==typeof h.dialog.__init&&h.dialog.__init(h.dialog),0===arguments.length)return h.dialog;var a=h.dialog;return a.main.apply(h.dialog,arguments),a.show.apply(h.dialog)}},closeAll:function(a){for(var b=p.slice(0),c=0;c<b.length;c+=1){var d=b[c];void 0!==a&&a===d||d.close()}},setting:function(a,c,d){if("notifier"===a)return u.setting(c,d);var e=b(a);return e?e.setting(c,d):void 0},set:function(a,b,c){return this.setting(a,b,c)},get:function(a,b){return this.setting(a,b)},notify:function(a,b,c,d){return u.create(b,d).push(a,c)},message:function(a,b,c){return u.create(null,c).push(a,b)},success:function(a,b,c){return u.create("success",c).push(a,b)},error:function(a,b,c){return u.create("error",c).push(a,b)},warning:function(a,b,c){return u.create("warning",c).push(a,b)},dismissAll:function(){u.dismissAll()}}}var n={ENTER:13,ESC:27,F1:112,F12:123,LEFT:37,RIGHT:39},o={autoReset:!0,basic:!1,closable:!0,closableByDimmer:!0,frameless:!1,maintainFocus:!0,maximizable:!0,modal:!0,movable:!0,moveBounded:!1,overflow:!0,padding:!0,pinnable:!0,pinned:!0,preventBodyShift:!1,resizable:!0,startMaximized:!1,transition:"pulse",notifier:{delay:5,position:"bottom-right",closeButton:!1},glossary:{title:"AlertifyJS",ok:"OK",cancel:"Cancel",acccpt:"Accept",deny:"Deny",confirm:"Confirm",decline:"Decline",close:"Close",maximize:"Maximize",restore:"Restore"},theme:{input:"ajs-input",ok:"ajs-ok",cancel:"ajs-cancel"}},p=[],q=function(){return document.addEventListener?function(a,b,c,d){a.addEventListener(b,c,!0===d)}:document.attachEvent?function(a,b,c){a.attachEvent("on"+b,c)}:void 0}(),r=function(){return document.removeEventListener?function(a,b,c,d){a.removeEventListener(b,c,!0===d)}:document.detachEvent?function(a,b,c){a.detachEvent("on"+b,c)}:void 0}(),s=function(){var a,b,c=!1,d={animation:"animationend",OAnimation:"oAnimationEnd oanimationend",msAnimation:"MSAnimationEnd",MozAnimation:"animationend",WebkitAnimation:"webkitAnimationEnd"};for(a in d)if(void 0!==document.documentElement.style[a]){b=d[a],c=!0;break}return{type:b,supported:c}}(),t=function(){function m(a){if(!a.__internal){delete a.__init,a.__settings||(a.__settings=h(a.settings)),null===za&&document.body.setAttribute("tabindex","0");var c;"function"==typeof a.setup?(c=a.setup(),c.options=c.options||{},c.focus=c.focus||{}):c={buttons:[],focus:{element:null,select:!1},options:{}},"object"!=typeof a.hooks&&(a.hooks={});var d=[];if(Array.isArray(c.buttons))for(var e=0;e<c.buttons.length;e+=1){var f=c.buttons[e],g={};for(var i in f)f.hasOwnProperty(i)&&(g[i]=f[i]);d.push(g)}var k=a.__internal={isOpen:!1,activeElement:document.body,timerIn:void 0,timerOut:void 0,buttons:d,focus:c.focus,options:{title:void 0,modal:void 0,basic:void 0,frameless:void 0,pinned:void 0,movable:void 0,moveBounded:void 0,resizable:void 0,autoReset:void 0,closable:void 0,closableByDimmer:void 0,maximizable:void 0,startMaximized:void 0,pinnable:void 0,transition:void 0,padding:void 0,overflow:void 0,onshow:void 0,onclosing:void 0,onclose:void 0,onfocus:void 0,onmove:void 0,onmoved:void 0,onresize:void 0,onresized:void 0,onmaximize:void 0,onmaximized:void 0,onrestore:void 0,onrestored:void 0},resetHandler:void 0,beginMoveHandler:void 0,beginResizeHandler:void 0,bringToFrontHandler:void 0,modalClickHandler:void 0,buttonsClickHandler:void 0,commandsClickHandler:void 0,transitionInHandler:void 0,transitionOutHandler:void 0,destroy:void 0},l={};l.root=document.createElement("div"),l.root.className=Ca.base+" "+Ca.hidden+" ",l.root.innerHTML=Ba.dimmer+Ba.modal,l.dimmer=l.root.firstChild,l.modal=l.root.lastChild,l.modal.innerHTML=Ba.dialog,l.dialog=l.modal.firstChild,l.dialog.innerHTML=Ba.reset+Ba.commands+Ba.header+Ba.body+Ba.footer+Ba.resizeHandle+Ba.reset,l.reset=[],l.reset.push(l.dialog.firstChild),l.reset.push(l.dialog.lastChild),l.commands={},l.commands.container=l.reset[0].nextSibling,l.commands.pin=l.commands.container.firstChild,l.commands.maximize=l.commands.pin.nextSibling,l.commands.close=l.commands.maximize.nextSibling,l.header=l.commands.container.nextSibling,l.body=l.header.nextSibling,l.body.innerHTML=Ba.content,l.content=l.body.firstChild,l.footer=l.body.nextSibling,l.footer.innerHTML=Ba.buttons.auxiliary+Ba.buttons.primary,l.resizeHandle=l.footer.nextSibling,l.buttons={},l.buttons.auxiliary=l.footer.firstChild,l.buttons.primary=l.buttons.auxiliary.nextSibling,l.buttons.primary.innerHTML=Ba.button,l.buttonTemplate=l.buttons.primary.firstChild,l.buttons.primary.removeChild(l.buttonTemplate);for(var m=0;m<a.__internal.buttons.length;m+=1){var n=a.__internal.buttons[m];ya.indexOf(n.key)<0&&ya.push(n.key),n.element=l.buttonTemplate.cloneNode(),n.element.innerHTML=n.text,"string"==typeof n.className&&""!==n.className&&b(n.element,n.className);for(var o in n.attrs)"className"!==o&&n.attrs.hasOwnProperty(o)&&n.element.setAttribute(o,n.attrs[o]);"auxiliary"===n.scope?l.buttons.auxiliary.appendChild(n.element):l.buttons.primary.appendChild(n.element)}a.elements=l,k.resetHandler=j(a,X),k.beginMoveHandler=j(a,aa),k.beginResizeHandler=j(a,ga),k.bringToFrontHandler=j(a,B),k.modalClickHandler=j(a,R),k.buttonsClickHandler=j(a,T),k.commandsClickHandler=j(a,F),k.transitionInHandler=j(a,Y),k.transitionOutHandler=j(a,Z);for(var p in k.options)void 0!==c.options[p]?a.set(p,c.options[p]):v.defaults.hasOwnProperty(p)?a.set(p,v.defaults[p]):"title"===p&&a.set(p,v.defaults.glossary[p]);"function"==typeof a.build&&a.build()}document.body.appendChild(a.elements.root)}function o(){wa=f(),xa=e()}function t(){a.scrollTo(wa,xa)}function u(){for(var a=0,d=0;d<p.length;d+=1){var e=p[d];(e.isModal()||e.isMaximized())&&(a+=1)}0===a&&document.body.className.indexOf(Ca.noOverflow)>=0?(c(document.body,Ca.noOverflow),w(!1)):a>0&&document.body.className.indexOf(Ca.noOverflow)<0&&(w(!0),b(document.body,Ca.noOverflow))}function w(d){v.defaults.preventBodyShift&&document.documentElement.scrollHeight>document.documentElement.clientHeight&&(d?(Ea=xa,Da=a.getComputedStyle(document.body).top,b(document.body,Ca.fixed),document.body.style.top=-xa+"px"):(xa=Ea,document.body.style.top=Da,c(document.body,Ca.fixed),t()))}function x(a,d,e){"string"==typeof e&&c(a.elements.root,Ca.prefix+e),b(a.elements.root,Ca.prefix+d),za=a.elements.root.offsetWidth}function y(a){a.get("modal")?(c(a.elements.root,Ca.modeless),a.isOpen()&&(pa(a),N(a),u())):(b(a.elements.root,Ca.modeless),a.isOpen()&&(oa(a),N(a),u()))}function z(a){a.get("basic")?b(a.elements.root,Ca.basic):c(a.elements.root,Ca.basic)}function A(a){a.get("frameless")?b(a.elements.root,Ca.frameless):c(a.elements.root,Ca.frameless)}function B(a,b){for(var c=p.indexOf(b),d=c+1;d<p.length;d+=1)if(p[d].isModal())return;return document.body.lastChild!==b.elements.root&&(document.body.appendChild(b.elements.root),p.splice(p.indexOf(b),1),p.push(b),W(b)),!1}function C(a,d,e,f){switch(d){case"title":a.setHeader(f);break;case"modal":y(a);break;case"basic":z(a);break;case"frameless":A(a);break;case"pinned":O(a);break;case"closable":Q(a);break;case"maximizable":P(a);break;case"pinnable":K(a);break;case"movable":ea(a);break;case"resizable":ka(a);break;case"transition":x(a,f,e);break;case"padding":f?c(a.elements.root,Ca.noPadding):a.elements.root.className.indexOf(Ca.noPadding)<0&&b(a.elements.root,Ca.noPadding);break;case"overflow":f?c(a.elements.root,Ca.noOverflow):a.elements.root.className.indexOf(Ca.noOverflow)<0&&b(a.elements.root,Ca.noOverflow);break;case"transition":x(a,f,e)}"function"==typeof a.hooks.onupdate&&a.hooks.onupdate.call(a,d,e,f)}function D(a,b,c,d,e){var f={op:void 0,items:[]};if(void 0===e&&"string"==typeof d)f.op="get",b.hasOwnProperty(d)?(f.found=!0,f.value=b[d]):(f.found=!1,f.value=void 0);else{var g;if(f.op="set","object"==typeof d){var h=d;for(var i in h)b.hasOwnProperty(i)?(b[i]!==h[i]&&(g=b[i],b[i]=h[i],c.call(a,i,g,h[i])),f.items.push({key:i,value:h[i],found:!0})):f.items.push({key:i,value:h[i],found:!1})}else{if("string"!=typeof d)throw new Error("args must be a string or object");b.hasOwnProperty(d)?(b[d]!==e&&(g=b[d],b[d]=e,c.call(a,d,g,e)),f.items.push({key:d,value:e,found:!0})):f.items.push({key:d,value:e,found:!1})}}return f}function E(a){var b;S(a,function(a){return b=!0===a.invokeOnClose}),!b&&a.isOpen()&&a.close()}function F(a,b){switch(a.srcElement||a.target){case b.elements.commands.pin:b.isPinned()?H(b):G(b);break;case b.elements.commands.maximize:b.isMaximized()?J(b):I(b);break;case b.elements.commands.close:E(b)}return!1}function G(a){a.set("pinned",!0)}function H(a){a.set("pinned",!1)}function I(a){l("onmaximize",a),b(a.elements.root,Ca.maximized),a.isOpen()&&u(),l("onmaximized",a)}function J(a){l("onrestore",a),c(a.elements.root,Ca.maximized),a.isOpen()&&u(),l("onrestored",a)}function K(a){a.get("pinnable")?b(a.elements.root,Ca.pinnable):c(a.elements.root,Ca.pinnable)}function L(a){var b=f();a.elements.modal.style.marginTop=e()+"px",a.elements.modal.style.marginLeft=b+"px",a.elements.modal.style.marginRight=-b+"px"}function M(a){var b=parseInt(a.elements.modal.style.marginTop,10),c=parseInt(a.elements.modal.style.marginLeft,10);if(a.elements.modal.style.marginTop="",a.elements.modal.style.marginLeft="",a.elements.modal.style.marginRight="",a.isOpen()){var d=0,g=0;""!==a.elements.dialog.style.top&&(d=parseInt(a.elements.dialog.style.top,10)),a.elements.dialog.style.top=d+(b-e())+"px",""!==a.elements.dialog.style.left&&(g=parseInt(a.elements.dialog.style.left,10)),a.elements.dialog.style.left=g+(c-f())+"px"}}function N(a){a.get("modal")||a.get("pinned")?M(a):L(a)}function O(a){a.get("pinned")?(c(a.elements.root,Ca.unpinned),a.isOpen()&&M(a)):(b(a.elements.root,Ca.unpinned),a.isOpen()&&!a.isModal()&&L(a))}function P(a){a.get("maximizable")?b(a.elements.root,Ca.maximizable):c(a.elements.root,Ca.maximizable)}function Q(a){a.get("closable")?(b(a.elements.root,Ca.closable),ua(a)):(c(a.elements.root,Ca.closable),va(a))}function R(a,b){var c=a.srcElement||a.target;return Fa||c!==b.elements.modal||!0!==b.get("closableByDimmer")||E(b),Fa=!1,!1}function S(a,b){for(var c=0;c<a.__internal.buttons.length;c+=1){var d=a.__internal.buttons[c];if(!d.element.disabled&&b(d)){var e=k(c,d);"function"==typeof a.callback&&a.callback.apply(a,[e]),!1===e.cancel&&a.close();break}}}function T(a,b){var c=a.srcElement||a.target;S(b,function(a){return a.element===c&&(Ga=!0)})}function U(a){if(Ga)return void(Ga=!1);var b=p[p.length-1],c=a.keyCode;return 0===b.__internal.buttons.length&&c===n.ESC&&!0===b.get("closable")?(E(b),!1):ya.indexOf(c)>-1?(S(b,function(a){return a.key===c}),!1):void 0}function V(a){var b=p[p.length-1],c=a.keyCode;if(c===n.LEFT||c===n.RIGHT){for(var d=b.__internal.buttons,e=0;e<d.length;e+=1)if(document.activeElement===d[e].element)switch(c){case n.LEFT:return void d[(e||d.length)-1].element.focus();case n.RIGHT:return void d[(e+1)%d.length].element.focus()}}else if(c<n.F12+1&&c>n.F1-1&&ya.indexOf(c)>-1)return a.preventDefault(),a.stopPropagation(),S(b,function(a){return a.key===c}),!1}function W(a,b){if(b)b.focus();else{var c=a.__internal.focus,d=c.element;switch(typeof c.element){case"number":a.__internal.buttons.length>c.element&&(d=!0===a.get("basic")?a.elements.reset[0]:a.__internal.buttons[c.element].element);break;case"string":d=a.elements.body.querySelector(c.element);break;case"function":d=c.element.call(a)}void 0!==d&&null!==d||0!==a.__internal.buttons.length||(d=a.elements.reset[0]),d&&d.focus&&(d.focus(),c.select&&d.select&&d.select())}}function X(a,b){if(!b)for(var c=p.length-1;c>-1;c-=1)if(p[c].isModal()){b=p[c];break}if(b&&b.isModal()){var d,e=a.srcElement||a.target,f=e===b.elements.reset[1]||0===b.__internal.buttons.length&&e===document.body;f&&(b.get("maximizable")?d=b.elements.commands.maximize:b.get("closable")&&(d=b.elements.commands.close)),void 0===d&&("number"==typeof b.__internal.focus.element?e===b.elements.reset[0]?d=b.elements.buttons.auxiliary.firstChild||b.elements.buttons.primary.firstChild:f&&(d=b.elements.reset[0]):e===b.elements.reset[0]&&(d=b.elements.buttons.primary.lastChild||b.elements.buttons.auxiliary.lastChild)),W(b,d)}}function Y(a,b){clearTimeout(b.__internal.timerIn),W(b),t(),Ga=!1,l("onfocus",b),r(b.elements.dialog,s.type,b.__internal.transitionInHandler),c(b.elements.root,Ca.animationIn)}function Z(a,b){clearTimeout(b.__internal.timerOut),r(b.elements.dialog,s.type,b.__internal.transitionOutHandler),da(b),ja(b),b.isMaximized()&&!b.get("startMaximized")&&J(b),v.defaults.maintainFocus&&b.__internal.activeElement&&(b.__internal.activeElement.focus(),b.__internal.activeElement=null),"function"==typeof b.__internal.destroy&&b.__internal.destroy.apply(b)}function $(a,b){var c=a[Ka]-Ia,d=a[La]-Ja;Na&&(d-=document.body.scrollTop),b.style.left=c+"px",b.style.top=d+"px"}function _(a,b){var c=a[Ka]-Ia,d=a[La]-Ja;Na&&(d-=document.body.scrollTop),b.style.left=Math.min(Ma.maxLeft,Math.max(Ma.minLeft,c))+"px",b.style.top=Na?Math.min(Ma.maxTop,Math.max(Ma.minTop,d))+"px":Math.max(Ma.minTop,d)+"px"}function aa(a,c){if(null===Pa&&!c.isMaximized()&&c.get("movable")){var d,e=0,f=0;if("touchstart"===a.type?(a.preventDefault(),d=a.targetTouches[0],Ka="clientX",La="clientY"):0===a.button&&(d=a),d){var g=c.elements.dialog;if(b(g,Ca.capture),g.style.left&&(e=parseInt(g.style.left,10)),g.style.top&&(f=parseInt(g.style.top,10)),Ia=d[Ka]-e,Ja=d[La]-f,c.isModal()?Ja+=c.elements.modal.scrollTop:c.isPinned()&&(Ja-=document.body.scrollTop),c.get("moveBounded")){var h=g,i=-e,j=-f;do{i+=h.offsetLeft,j+=h.offsetTop}while(h=h.offsetParent);Ma={maxLeft:i,minLeft:-i,maxTop:document.documentElement.clientHeight-g.clientHeight-j,minTop:-j},Oa=_}else Ma=null,Oa=$;return l("onmove",c),Na=!c.isModal()&&c.isPinned(),Ha=c,Oa(d,g),b(document.body,Ca.noSelection),!1}}}function ba(a){if(Ha){var b;"touchmove"===a.type?(a.preventDefault(),b=a.targetTouches[0]):0===a.button&&(b=a),b&&Oa(b,Ha.elements.dialog)}}function ca(){if(Ha){var a=Ha;Ha=Ma=null,c(document.body,Ca.noSelection),c(a.elements.dialog,Ca.capture),l("onmoved",a)}}function da(a){Ha=null;var b=a.elements.dialog;b.style.left=b.style.top=""}function ea(a){a.get("movable")?(b(a.elements.root,Ca.movable),a.isOpen()&&qa(a)):(da(a),c(a.elements.root,Ca.movable),a.isOpen()&&ra(a))}function fa(a,b,c){var e=b,f=0,g=0;do{f+=e.offsetLeft,g+=e.offsetTop}while(e=e.offsetParent);var h,i;!0===c?(h=a.pageX,i=a.pageY):(h=a.clientX,i=a.clientY);var j=d();if(j&&(h=document.body.offsetWidth-h,isNaN(Qa)||(f=document.body.offsetWidth-f-b.offsetWidth)),b.style.height=i-g+Ta+"px",b.style.width=h-f+Ta+"px",!isNaN(Qa)){var k=.5*Math.abs(b.offsetWidth-Ra);j&&(k*=-1),b.offsetWidth>Ra?b.style.left=Qa+k+"px":b.offsetWidth>=Sa&&(b.style.left=Qa-k+"px")}}function ga(a,c){if(!c.isMaximized()){var d;if("touchstart"===a.type?(a.preventDefault(),d=a.targetTouches[0]):0===a.button&&(d=a),d){l("onresize",c),Pa=c,Ta=c.elements.resizeHandle.offsetHeight/2;var e=c.elements.dialog;return b(e,Ca.capture),Qa=parseInt(e.style.left,10),e.style.height=e.offsetHeight+"px",e.style.minHeight=c.elements.header.offsetHeight+c.elements.footer.offsetHeight+"px",e.style.width=(Ra=e.offsetWidth)+"px","none"!==e.style.maxWidth&&(e.style.minWidth=(Sa=e.offsetWidth)+"px"),e.style.maxWidth="none",b(document.body,Ca.noSelection),!1}}}function ha(a){if(Pa){var b;"touchmove"===a.type?(a.preventDefault(),b=a.targetTouches[0]):0===a.button&&(b=a),b&&fa(b,Pa.elements.dialog,!Pa.get("modal")&&!Pa.get("pinned"))}}function ia(){if(Pa){var a=Pa;Pa=null,c(document.body,Ca.noSelection),c(a.elements.dialog,Ca.capture),Fa=!0,l("onresized",a)}}function ja(a){Pa=null;var b=a.elements.dialog;"none"===b.style.maxWidth&&(b.style.maxWidth=b.style.minWidth=b.style.width=b.style.height=b.style.minHeight=b.style.left="",Qa=Number.Nan,Ra=Sa=Ta=0)}function ka(a){a.get("resizable")?(b(a.elements.root,Ca.resizable),a.isOpen()&&sa(a)):(ja(a),c(a.elements.root,Ca.resizable),a.isOpen()&&ta(a))}function la(){for(var a=0;a<p.length;a+=1){var b=p[a];b.get("autoReset")&&(da(b),ja(b))}}function ma(b){1===p.length&&(q(a,"resize",la),q(document.body,"keyup",U),q(document.body,"keydown",V),q(document.body,"focus",X),q(document.documentElement,"mousemove",ba),q(document.documentElement,"touchmove",ba),q(document.documentElement,"mouseup",ca),q(document.documentElement,"touchend",ca),q(document.documentElement,"mousemove",ha),q(document.documentElement,"touchmove",ha),q(document.documentElement,"mouseup",ia),q(document.documentElement,"touchend",ia)),q(b.elements.commands.container,"click",b.__internal.commandsClickHandler),q(b.elements.footer,"click",b.__internal.buttonsClickHandler),q(b.elements.reset[0],"focus",b.__internal.resetHandler),q(b.elements.reset[1],"focus",b.__internal.resetHandler),Ga=!0,q(b.elements.dialog,s.type,b.__internal.transitionInHandler),b.get("modal")||oa(b),b.get("resizable")&&sa(b),b.get("movable")&&qa(b)}function na(b){1===p.length&&(r(a,"resize",la),r(document.body,"keyup",U),r(document.body,"keydown",V),r(document.body,"focus",X),r(document.documentElement,"mousemove",ba),r(document.documentElement,"mouseup",ca),r(document.documentElement,"mousemove",ha),r(document.documentElement,"mouseup",ia)),r(b.elements.commands.container,"click",b.__internal.commandsClickHandler),r(b.elements.footer,"click",b.__internal.buttonsClickHandler),r(b.elements.reset[0],"focus",b.__internal.resetHandler),r(b.elements.reset[1],"focus",b.__internal.resetHandler),q(b.elements.dialog,s.type,b.__internal.transitionOutHandler),b.get("modal")||pa(b),b.get("movable")&&ra(b),b.get("resizable")&&ta(b)}function oa(a){q(a.elements.dialog,"focus",a.__internal.bringToFrontHandler,!0)}function pa(a){r(a.elements.dialog,"focus",a.__internal.bringToFrontHandler,!0)}function qa(a){q(a.elements.header,"mousedown",a.__internal.beginMoveHandler),q(a.elements.header,"touchstart",a.__internal.beginMoveHandler)}function ra(a){r(a.elements.header,"mousedown",a.__internal.beginMoveHandler),r(a.elements.header,"touchstart",a.__internal.beginMoveHandler)}function sa(a){q(a.elements.resizeHandle,"mousedown",a.__internal.beginResizeHandler),q(a.elements.resizeHandle,"touchstart",a.__internal.beginResizeHandler)}function ta(a){r(a.elements.resizeHandle,"mousedown",a.__internal.beginResizeHandler),r(a.elements.resizeHandle,"touchstart",a.__internal.beginResizeHandler)}function ua(a){q(a.elements.modal,"click",a.__internal.modalClickHandler)}function va(a){r(a.elements.modal,"click",a.__internal.modalClickHandler)}var wa,xa,ya=[],za=null,Aa=a.navigator.userAgent.indexOf("Safari")>-1&&a.navigator.userAgent.indexOf("Chrome")<0,Ba={dimmer:'<div class="ajs-dimmer"></div>',modal:'<div class="ajs-modal" tabindex="0"></div>',dialog:'<div class="ajs-dialog" tabindex="0"></div>',reset:'<button class="ajs-reset"></button>',commands:'<div class="ajs-commands"><button class="ajs-pin"></button><button class="ajs-maximize"></button><button class="ajs-close"></button></div>',header:'<div class="ajs-header"></div>',body:'<div class="ajs-body"></div>',content:'<div class="ajs-content"></div>',footer:'<div class="ajs-footer"></div>',buttons:{primary:'<div class="ajs-primary ajs-buttons"></div>',auxiliary:'<div class="ajs-auxiliary ajs-buttons"></div>'},button:'<button class="ajs-button"></button>',resizeHandle:'<div class="ajs-handle"></div>'},Ca={animationIn:"ajs-in",animationOut:"ajs-out",base:"alertify",basic:"ajs-basic",capture:"ajs-capture",closable:"ajs-closable",fixed:"ajs-fixed",frameless:"ajs-frameless",hidden:"ajs-hidden",maximize:"ajs-maximize",maximized:"ajs-maximized",maximizable:"ajs-maximizable",modeless:"ajs-modeless",movable:"ajs-movable",noSelection:"ajs-no-selection",noOverflow:"ajs-no-overflow",noPadding:"ajs-no-padding",pin:"ajs-pin",pinnable:"ajs-pinnable",prefix:"ajs-",resizable:"ajs-resizable",restore:"ajs-restore",shake:"ajs-shake",unpinned:"ajs-unpinned"},Da="",Ea=0,Fa=!1,Ga=!1,Ha=null,Ia=0,Ja=0,Ka="pageX",La="pageY",Ma=null,Na=!1,Oa=null,Pa=null,Qa=Number.Nan,Ra=0,Sa=0,Ta=0;return{__init:m,isOpen:function(){return this.__internal.isOpen},isModal:function(){return this.elements.root.className.indexOf(Ca.modeless)<0},isMaximized:function(){return this.elements.root.className.indexOf(Ca.maximized)>-1},isPinned:function(){return this.elements.root.className.indexOf(Ca.unpinned)<0},maximize:function(){return this.isMaximized()||I(this),this},restore:function(){return this.isMaximized()&&J(this),this},pin:function(){return this.isPinned()||G(this),this},unpin:function(){return this.isPinned()&&H(this),this},bringToFront:function(){return B(null,this),this},moveTo:function(a,b){if(!isNaN(a)&&!isNaN(b)){l("onmove",this);var c=this.elements.dialog,e=c,f=0,g=0;c.style.left&&(f-=parseInt(c.style.left,10)),c.style.top&&(g-=parseInt(c.style.top,10));do{f+=e.offsetLeft,g+=e.offsetTop}while(e=e.offsetParent);var h=a-f,i=b-g;d()&&(h*=-1),c.style.left=h+"px",c.style.top=i+"px",l("onmoved",this)}return this},resizeTo:function(a,b){var c=parseFloat(a),d=parseFloat(b),e=/(\d*\.\d+|\d+)%/;if(!isNaN(c)&&!isNaN(d)&&!0===this.get("resizable")){l("onresize",this),(""+a).match(e)&&(c=c/100*document.documentElement.clientWidth),(""+b).match(e)&&(d=d/100*document.documentElement.clientHeight);var f=this.elements.dialog;"none"!==f.style.maxWidth&&(f.style.minWidth=(Sa=f.offsetWidth)+"px"),f.style.maxWidth="none",f.style.minHeight=this.elements.header.offsetHeight+this.elements.footer.offsetHeight+"px",f.style.width=c+"px",f.style.height=d+"px",l("onresized",this)}return this},setting:function(a,b){var c=this,d=D(this,this.__internal.options,function(a,b,d){C(c,a,b,d)},a,b);if("get"===d.op)return d.found?d.value:void 0!==this.settings?D(this,this.settings,this.settingUpdated||function(){},a,b).value:void 0;if("set"===d.op){if(d.items.length>0)for(var e=this.settingUpdated||function(){},f=0;f<d.items.length;f+=1){var g=d.items[f];g.found||void 0===this.settings||D(this,this.settings,e,g.key,g.value)}return this}},set:function(a,b){return this.setting(a,b),this},get:function(a){return this.setting(a)},setHeader:function(b){return"string"==typeof b?(g(this.elements.header),this.elements.header.innerHTML=b):b instanceof a.HTMLElement&&this.elements.header.firstChild!==b&&(g(this.elements.header),this.elements.header.appendChild(b)),this},setContent:function(b){return"string"==typeof b?(g(this.elements.content),this.elements.content.innerHTML=b):b instanceof a.HTMLElement&&this.elements.content.firstChild!==b&&(g(this.elements.content),this.elements.content.appendChild(b)),this},showModal:function(a){return this.show(!0,a)},show:function(a,d){if(m(this),this.__internal.isOpen){da(this),ja(this),b(this.elements.dialog,Ca.shake);var e=this;setTimeout(function(){c(e.elements.dialog,Ca.shake)},200)}else{if(this.__internal.isOpen=!0,p.push(this),v.defaults.maintainFocus&&(this.__internal.activeElement=document.activeElement),"function"==typeof this.prepare&&this.prepare(),ma(this),void 0!==a&&this.set("modal",a),o(),u(),"string"==typeof d&&""!==d&&(this.__internal.className=d,b(this.elements.root,d)),this.get("startMaximized")?this.maximize():this.isMaximized()&&J(this),N(this),c(this.elements.root,Ca.animationOut),b(this.elements.root,Ca.animationIn),clearTimeout(this.__internal.timerIn),this.__internal.timerIn=setTimeout(this.__internal.transitionInHandler,s.supported?1e3:100),Aa){var f=this.elements.root;f.style.display="none",setTimeout(function(){f.style.display="block"},0)}za=this.elements.root.offsetWidth,c(this.elements.root,Ca.hidden),"function"==typeof this.hooks.onshow&&this.hooks.onshow.call(this),l("onshow",this)}return this},close:function(){return this.__internal.isOpen&&!1!==l("onclosing",this)&&(na(this),c(this.elements.root,Ca.animationIn),b(this.elements.root,Ca.animationOut),clearTimeout(this.__internal.timerOut),this.__internal.timerOut=setTimeout(this.__internal.transitionOutHandler,s.supported?1e3:100),b(this.elements.root,Ca.hidden),za=this.elements.modal.offsetWidth,void 0!==this.__internal.className&&""!==this.__internal.className&&c(this.elements.root,this.__internal.className),"function"==typeof this.hooks.onclose&&this.hooks.onclose.call(this),l("onclose",this),p.splice(p.indexOf(this),1),this.__internal.isOpen=!1,u()),this},closeOthers:function(){return v.closeAll(this),this},destroy:function(){return this.__internal.isOpen?(this.__internal.destroy=function(){i(this,m)},this.close()):i(this,m),this}}}(),u=function(){function d(a){a.__internal||(a.__internal={position:v.defaults.notifier.position,delay:v.defaults.notifier.delay},l=document.createElement("DIV"),h(a)),l.parentNode!==document.body&&document.body.appendChild(l)}function e(a){a.__internal.pushed=!0,m.push(a)}function f(a){m.splice(m.indexOf(a),1),a.__internal.pushed=!1}function h(a){switch(l.className=n.base,a.__internal.position){case"top-right":b(l,n.top+" "+n.right);break;case"top-left":b(l,n.top+" "+n.left);break;case"top-center":b(l,n.top+" "+n.center);break;case"bottom-left":b(l,n.bottom+" "+n.left);break;case"bottom-center":b(l,n.bottom+" "+n.center);break;default:case"bottom-right":b(l,n.bottom+" "+n.right)}}function i(d,h){function i(a,b){b.__internal.closeButton&&"true"!==a.target.getAttribute("data-close")||b.dismiss(!0)}function m(a,b){r(b.element,s.type,m),l.removeChild(b.element)}function o(a){return a.__internal||(a.__internal={pushed:!1,delay:void 0,timer:void 0,clickHandler:void 0,transitionEndHandler:void 0,transitionTimeout:void 0},a.__internal.clickHandler=j(a,i),a.__internal.transitionEndHandler=j(a,m)),a}function p(a){clearTimeout(a.__internal.timer),clearTimeout(a.__internal.transitionTimeout)}return o({element:d,push:function(a,c){if(!this.__internal.pushed){e(this),p(this);var d,f;switch(arguments.length){case 0:f=this.__internal.delay;break;case 1:"number"==typeof a?f=a:(d=a,f=this.__internal.delay);break;case 2:d=a,f=c}return this.__internal.closeButton=v.defaults.notifier.closeButton,void 0!==d&&this.setContent(d),u.__internal.position.indexOf("top")<0?l.appendChild(this.element):l.insertBefore(this.element,l.firstChild),k=this.element.offsetWidth,b(this.element,n.visible),q(this.element,"click",this.__internal.clickHandler),this.delay(f)}return this},ondismiss:function(){},callback:h,dismiss:function(a){return this.__internal.pushed&&(p(this),"function"==typeof this.ondismiss&&!1===this.ondismiss.call(this)||(r(this.element,"click",this.__internal.clickHandler),void 0!==this.element&&this.element.parentNode===l&&(this.__internal.transitionTimeout=setTimeout(this.__internal.transitionEndHandler,s.supported?1e3:100),c(this.element,n.visible),"function"==typeof this.callback&&this.callback.call(this,a)),f(this))),this},delay:function(a){if(p(this),this.__internal.delay=void 0===a||isNaN(+a)?u.__internal.delay:+a,this.__internal.delay>0){var b=this;this.__internal.timer=setTimeout(function(){b.dismiss()},1e3*this.__internal.delay)}return this},setContent:function(c){if("string"==typeof c?(g(this.element),this.element.innerHTML=c):c instanceof a.HTMLElement&&this.element.firstChild!==c&&(g(this.element),this.element.appendChild(c)),this.__internal.closeButton){var d=document.createElement("span");b(d,n.close),d.setAttribute("data-close",!0),this.element.appendChild(d)}return this},dismissOthers:function(){return u.dismissAll(this),this}})}var k,l,m=[],n={base:"alertify-notifier",message:"ajs-message",top:"ajs-top",right:"ajs-right",bottom:"ajs-bottom",left:"ajs-left",center:"ajs-center",visible:"ajs-visible",hidden:"ajs-hidden",close:"ajs-close"};return{setting:function(a,b){if(d(this),void 0===b)return this.__internal[a];switch(a){case"position":this.__internal.position=b,h(this);break;case"delay":this.__internal.delay=b}return this},set:function(a,b){return this.setting(a,b),this},get:function(a){return this.setting(a)},create:function(a,b){d(this);var c=document.createElement("div");return c.className=n.message+("string"==typeof a&&""!==a?" ajs-"+a:""),i(c,b)},dismissAll:function(a){for(var b=m.slice(0),c=0;c<b.length;c+=1){var d=b[c];void 0!==a&&a===d||d.dismiss()}}}}(),v=new m;v.dialog("alert",function(){return{main:function(a,b,c){var d,e,f;switch(arguments.length){case 1:e=a;break;case 2:"function"==typeof b?(e=a,f=b):(d=a,e=b);break;case 3:d=a,e=b,f=c}return this.set("title",d),this.set("message",e),this.set("onok",f),this},setup:function(){return{buttons:[{text:v.defaults.glossary.ok,key:n.ESC,invokeOnClose:!0,className:v.defaults.theme.ok}],focus:{element:0,select:!1},options:{maximizable:!1,resizable:!1}}},build:function(){},prepare:function(){},setMessage:function(a){this.setContent(a)},settings:{message:void 0,onok:void 0,label:void 0},settingUpdated:function(a,b,c){switch(a){case"message":this.setMessage(c);break;case"label":this.__internal.buttons[0].element&&(this.__internal.buttons[0].element.innerHTML=c)}},callback:function(a){if("function"==typeof this.get("onok")){var b=this.get("onok").call(this,a);void 0!==b&&(a.cancel=!b)}}}}),v.dialog("confirm",function(){function a(a){null!==c.timer&&(clearInterval(c.timer),c.timer=null,a.__internal.buttons[c.index].element.innerHTML=c.text)}function b(b,d,e){a(b),c.duration=e,c.index=d,c.text=b.__internal.buttons[d].element.innerHTML,c.timer=setInterval(j(b,c.task),1e3),c.task(null,b)}var c={timer:null,index:null,text:null,duration:null,task:function(b,d){if(d.isOpen()){if(d.__internal.buttons[c.index].element.innerHTML=c.text+" (&#8207;"+c.duration+"&#8207;) ",c.duration-=1,-1===c.duration){a(d);var e=d.__internal.buttons[c.index],f=k(c.index,e);"function"==typeof d.callback&&d.callback.apply(d,[f]),!1!==f.close&&d.close()}}else a(d)}};return{main:function(a,b,c,d){var e,f,g,h;switch(arguments.length){case 1:f=a;break;case 2:f=a,g=b;break;case 3:f=a,g=b,h=c;break;case 4:e=a,f=b,g=c,h=d}return this.set("title",e),this.set("message",f),this.set("onok",g),this.set("oncancel",h),this},setup:function(){return{buttons:[{text:v.defaults.glossary.ok,key:n.ENTER,className:v.defaults.theme.ok},{text:v.defaults.glossary.cancel,key:n.ESC,invokeOnClose:!0,
3
+ className:v.defaults.theme.cancel}],focus:{element:0,select:!1},options:{maximizable:!1,resizable:!1}}},build:function(){},prepare:function(){},setMessage:function(a){this.setContent(a)},settings:{message:null,labels:null,onok:null,oncancel:null,defaultFocus:null,reverseButtons:null},settingUpdated:function(a,b,c){switch(a){case"message":this.setMessage(c);break;case"labels":"ok"in c&&this.__internal.buttons[0].element&&(this.__internal.buttons[0].text=c.ok,this.__internal.buttons[0].element.innerHTML=c.ok),"cancel"in c&&this.__internal.buttons[1].element&&(this.__internal.buttons[1].text=c.cancel,this.__internal.buttons[1].element.innerHTML=c.cancel);break;case"reverseButtons":!0===c?this.elements.buttons.primary.appendChild(this.__internal.buttons[0].element):this.elements.buttons.primary.appendChild(this.__internal.buttons[1].element);break;case"defaultFocus":this.__internal.focus.element="ok"===c?0:1}},callback:function(b){a(this);var c;switch(b.index){case 0:"function"==typeof this.get("onok")&&void 0!==(c=this.get("onok").call(this,b))&&(b.cancel=!c);break;case 1:"function"==typeof this.get("oncancel")&&void 0!==(c=this.get("oncancel").call(this,b))&&(b.cancel=!c)}},autoOk:function(a){return b(this,0,a),this},autoCancel:function(a){return b(this,1,a),this}}}),v.dialog("prompt",function(){var b=document.createElement("INPUT"),c=document.createElement("P");return{main:function(a,b,c,d,e){var f,g,h,i,j;switch(arguments.length){case 1:g=a;break;case 2:g=a,h=b;break;case 3:g=a,h=b,i=c;break;case 4:g=a,h=b,i=c,j=d;break;case 5:f=a,g=b,h=c,i=d,j=e}return this.set("title",f),this.set("message",g),this.set("value",h),this.set("onok",i),this.set("oncancel",j),this},setup:function(){return{buttons:[{text:v.defaults.glossary.ok,key:n.ENTER,className:v.defaults.theme.ok},{text:v.defaults.glossary.cancel,key:n.ESC,invokeOnClose:!0,className:v.defaults.theme.cancel}],focus:{element:b,select:!0},options:{maximizable:!1,resizable:!1}}},build:function(){b.className=v.defaults.theme.input,b.setAttribute("type","text"),b.value=this.get("value"),this.elements.content.appendChild(c),this.elements.content.appendChild(b)},prepare:function(){},setMessage:function(b){"string"==typeof b?(g(c),c.innerHTML=b):b instanceof a.HTMLElement&&c.firstChild!==b&&(g(c),c.appendChild(b))},settings:{message:void 0,labels:void 0,onok:void 0,oncancel:void 0,value:"",type:"text",reverseButtons:void 0},settingUpdated:function(a,c,d){switch(a){case"message":this.setMessage(d);break;case"value":b.value=d;break;case"type":switch(d){case"text":case"color":case"date":case"datetime-local":case"email":case"month":case"number":case"password":case"search":case"tel":case"time":case"week":b.type=d;break;default:b.type="text"}break;case"labels":d.ok&&this.__internal.buttons[0].element&&(this.__internal.buttons[0].element.innerHTML=d.ok),d.cancel&&this.__internal.buttons[1].element&&(this.__internal.buttons[1].element.innerHTML=d.cancel);break;case"reverseButtons":!0===d?this.elements.buttons.primary.appendChild(this.__internal.buttons[0].element):this.elements.buttons.primary.appendChild(this.__internal.buttons[1].element)}},callback:function(a){var c;switch(a.index){case 0:this.settings.value=b.value,"function"==typeof this.get("onok")&&void 0!==(c=this.get("onok").call(this,a,this.settings.value))&&(a.cancel=!c);break;case 1:"function"==typeof this.get("oncancel")&&void 0!==(c=this.get("oncancel").call(this,a))&&(a.cancel=!c),a.cancel||(b.value=this.settings.value)}}}}),"object"==typeof module&&"object"==typeof module.exports?module.exports=v:"function"==typeof define&&define.amd?define([],function(){return v}):a.alertify||(a.alertify=v)}("undefined"!=typeof window?window:this);
scripts/chartjs/chart.js ADDED
@@ -0,0 +1,14156 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ /*!
2
+ * Chart.js
3
+ * http://chartjs.org/
4
+ * Version: 2.7.1
5
+ *
6
+ * Copyright 2017 Nick Downie
7
+ * Released under the MIT license
8
+ * https://github.com/chartjs/Chart.js/blob/master/LICENSE.md
9
+ */
10
+ (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 e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);var f=new Error("Cannot find module '"+o+"'");throw f.code="MODULE_NOT_FOUND",f}var l=n[o]={exports:{}};t[o][0].call(l.exports,function(e){var n=t[o][1][e];return s(n?n:e)},l,l.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o<r.length;o++)s(r[o]);return s})({1:[function(require,module,exports){
11
+
12
+ },{}],2:[function(require,module,exports){
13
+ /* MIT license */
14
+ var colorNames = require(6);
15
+
16
+ module.exports = {
17
+ getRgba: getRgba,
18
+ getHsla: getHsla,
19
+ getRgb: getRgb,
20
+ getHsl: getHsl,
21
+ getHwb: getHwb,
22
+ getAlpha: getAlpha,
23
+
24
+ hexString: hexString,
25
+ rgbString: rgbString,
26
+ rgbaString: rgbaString,
27
+ percentString: percentString,
28
+ percentaString: percentaString,
29
+ hslString: hslString,
30
+ hslaString: hslaString,
31
+ hwbString: hwbString,
32
+ keyword: keyword
33
+ }
34
+
35
+ function getRgba(string) {
36
+ if (!string) {
37
+ return;
38
+ }
39
+ var abbr = /^#([a-fA-F0-9]{3})$/i,
40
+ hex = /^#([a-fA-F0-9]{6})$/i,
41
+ rgba = /^rgba?\(\s*([+-]?\d+)\s*,\s*([+-]?\d+)\s*,\s*([+-]?\d+)\s*(?:,\s*([+-]?[\d\.]+)\s*)?\)$/i,
42
+ per = /^rgba?\(\s*([+-]?[\d\.]+)\%\s*,\s*([+-]?[\d\.]+)\%\s*,\s*([+-]?[\d\.]+)\%\s*(?:,\s*([+-]?[\d\.]+)\s*)?\)$/i,
43
+ keyword = /(\w+)/;
44
+
45
+ var rgb = [0, 0, 0],
46
+ a = 1,
47
+ match = string.match(abbr);
48
+ if (match) {
49
+ match = match[1];
50
+ for (var i = 0; i < rgb.length; i++) {
51
+ rgb[i] = parseInt(match[i] + match[i], 16);
52
+ }
53
+ }
54
+ else if (match = string.match(hex)) {
55
+ match = match[1];
56
+ for (var i = 0; i < rgb.length; i++) {
57
+ rgb[i] = parseInt(match.slice(i * 2, i * 2 + 2), 16);
58
+ }
59
+ }
60
+ else if (match = string.match(rgba)) {
61
+ for (var i = 0; i < rgb.length; i++) {
62
+ rgb[i] = parseInt(match[i + 1]);
63
+ }
64
+ a = parseFloat(match[4]);
65
+ }
66
+ else if (match = string.match(per)) {
67
+ for (var i = 0; i < rgb.length; i++) {
68
+ rgb[i] = Math.round(parseFloat(match[i + 1]) * 2.55);
69
+ }
70
+ a = parseFloat(match[4]);
71
+ }
72
+ else if (match = string.match(keyword)) {
73
+ if (match[1] == "transparent") {
74
+ return [0, 0, 0, 0];
75
+ }
76
+ rgb = colorNames[match[1]];
77
+ if (!rgb) {
78
+ return;
79
+ }
80
+ }
81
+
82
+ for (var i = 0; i < rgb.length; i++) {
83
+ rgb[i] = scale(rgb[i], 0, 255);
84
+ }
85
+ if (!a && a != 0) {
86
+ a = 1;
87
+ }
88
+ else {
89
+ a = scale(a, 0, 1);
90
+ }
91
+ rgb[3] = a;
92
+ return rgb;
93
+ }
94
+
95
+ function getHsla(string) {
96
+ if (!string) {
97
+ return;
98
+ }
99
+ var hsl = /^hsla?\(\s*([+-]?\d+)(?:deg)?\s*,\s*([+-]?[\d\.]+)%\s*,\s*([+-]?[\d\.]+)%\s*(?:,\s*([+-]?[\d\.]+)\s*)?\)/;
100
+ var match = string.match(hsl);
101
+ if (match) {
102
+ var alpha = parseFloat(match[4]);
103
+ var h = scale(parseInt(match[1]), 0, 360),
104
+ s = scale(parseFloat(match[2]), 0, 100),
105
+ l = scale(parseFloat(match[3]), 0, 100),
106
+ a = scale(isNaN(alpha) ? 1 : alpha, 0, 1);
107
+ return [h, s, l, a];
108
+ }
109
+ }
110
+
111
+ function getHwb(string) {
112
+ if (!string) {
113
+ return;
114
+ }
115
+ var hwb = /^hwb\(\s*([+-]?\d+)(?:deg)?\s*,\s*([+-]?[\d\.]+)%\s*,\s*([+-]?[\d\.]+)%\s*(?:,\s*([+-]?[\d\.]+)\s*)?\)/;
116
+ var match = string.match(hwb);
117
+ if (match) {
118
+ var alpha = parseFloat(match[4]);
119
+ var h = scale(parseInt(match[1]), 0, 360),
120
+ w = scale(parseFloat(match[2]), 0, 100),
121
+ b = scale(parseFloat(match[3]), 0, 100),
122
+ a = scale(isNaN(alpha) ? 1 : alpha, 0, 1);
123
+ return [h, w, b, a];
124
+ }
125
+ }
126
+
127
+ function getRgb(string) {
128
+ var rgba = getRgba(string);
129
+ return rgba && rgba.slice(0, 3);
130
+ }
131
+
132
+ function getHsl(string) {
133
+ var hsla = getHsla(string);
134
+ return hsla && hsla.slice(0, 3);
135
+ }
136
+
137
+ function getAlpha(string) {
138
+ var vals = getRgba(string);
139
+ if (vals) {
140
+ return vals[3];
141
+ }
142
+ else if (vals = getHsla(string)) {
143
+ return vals[3];
144
+ }
145
+ else if (vals = getHwb(string)) {
146
+ return vals[3];
147
+ }
148
+ }
149
+
150
+ // generators
151
+ function hexString(rgb) {
152
+ return "#" + hexDouble(rgb[0]) + hexDouble(rgb[1])
153
+ + hexDouble(rgb[2]);
154
+ }
155
+
156
+ function rgbString(rgba, alpha) {
157
+ if (alpha < 1 || (rgba[3] && rgba[3] < 1)) {
158
+ return rgbaString(rgba, alpha);
159
+ }
160
+ return "rgb(" + rgba[0] + ", " + rgba[1] + ", " + rgba[2] + ")";
161
+ }
162
+
163
+ function rgbaString(rgba, alpha) {
164
+ if (alpha === undefined) {
165
+ alpha = (rgba[3] !== undefined ? rgba[3] : 1);
166
+ }
167
+ return "rgba(" + rgba[0] + ", " + rgba[1] + ", " + rgba[2]
168
+ + ", " + alpha + ")";
169
+ }
170
+
171
+ function percentString(rgba, alpha) {
172
+ if (alpha < 1 || (rgba[3] && rgba[3] < 1)) {
173
+ return percentaString(rgba, alpha);
174
+ }
175
+ var r = Math.round(rgba[0]/255 * 100),
176
+ g = Math.round(rgba[1]/255 * 100),
177
+ b = Math.round(rgba[2]/255 * 100);
178
+
179
+ return "rgb(" + r + "%, " + g + "%, " + b + "%)";
180
+ }
181
+
182
+ function percentaString(rgba, alpha) {
183
+ var r = Math.round(rgba[0]/255 * 100),
184
+ g = Math.round(rgba[1]/255 * 100),
185
+ b = Math.round(rgba[2]/255 * 100);
186
+ return "rgba(" + r + "%, " + g + "%, " + b + "%, " + (alpha || rgba[3] || 1) + ")";
187
+ }
188
+
189
+ function hslString(hsla, alpha) {
190
+ if (alpha < 1 || (hsla[3] && hsla[3] < 1)) {
191
+ return hslaString(hsla, alpha);
192
+ }
193
+ return "hsl(" + hsla[0] + ", " + hsla[1] + "%, " + hsla[2] + "%)";
194
+ }
195
+
196
+ function hslaString(hsla, alpha) {
197
+ if (alpha === undefined) {
198
+ alpha = (hsla[3] !== undefined ? hsla[3] : 1);
199
+ }
200
+ return "hsla(" + hsla[0] + ", " + hsla[1] + "%, " + hsla[2] + "%, "
201
+ + alpha + ")";
202
+ }
203
+
204
+ // hwb is a bit different than rgb(a) & hsl(a) since there is no alpha specific syntax
205
+ // (hwb have alpha optional & 1 is default value)
206
+ function hwbString(hwb, alpha) {
207
+ if (alpha === undefined) {
208
+ alpha = (hwb[3] !== undefined ? hwb[3] : 1);
209
+ }
210
+ return "hwb(" + hwb[0] + ", " + hwb[1] + "%, " + hwb[2] + "%"
211
+ + (alpha !== undefined && alpha !== 1 ? ", " + alpha : "") + ")";
212
+ }
213
+
214
+ function keyword(rgb) {
215
+ return reverseNames[rgb.slice(0, 3)];
216
+ }
217
+
218
+ // helpers
219
+ function scale(num, min, max) {
220
+ return Math.min(Math.max(min, num), max);
221
+ }
222
+
223
+ function hexDouble(num) {
224
+ var str = num.toString(16).toUpperCase();
225
+ return (str.length < 2) ? "0" + str : str;
226
+ }
227
+
228
+
229
+ //create a list of reverse color names
230
+ var reverseNames = {};
231
+ for (var name in colorNames) {
232
+ reverseNames[colorNames[name]] = name;
233
+ }
234
+
235
+ },{"6":6}],3:[function(require,module,exports){
236
+ /* MIT license */
237
+ var convert = require(5);
238
+ var string = require(2);
239
+
240
+ var Color = function (obj) {
241
+ if (obj instanceof Color) {
242
+ return obj;
243
+ }
244
+ if (!(this instanceof Color)) {
245
+ return new Color(obj);
246
+ }
247
+
248
+ this.valid = false;
249
+ this.values = {
250
+ rgb: [0, 0, 0],
251
+ hsl: [0, 0, 0],
252
+ hsv: [0, 0, 0],
253
+ hwb: [0, 0, 0],
254
+ cmyk: [0, 0, 0, 0],
255
+ alpha: 1
256
+ };
257
+
258
+ // parse Color() argument
259
+ var vals;
260
+ if (typeof obj === 'string') {
261
+ vals = string.getRgba(obj);
262
+ if (vals) {
263
+ this.setValues('rgb', vals);
264
+ } else if (vals = string.getHsla(obj)) {
265
+ this.setValues('hsl', vals);
266
+ } else if (vals = string.getHwb(obj)) {
267
+ this.setValues('hwb', vals);
268
+ }
269
+ } else if (typeof obj === 'object') {
270
+ vals = obj;
271
+ if (vals.r !== undefined || vals.red !== undefined) {
272
+ this.setValues('rgb', vals);
273
+ } else if (vals.l !== undefined || vals.lightness !== undefined) {
274
+ this.setValues('hsl', vals);
275
+ } else if (vals.v !== undefined || vals.value !== undefined) {
276
+ this.setValues('hsv', vals);
277
+ } else if (vals.w !== undefined || vals.whiteness !== undefined) {
278
+ this.setValues('hwb', vals);
279
+ } else if (vals.c !== undefined || vals.cyan !== undefined) {
280
+ this.setValues('cmyk', vals);
281
+ }
282
+ }
283
+ };
284
+
285
+
286
+ Color.prototype = {
287
+ isValid: function () {
288
+ return this.valid;
289
+ },
290
+ rgb: function () {
291
+ return this.setSpace('rgb', arguments);
292
+ },
293
+ hsl: function () {
294
+ return this.setSpace('hsl', arguments);
295
+ },
296
+ hsv: function () {
297
+ return this.setSpace('hsv', arguments);
298
+ },
299
+ hwb: function () {
300
+ return this.setSpace('hwb', arguments);
301
+ },
302
+ cmyk: function () {
303
+ return this.setSpace('cmyk', arguments);
304
+ },
305
+
306
+ rgbArray: function () {
307
+ return this.values.rgb;
308
+ },
309
+ hslArray: function () {
310
+ return this.values.hsl;
311
+ },
312
+ hsvArray: function () {
313
+ return this.values.hsv;
314
+ },
315
+ hwbArray: function () {
316
+ var values = this.values;
317
+ if (values.alpha !== 1) {
318
+ return values.hwb.concat([values.alpha]);
319
+ }
320
+ return values.hwb;
321
+ },
322
+ cmykArray: function () {
323
+ return this.values.cmyk;
324
+ },
325
+ rgbaArray: function () {
326
+ var values = this.values;
327
+ return values.rgb.concat([values.alpha]);
328
+ },
329
+ hslaArray: function () {
330
+ var values = this.values;
331
+ return values.hsl.concat([values.alpha]);
332
+ },
333
+ alpha: function (val) {
334
+ if (val === undefined) {
335
+ return this.values.alpha;
336
+ }
337
+ this.setValues('alpha', val);
338
+ return this;
339
+ },
340
+
341
+ red: function (val) {
342
+ return this.setChannel('rgb', 0, val);
343
+ },
344
+ green: function (val) {
345
+ return this.setChannel('rgb', 1, val);
346
+ },
347
+ blue: function (val) {
348
+ return this.setChannel('rgb', 2, val);
349
+ },
350
+ hue: function (val) {
351
+ if (val) {
352
+ val %= 360;
353
+ val = val < 0 ? 360 + val : val;
354
+ }
355
+ return this.setChannel('hsl', 0, val);
356
+ },
357
+ saturation: function (val) {
358
+ return this.setChannel('hsl', 1, val);
359
+ },
360
+ lightness: function (val) {
361
+ return this.setChannel('hsl', 2, val);
362
+ },
363
+ saturationv: function (val) {
364
+ return this.setChannel('hsv', 1, val);
365
+ },
366
+ whiteness: function (val) {
367
+ return this.setChannel('hwb', 1, val);
368
+ },
369
+ blackness: function (val) {
370
+ return this.setChannel('hwb', 2, val);
371
+ },
372
+ value: function (val) {
373
+ return this.setChannel('hsv', 2, val);
374
+ },
375
+ cyan: function (val) {
376
+ return this.setChannel('cmyk', 0, val);
377
+ },
378
+ magenta: function (val) {
379
+ return this.setChannel('cmyk', 1, val);
380
+ },
381
+ yellow: function (val) {
382
+ return this.setChannel('cmyk', 2, val);
383
+ },
384
+ black: function (val) {
385
+ return this.setChannel('cmyk', 3, val);
386
+ },
387
+
388
+ hexString: function () {
389
+ return string.hexString(this.values.rgb);
390
+ },
391
+ rgbString: function () {
392
+ return string.rgbString(this.values.rgb, this.values.alpha);
393
+ },
394
+ rgbaString: function () {
395
+ return string.rgbaString(this.values.rgb, this.values.alpha);
396
+ },
397
+ percentString: function () {
398
+ return string.percentString(this.values.rgb, this.values.alpha);
399
+ },
400
+ hslString: function () {
401
+ return string.hslString(this.values.hsl, this.values.alpha);
402
+ },
403
+ hslaString: function () {
404
+ return string.hslaString(this.values.hsl, this.values.alpha);
405
+ },
406
+ hwbString: function () {
407
+ return string.hwbString(this.values.hwb, this.values.alpha);
408
+ },
409
+ keyword: function () {
410
+ return string.keyword(this.values.rgb, this.values.alpha);
411
+ },
412
+
413
+ rgbNumber: function () {
414
+ var rgb = this.values.rgb;
415
+ return (rgb[0] << 16) | (rgb[1] << 8) | rgb[2];
416
+ },
417
+
418
+ luminosity: function () {
419
+ // http://www.w3.org/TR/WCAG20/#relativeluminancedef
420
+ var rgb = this.values.rgb;
421
+ var lum = [];
422
+ for (var i = 0; i < rgb.length; i++) {
423
+ var chan = rgb[i] / 255;
424
+ lum[i] = (chan <= 0.03928) ? chan / 12.92 : Math.pow(((chan + 0.055) / 1.055), 2.4);
425
+ }
426
+ return 0.2126 * lum[0] + 0.7152 * lum[1] + 0.0722 * lum[2];
427
+ },
428
+
429
+ contrast: function (color2) {
430
+ // http://www.w3.org/TR/WCAG20/#contrast-ratiodef
431
+ var lum1 = this.luminosity();
432
+ var lum2 = color2.luminosity();
433
+ if (lum1 > lum2) {
434
+ return (lum1 + 0.05) / (lum2 + 0.05);
435
+ }
436
+ return (lum2 + 0.05) / (lum1 + 0.05);
437
+ },
438
+
439
+ level: function (color2) {
440
+ var contrastRatio = this.contrast(color2);
441
+ if (contrastRatio >= 7.1) {
442
+ return 'AAA';
443
+ }
444
+
445
+ return (contrastRatio >= 4.5) ? 'AA' : '';
446
+ },
447
+
448
+ dark: function () {
449
+ // YIQ equation from http://24ways.org/2010/calculating-color-contrast
450
+ var rgb = this.values.rgb;
451
+ var yiq = (rgb[0] * 299 + rgb[1] * 587 + rgb[2] * 114) / 1000;
452
+ return yiq < 128;
453
+ },
454
+
455
+ light: function () {
456
+ return !this.dark();
457
+ },
458
+
459
+ negate: function () {
460
+ var rgb = [];
461
+ for (var i = 0; i < 3; i++) {
462
+ rgb[i] = 255 - this.values.rgb[i];
463
+ }
464
+ this.setValues('rgb', rgb);
465
+ return this;
466
+ },
467
+
468
+ lighten: function (ratio) {
469
+ var hsl = this.values.hsl;
470
+ hsl[2] += hsl[2] * ratio;
471
+ this.setValues('hsl', hsl);
472
+ return this;
473
+ },
474
+
475
+ darken: function (ratio) {
476
+ var hsl = this.values.hsl;
477
+ hsl[2] -= hsl[2] * ratio;
478
+ this.setValues('hsl', hsl);
479
+ return this;
480
+ },
481
+
482
+ saturate: function (ratio) {
483
+ var hsl = this.values.hsl;
484
+ hsl[1] += hsl[1] * ratio;
485
+ this.setValues('hsl', hsl);
486
+ return this;
487
+ },
488
+
489
+ desaturate: function (ratio) {
490
+ var hsl = this.values.hsl;
491
+ hsl[1] -= hsl[1] * ratio;
492
+ this.setValues('hsl', hsl);
493
+ return this;
494
+ },
495
+
496
+ whiten: function (ratio) {
497
+ var hwb = this.values.hwb;
498
+ hwb[1] += hwb[1] * ratio;
499
+ this.setValues('hwb', hwb);
500
+ return this;
501
+ },
502
+
503
+ blacken: function (ratio) {
504
+ var hwb = this.values.hwb;
505
+ hwb[2] += hwb[2] * ratio;
506
+ this.setValues('hwb', hwb);
507
+ return this;
508
+ },
509
+
510
+ greyscale: function () {
511
+ var rgb = this.values.rgb;
512
+ // http://en.wikipedia.org/wiki/Grayscale#Converting_color_to_grayscale
513
+ var val = rgb[0] * 0.3 + rgb[1] * 0.59 + rgb[2] * 0.11;
514
+ this.setValues('rgb', [val, val, val]);
515
+ return this;
516
+ },
517
+
518
+ clearer: function (ratio) {
519
+ var alpha = this.values.alpha;
520
+ this.setValues('alpha', alpha - (alpha * ratio));
521
+ return this;
522
+ },
523
+
524
+ opaquer: function (ratio) {
525
+ var alpha = this.values.alpha;
526
+ this.setValues('alpha', alpha + (alpha * ratio));
527
+ return this;
528
+ },
529
+
530
+ rotate: function (degrees) {
531
+ var hsl = this.values.hsl;
532
+ var hue = (hsl[0] + degrees) % 360;
533
+ hsl[0] = hue < 0 ? 360 + hue : hue;
534
+ this.setValues('hsl', hsl);
535
+ return this;
536
+ },
537
+
538
+ /**
539
+ * Ported from sass implementation in C
540
+ * https://github.com/sass/libsass/blob/0e6b4a2850092356aa3ece07c6b249f0221caced/functions.cpp#L209
541
+ */
542
+ mix: function (mixinColor, weight) {
543
+ var color1 = this;
544
+ var color2 = mixinColor;
545
+ var p = weight === undefined ? 0.5 : weight;
546
+
547
+ var w = 2 * p - 1;
548
+ var a = color1.alpha() - color2.alpha();
549
+
550
+ var w1 = (((w * a === -1) ? w : (w + a) / (1 + w * a)) + 1) / 2.0;
551
+ var w2 = 1 - w1;
552
+
553
+ return this
554
+ .rgb(
555
+ w1 * color1.red() + w2 * color2.red(),
556
+ w1 * color1.green() + w2 * color2.green(),
557
+ w1 * color1.blue() + w2 * color2.blue()
558
+ )
559
+ .alpha(color1.alpha() * p + color2.alpha() * (1 - p));
560
+ },
561
+
562
+ toJSON: function () {
563
+ return this.rgb();
564
+ },
565
+
566
+ clone: function () {
567
+ // NOTE(SB): using node-clone creates a dependency to Buffer when using browserify,
568
+ // making the final build way to big to embed in Chart.js. So let's do it manually,
569
+ // assuming that values to clone are 1 dimension arrays containing only numbers,
570
+ // except 'alpha' which is a number.
571
+ var result = new Color();
572
+ var source = this.values;
573
+ var target = result.values;
574
+ var value, type;
575
+
576
+ for (var prop in source) {
577
+ if (source.hasOwnProperty(prop)) {
578
+ value = source[prop];
579
+ type = ({}).toString.call(value);
580
+ if (type === '[object Array]') {
581
+ target[prop] = value.slice(0);
582
+ } else if (type === '[object Number]') {
583
+ target[prop] = value;
584
+ } else {
585
+ console.error('unexpected color value:', value);
586
+ }
587
+ }
588
+ }
589
+
590
+ return result;
591
+ }
592
+ };
593
+
594
+ Color.prototype.spaces = {
595
+ rgb: ['red', 'green', 'blue'],
596
+ hsl: ['hue', 'saturation', 'lightness'],
597
+ hsv: ['hue', 'saturation', 'value'],
598
+ hwb: ['hue', 'whiteness', 'blackness'],
599
+ cmyk: ['cyan', 'magenta', 'yellow', 'black']
600
+ };
601
+
602
+ Color.prototype.maxes = {
603
+ rgb: [255, 255, 255],
604
+ hsl: [360, 100, 100],
605
+ hsv: [360, 100, 100],
606
+ hwb: [360, 100, 100],
607
+ cmyk: [100, 100, 100, 100]
608
+ };
609
+
610
+ Color.prototype.getValues = function (space) {
611
+ var values = this.values;
612
+ var vals = {};
613
+
614
+ for (var i = 0; i < space.length; i++) {
615
+ vals[space.charAt(i)] = values[space][i];
616
+ }
617
+
618
+ if (values.alpha !== 1) {
619
+ vals.a = values.alpha;
620
+ }
621
+
622
+ // {r: 255, g: 255, b: 255, a: 0.4}
623
+ return vals;
624
+ };
625
+
626
+ Color.prototype.setValues = function (space, vals) {
627
+ var values = this.values;
628
+ var spaces = this.spaces;
629
+ var maxes = this.maxes;
630
+ var alpha = 1;
631
+ var i;
632
+
633
+ this.valid = true;
634
+
635
+ if (space === 'alpha') {
636
+ alpha = vals;
637
+ } else if (vals.length) {
638
+ // [10, 10, 10]
639
+ values[space] = vals.slice(0, space.length);
640
+ alpha = vals[space.length];
641
+ } else if (vals[space.charAt(0)] !== undefined) {
642
+ // {r: 10, g: 10, b: 10}
643
+ for (i = 0; i < space.length; i++) {
644
+ values[space][i] = vals[space.charAt(i)];
645
+ }
646
+
647
+ alpha = vals.a;
648
+ } else if (vals[spaces[space][0]] !== undefined) {
649
+ // {red: 10, green: 10, blue: 10}
650
+ var chans = spaces[space];
651
+
652
+ for (i = 0; i < space.length; i++) {
653
+ values[space][i] = vals[chans[i]];
654
+ }
655
+
656
+ alpha = vals.alpha;
657
+ }
658
+
659
+ values.alpha = Math.max(0, Math.min(1, (alpha === undefined ? values.alpha : alpha)));
660
+
661
+ if (space === 'alpha') {
662
+ return false;
663
+ }
664
+
665
+ var capped;
666
+
667
+ // cap values of the space prior converting all values
668
+ for (i = 0; i < space.length; i++) {
669
+ capped = Math.max(0, Math.min(maxes[space][i], values[space][i]));
670
+ values[space][i] = Math.round(capped);
671
+ }
672
+
673
+ // convert to all the other color spaces
674
+ for (var sname in spaces) {
675
+ if (sname !== space) {
676
+ values[sname] = convert[space][sname](values[space]);
677
+ }
678
+ }
679
+
680
+ return true;
681
+ };
682
+
683
+ Color.prototype.setSpace = function (space, args) {
684
+ var vals = args[0];
685
+
686
+ if (vals === undefined) {
687
+ // color.rgb()
688
+ return this.getValues(space);
689
+ }
690
+
691
+ // color.rgb(10, 10, 10)
692
+ if (typeof vals === 'number') {
693
+ vals = Array.prototype.slice.call(args);
694
+ }
695
+
696
+ this.setValues(space, vals);
697
+ return this;
698
+ };
699
+
700
+ Color.prototype.setChannel = function (space, index, val) {
701
+ var svalues = this.values[space];
702
+ if (val === undefined) {
703
+ // color.red()
704
+ return svalues[index];
705
+ } else if (val === svalues[index]) {
706
+ // color.red(color.red())
707
+ return this;
708
+ }
709
+
710
+ // color.red(100)
711
+ svalues[index] = val;
712
+ this.setValues(space, svalues);
713
+
714
+ return this;
715
+ };
716
+
717
+ //if (typeof window !== 'undefined') {
718
+ // window.Color = Color;
719
+ //}
720
+ if (typeof window !== 'undefined') {
721
+ window.Chart = window.Chart || {};
722
+ window.Chart.Color = Color;
723
+
724
+ if (typeof window.Color === 'undefined') {
725
+ // maintain backward compatibility ONLY if no other Color lib has been defined
726
+ window.Color = Color;
727
+ }
728
+ }
729
+
730
+
731
+ module.exports = Color;
732
+
733
+ },{"2":2,"5":5}],4:[function(require,module,exports){
734
+ /* MIT license */
735
+
736
+ module.exports = {
737
+ rgb2hsl: rgb2hsl,
738
+ rgb2hsv: rgb2hsv,
739
+ rgb2hwb: rgb2hwb,
740
+ rgb2cmyk: rgb2cmyk,
741
+ rgb2keyword: rgb2keyword,
742
+ rgb2xyz: rgb2xyz,
743
+ rgb2lab: rgb2lab,
744
+ rgb2lch: rgb2lch,
745
+
746
+ hsl2rgb: hsl2rgb,
747
+ hsl2hsv: hsl2hsv,
748
+ hsl2hwb: hsl2hwb,
749
+ hsl2cmyk: hsl2cmyk,
750
+ hsl2keyword: hsl2keyword,
751
+
752
+ hsv2rgb: hsv2rgb,
753
+ hsv2hsl: hsv2hsl,
754
+ hsv2hwb: hsv2hwb,
755
+ hsv2cmyk: hsv2cmyk,
756
+ hsv2keyword: hsv2keyword,
757
+
758
+ hwb2rgb: hwb2rgb,
759
+ hwb2hsl: hwb2hsl,
760
+ hwb2hsv: hwb2hsv,
761
+ hwb2cmyk: hwb2cmyk,
762
+ hwb2keyword: hwb2keyword,
763
+
764
+ cmyk2rgb: cmyk2rgb,
765
+ cmyk2hsl: cmyk2hsl,
766
+ cmyk2hsv: cmyk2hsv,
767
+ cmyk2hwb: cmyk2hwb,
768
+ cmyk2keyword: cmyk2keyword,
769
+
770
+ keyword2rgb: keyword2rgb,
771
+ keyword2hsl: keyword2hsl,
772
+ keyword2hsv: keyword2hsv,
773
+ keyword2hwb: keyword2hwb,
774
+ keyword2cmyk: keyword2cmyk,
775
+ keyword2lab: keyword2lab,
776
+ keyword2xyz: keyword2xyz,
777
+
778
+ xyz2rgb: xyz2rgb,
779
+ xyz2lab: xyz2lab,
780
+ xyz2lch: xyz2lch,
781
+
782
+ lab2xyz: lab2xyz,
783
+ lab2rgb: lab2rgb,
784
+ lab2lch: lab2lch,
785
+
786
+ lch2lab: lch2lab,
787
+ lch2xyz: lch2xyz,
788
+ lch2rgb: lch2rgb
789
+ }
790
+
791
+
792
+ function rgb2hsl(rgb) {
793
+ var r = rgb[0]/255,
794
+ g = rgb[1]/255,
795
+ b = rgb[2]/255,
796
+ min = Math.min(r, g, b),
797
+ max = Math.max(r, g, b),
798
+ delta = max - min,
799
+ h, s, l;
800
+
801
+ if (max == min)
802
+ h = 0;
803
+ else if (r == max)
804
+ h = (g - b) / delta;
805
+ else if (g == max)
806
+ h = 2 + (b - r) / delta;
807
+ else if (b == max)
808
+ h = 4 + (r - g)/ delta;
809
+
810
+ h = Math.min(h * 60, 360);
811
+
812
+ if (h < 0)
813
+ h += 360;
814
+
815
+ l = (min + max) / 2;
816
+
817
+ if (max == min)
818
+ s = 0;
819
+ else if (l <= 0.5)
820
+ s = delta / (max + min);
821
+ else
822
+ s = delta / (2 - max - min);
823
+
824
+ return [h, s * 100, l * 100];
825
+ }
826
+
827
+ function rgb2hsv(rgb) {
828
+ var r = rgb[0],
829
+ g = rgb[1],
830
+ b = rgb[2],
831
+ min = Math.min(r, g, b),
832
+ max = Math.max(r, g, b),
833
+ delta = max - min,
834
+ h, s, v;
835
+
836
+ if (max == 0)
837
+ s = 0;
838
+ else
839
+ s = (delta/max * 1000)/10;
840
+
841
+ if (max == min)
842
+ h = 0;
843
+ else if (r == max)
844
+ h = (g - b) / delta;
845
+ else if (g == max)
846
+ h = 2 + (b - r) / delta;
847
+ else if (b == max)
848
+ h = 4 + (r - g) / delta;
849
+
850
+ h = Math.min(h * 60, 360);
851
+
852
+ if (h < 0)
853
+ h += 360;
854
+
855
+ v = ((max / 255) * 1000) / 10;
856
+
857
+ return [h, s, v];
858
+ }
859
+
860
+ function rgb2hwb(rgb) {
861
+ var r = rgb[0],
862
+ g = rgb[1],
863
+ b = rgb[2],
864
+ h = rgb2hsl(rgb)[0],
865
+ w = 1/255 * Math.min(r, Math.min(g, b)),
866
+ b = 1 - 1/255 * Math.max(r, Math.max(g, b));
867
+
868
+ return [h, w * 100, b * 100];
869
+ }
870
+
871
+ function rgb2cmyk(rgb) {
872
+ var r = rgb[0] / 255,
873
+ g = rgb[1] / 255,
874
+ b = rgb[2] / 255,
875
+ c, m, y, k;
876
+
877
+ k = Math.min(1 - r, 1 - g, 1 - b);
878
+ c = (1 - r - k) / (1 - k) || 0;
879
+ m = (1 - g - k) / (1 - k) || 0;
880
+ y = (1 - b - k) / (1 - k) || 0;
881
+ return [c * 100, m * 100, y * 100, k * 100];
882
+ }
883
+
884
+ function rgb2keyword(rgb) {
885
+ return reverseKeywords[JSON.stringify(rgb)];
886
+ }
887
+
888
+ function rgb2xyz(rgb) {
889
+ var r = rgb[0] / 255,
890
+ g = rgb[1] / 255,
891
+ b = rgb[2] / 255;
892
+
893
+ // assume sRGB
894
+ r = r > 0.04045 ? Math.pow(((r + 0.055) / 1.055), 2.4) : (r / 12.92);
895
+ g = g > 0.04045 ? Math.pow(((g + 0.055) / 1.055), 2.4) : (g / 12.92);
896
+ b = b > 0.04045 ? Math.pow(((b + 0.055) / 1.055), 2.4) : (b / 12.92);
897
+
898
+ var x = (r * 0.4124) + (g * 0.3576) + (b * 0.1805);
899
+ var y = (r * 0.2126) + (g * 0.7152) + (b * 0.0722);
900
+ var z = (r * 0.0193) + (g * 0.1192) + (b * 0.9505);
901
+
902
+ return [x * 100, y *100, z * 100];
903
+ }
904
+
905
+ function rgb2lab(rgb) {
906
+ var xyz = rgb2xyz(rgb),
907
+ x = xyz[0],
908
+ y = xyz[1],
909
+ z = xyz[2],
910
+ l, a, b;
911
+
912
+ x /= 95.047;
913
+ y /= 100;
914
+ z /= 108.883;
915
+
916
+ x = x > 0.008856 ? Math.pow(x, 1/3) : (7.787 * x) + (16 / 116);
917
+ y = y > 0.008856 ? Math.pow(y, 1/3) : (7.787 * y) + (16 / 116);
918
+ z = z > 0.008856 ? Math.pow(z, 1/3) : (7.787 * z) + (16 / 116);
919
+
920
+ l = (116 * y) - 16;
921
+ a = 500 * (x - y);
922
+ b = 200 * (y - z);
923
+
924
+ return [l, a, b];
925
+ }
926
+
927
+ function rgb2lch(args) {
928
+ return lab2lch(rgb2lab(args));
929
+ }
930
+
931
+ function hsl2rgb(hsl) {
932
+ var h = hsl[0] / 360,
933
+ s = hsl[1] / 100,
934
+ l = hsl[2] / 100,
935
+ t1, t2, t3, rgb, val;
936
+
937
+ if (s == 0) {
938
+ val = l * 255;
939
+ return [val, val, val];
940
+ }
941
+
942
+ if (l < 0.5)
943
+ t2 = l * (1 + s);
944
+ else
945
+ t2 = l + s - l * s;
946
+ t1 = 2 * l - t2;
947
+
948
+ rgb = [0, 0, 0];
949
+ for (var i = 0; i < 3; i++) {
950
+ t3 = h + 1 / 3 * - (i - 1);
951
+ t3 < 0 && t3++;
952
+ t3 > 1 && t3--;
953
+
954
+ if (6 * t3 < 1)
955
+ val = t1 + (t2 - t1) * 6 * t3;
956
+ else if (2 * t3 < 1)
957
+ val = t2;
958
+ else if (3 * t3 < 2)
959
+ val = t1 + (t2 - t1) * (2 / 3 - t3) * 6;
960
+ else
961
+ val = t1;
962
+
963
+ rgb[i] = val * 255;
964
+ }
965
+
966
+ return rgb;
967
+ }
968
+
969
+ function hsl2hsv(hsl) {
970
+ var h = hsl[0],
971
+ s = hsl[1] / 100,
972
+ l = hsl[2] / 100,
973
+ sv, v;
974
+
975
+ if(l === 0) {
976
+ // no need to do calc on black
977
+ // also avoids divide by 0 error
978
+ return [0, 0, 0];
979
+ }
980
+
981
+ l *= 2;
982
+ s *= (l <= 1) ? l : 2 - l;
983
+ v = (l + s) / 2;
984
+ sv = (2 * s) / (l + s);
985
+ return [h, sv * 100, v * 100];
986
+ }
987
+
988
+ function hsl2hwb(args) {
989
+ return rgb2hwb(hsl2rgb(args));
990
+ }
991
+
992
+ function hsl2cmyk(args) {
993
+ return rgb2cmyk(hsl2rgb(args));
994
+ }
995
+
996
+ function hsl2keyword(args) {
997
+ return rgb2keyword(hsl2rgb(args));
998
+ }
999
+
1000
+
1001
+ function hsv2rgb(hsv) {
1002
+ var h = hsv[0] / 60,
1003
+ s = hsv[1] / 100,
1004
+ v = hsv[2] / 100,
1005
+ hi = Math.floor(h) % 6;
1006
+
1007
+ var f = h - Math.floor(h),
1008
+ p = 255 * v * (1 - s),
1009
+ q = 255 * v * (1 - (s * f)),
1010
+ t = 255 * v * (1 - (s * (1 - f))),
1011
+ v = 255 * v;
1012
+
1013
+ switch(hi) {
1014
+ case 0:
1015
+ return [v, t, p];
1016
+ case 1:
1017
+ return [q, v, p];
1018
+ case 2:
1019
+ return [p, v, t];
1020
+ case 3:
1021
+ return [p, q, v];
1022
+ case 4:
1023
+ return [t, p, v];
1024
+ case 5:
1025
+ return [v, p, q];
1026
+ }
1027
+ }
1028
+
1029
+ function hsv2hsl(hsv) {
1030
+ var h = hsv[0],
1031
+ s = hsv[1] / 100,
1032
+ v = hsv[2] / 100,
1033
+ sl, l;
1034
+
1035
+ l = (2 - s) * v;
1036
+ sl = s * v;
1037
+ sl /= (l <= 1) ? l : 2 - l;
1038
+ sl = sl || 0;
1039
+ l /= 2;
1040
+ return [h, sl * 100, l * 100];
1041
+ }
1042
+
1043
+ function hsv2hwb(args) {
1044
+ return rgb2hwb(hsv2rgb(args))
1045
+ }
1046
+
1047
+ function hsv2cmyk(args) {
1048
+ return rgb2cmyk(hsv2rgb(args));
1049
+ }
1050
+
1051
+ function hsv2keyword(args) {
1052
+ return rgb2keyword(hsv2rgb(args));
1053
+ }
1054
+
1055
+ // http://dev.w3.org/csswg/css-color/#hwb-to-rgb
1056
+ function hwb2rgb(hwb) {
1057
+ var h = hwb[0] / 360,
1058
+ wh = hwb[1] / 100,
1059
+ bl = hwb[2] / 100,
1060
+ ratio = wh + bl,
1061
+ i, v, f, n;
1062
+
1063
+ // wh + bl cant be > 1
1064
+ if (ratio > 1) {
1065
+ wh /= ratio;
1066
+ bl /= ratio;
1067
+ }
1068
+
1069
+ i = Math.floor(6 * h);
1070
+ v = 1 - bl;
1071
+ f = 6 * h - i;
1072
+ if ((i & 0x01) != 0) {
1073
+ f = 1 - f;
1074
+ }
1075
+ n = wh + f * (v - wh); // linear interpolation
1076
+
1077
+ switch (i) {
1078
+ default:
1079
+ case 6:
1080
+ case 0: r = v; g = n; b = wh; break;
1081
+ case 1: r = n; g = v; b = wh; break;
1082
+ case 2: r = wh; g = v; b = n; break;
1083
+ case 3: r = wh; g = n; b = v; break;
1084
+ case 4: r = n; g = wh; b = v; break;
1085
+ case 5: r = v; g = wh; b = n; break;
1086
+ }
1087
+
1088
+ return [r * 255, g * 255, b * 255];
1089
+ }
1090
+
1091
+ function hwb2hsl(args) {
1092
+ return rgb2hsl(hwb2rgb(args));
1093
+ }
1094
+
1095
+ function hwb2hsv(args) {
1096
+ return rgb2hsv(hwb2rgb(args));
1097
+ }
1098
+
1099
+ function hwb2cmyk(args) {
1100
+ return rgb2cmyk(hwb2rgb(args));
1101
+ }
1102
+
1103
+ function hwb2keyword(args) {
1104
+ return rgb2keyword(hwb2rgb(args));
1105
+ }
1106
+
1107
+ function cmyk2rgb(cmyk) {
1108
+ var c = cmyk[0] / 100,
1109
+ m = cmyk[1] / 100,
1110
+ y = cmyk[2] / 100,
1111
+ k = cmyk[3] / 100,
1112
+ r, g, b;
1113
+
1114
+ r = 1 - Math.min(1, c * (1 - k) + k);
1115
+ g = 1 - Math.min(1, m * (1 - k) + k);
1116
+ b = 1 - Math.min(1, y * (1 - k) + k);
1117
+ return [r * 255, g * 255, b * 255];
1118
+ }
1119
+
1120
+ function cmyk2hsl(args) {
1121
+ return rgb2hsl(cmyk2rgb(args));
1122
+ }
1123
+
1124
+ function cmyk2hsv(args) {
1125
+ return rgb2hsv(cmyk2rgb(args));
1126
+ }
1127
+
1128
+ function cmyk2hwb(args) {
1129
+ return rgb2hwb(cmyk2rgb(args));
1130
+ }
1131
+
1132
+ function cmyk2keyword(args) {
1133
+ return rgb2keyword(cmyk2rgb(args));
1134
+ }
1135
+
1136
+
1137
+ function xyz2rgb(xyz) {
1138
+ var x = xyz[0] / 100,
1139
+ y = xyz[1] / 100,
1140
+ z = xyz[2] / 100,
1141
+ r, g, b;
1142
+
1143
+ r = (x * 3.2406) + (y * -1.5372) + (z * -0.4986);
1144
+ g = (x * -0.9689) + (y * 1.8758) + (z * 0.0415);
1145
+ b = (x * 0.0557) + (y * -0.2040) + (z * 1.0570);
1146
+
1147
+ // assume sRGB
1148
+ r = r > 0.0031308 ? ((1.055 * Math.pow(r, 1.0 / 2.4)) - 0.055)
1149
+ : r = (r * 12.92);
1150
+
1151
+ g = g > 0.0031308 ? ((1.055 * Math.pow(g, 1.0 / 2.4)) - 0.055)
1152
+ : g = (g * 12.92);
1153
+
1154
+ b = b > 0.0031308 ? ((1.055 * Math.pow(b, 1.0 / 2.4)) - 0.055)
1155
+ : b = (b * 12.92);
1156
+
1157
+ r = Math.min(Math.max(0, r), 1);
1158
+ g = Math.min(Math.max(0, g), 1);
1159
+ b = Math.min(Math.max(0, b), 1);
1160
+
1161
+ return [r * 255, g * 255, b * 255];
1162
+ }
1163
+
1164
+ function xyz2lab(xyz) {
1165
+ var x = xyz[0],
1166
+ y = xyz[1],
1167
+ z = xyz[2],
1168
+ l, a, b;
1169
+
1170
+ x /= 95.047;
1171
+ y /= 100;
1172
+ z /= 108.883;
1173
+
1174
+ x = x > 0.008856 ? Math.pow(x, 1/3) : (7.787 * x) + (16 / 116);
1175
+ y = y > 0.008856 ? Math.pow(y, 1/3) : (7.787 * y) + (16 / 116);
1176
+ z = z > 0.008856 ? Math.pow(z, 1/3) : (7.787 * z) + (16 / 116);
1177
+
1178
+ l = (116 * y) - 16;
1179
+ a = 500 * (x - y);
1180
+ b = 200 * (y - z);
1181
+
1182
+ return [l, a, b];
1183
+ }
1184
+
1185
+ function xyz2lch(args) {
1186
+ return lab2lch(xyz2lab(args));
1187
+ }
1188
+
1189
+ function lab2xyz(lab) {
1190
+ var l = lab[0],
1191
+ a = lab[1],
1192
+ b = lab[2],
1193
+ x, y, z, y2;
1194
+
1195
+ if (l <= 8) {
1196
+ y = (l * 100) / 903.3;
1197
+ y2 = (7.787 * (y / 100)) + (16 / 116);
1198
+ } else {
1199
+ y = 100 * Math.pow((l + 16) / 116, 3);
1200
+ y2 = Math.pow(y / 100, 1/3);
1201
+ }
1202
+
1203
+ x = x / 95.047 <= 0.008856 ? x = (95.047 * ((a / 500) + y2 - (16 / 116))) / 7.787 : 95.047 * Math.pow((a / 500) + y2, 3);
1204
+
1205
+ z = z / 108.883 <= 0.008859 ? z = (108.883 * (y2 - (b / 200) - (16 / 116))) / 7.787 : 108.883 * Math.pow(y2 - (b / 200), 3);
1206
+
1207
+ return [x, y, z];
1208
+ }
1209
+
1210
+ function lab2lch(lab) {
1211
+ var l = lab[0],
1212
+ a = lab[1],
1213
+ b = lab[2],
1214
+ hr, h, c;
1215
+
1216
+ hr = Math.atan2(b, a);
1217
+ h = hr * 360 / 2 / Math.PI;
1218
+ if (h < 0) {
1219
+ h += 360;
1220
+ }
1221
+ c = Math.sqrt(a * a + b * b);
1222
+ return [l, c, h];
1223
+ }
1224
+
1225
+ function lab2rgb(args) {
1226
+ return xyz2rgb(lab2xyz(args));
1227
+ }
1228
+
1229
+ function lch2lab(lch) {
1230
+ var l = lch[0],
1231
+ c = lch[1],
1232
+ h = lch[2],
1233
+ a, b, hr;
1234
+
1235
+ hr = h / 360 * 2 * Math.PI;
1236
+ a = c * Math.cos(hr);
1237
+ b = c * Math.sin(hr);
1238
+ return [l, a, b];
1239
+ }
1240
+
1241
+ function lch2xyz(args) {
1242
+ return lab2xyz(lch2lab(args));
1243
+ }
1244
+
1245
+ function lch2rgb(args) {
1246
+ return lab2rgb(lch2lab(args));
1247
+ }
1248
+
1249
+ function keyword2rgb(keyword) {
1250
+ return cssKeywords[keyword];
1251
+ }
1252
+
1253
+ function keyword2hsl(args) {
1254
+ return rgb2hsl(keyword2rgb(args));
1255
+ }
1256
+
1257
+ function keyword2hsv(args) {
1258
+ return rgb2hsv(keyword2rgb(args));
1259
+ }
1260
+
1261
+ function keyword2hwb(args) {
1262
+ return rgb2hwb(keyword2rgb(args));
1263
+ }
1264
+
1265
+ function keyword2cmyk(args) {
1266
+ return rgb2cmyk(keyword2rgb(args));
1267
+ }
1268
+
1269
+ function keyword2lab(args) {
1270
+ return rgb2lab(keyword2rgb(args));
1271
+ }
1272
+
1273
+ function keyword2xyz(args) {
1274
+ return rgb2xyz(keyword2rgb(args));
1275
+ }
1276
+
1277
+ var cssKeywords = {
1278
+ aliceblue: [240,248,255],
1279
+ antiquewhite: [250,235,215],
1280
+ aqua: [0,255,255],
1281
+ aquamarine: [127,255,212],
1282
+ azure: [240,255,255],
1283
+ beige: [245,245,220],
1284
+ bisque: [255,228,196],
1285
+ black: [0,0,0],
1286
+ blanchedalmond: [255,235,205],
1287
+ blue: [0,0,255],
1288
+ blueviolet: [138,43,226],
1289
+ brown: [165,42,42],
1290
+ burlywood: [222,184,135],
1291
+ cadetblue: [95,158,160],
1292
+ chartreuse: [127,255,0],
1293
+ chocolate: [210,105,30],
1294
+ coral: [255,127,80],
1295
+ cornflowerblue: [100,149,237],
1296
+ cornsilk: [255,248,220],
1297
+ crimson: [220,20,60],
1298
+ cyan: [0,255,255],
1299
+ darkblue: [0,0,139],
1300
+ darkcyan: [0,139,139],
1301
+ darkgoldenrod: [184,134,11],
1302
+ darkgray: [169,169,169],
1303
+ darkgreen: [0,100,0],
1304
+ darkgrey: [169,169,169],
1305
+ darkkhaki: [189,183,107],
1306
+ darkmagenta: [139,0,139],
1307
+ darkolivegreen: [85,107,47],
1308
+ darkorange: [255,140,0],
1309
+ darkorchid: [153,50,204],
1310
+ darkred: [139,0,0],
1311
+ darksalmon: [233,150,122],
1312
+ darkseagreen: [143,188,143],
1313
+ darkslateblue: [72,61,139],
1314
+ darkslategray: [47,79,79],
1315
+ darkslategrey: [47,79,79],
1316
+ darkturquoise: [0,206,209],
1317
+ darkviolet: [148,0,211],
1318
+ deeppink: [255,20,147],
1319
+ deepskyblue: [0,191,255],
1320
+ dimgray: [105,105,105],
1321
+ dimgrey: [105,105,105],
1322
+ dodgerblue: [30,144,255],
1323
+ firebrick: [178,34,34],
1324
+ floralwhite: [255,250,240],
1325
+ forestgreen: [34,139,34],
1326
+ fuchsia: [255,0,255],
1327
+ gainsboro: [220,220,220],
1328
+ ghostwhite: [248,248,255],
1329
+ gold: [255,215,0],
1330
+ goldenrod: [218,165,32],
1331
+ gray: [128,128,128],
1332
+ green: [0,128,0],
1333
+ greenyellow: [173,255,47],
1334
+ grey: [128,128,128],
1335
+ honeydew: [240,255,240],
1336
+ hotpink: [255,105,180],
1337
+ indianred: [205,92,92],
1338
+ indigo: [75,0,130],
1339
+ ivory: [255,255,240],
1340
+ khaki: [240,230,140],
1341
+ lavender: [230,230,250],
1342
+ lavenderblush: [255,240,245],
1343
+ lawngreen: [124,252,0],
1344
+ lemonchiffon: [255,250,205],
1345
+ lightblue: [173,216,230],
1346
+ lightcoral: [240,128,128],
1347
+ lightcyan: [224,255,255],
1348
+ lightgoldenrodyellow: [250,250,210],
1349
+ lightgray: [211,211,211],
1350
+ lightgreen: [144,238,144],
1351
+ lightgrey: [211,211,211],
1352
+ lightpink: [255,182,193],
1353
+ lightsalmon: [255,160,122],
1354
+ lightseagreen: [32,178,170],
1355
+ lightskyblue: [135,206,250],
1356
+ lightslategray: [119,136,153],
1357
+ lightslategrey: [119,136,153],
1358
+ lightsteelblue: [176,196,222],
1359
+ lightyellow: [255,255,224],
1360
+ lime: [0,255,0],
1361
+ limegreen: [50,205,50],
1362
+ linen: [250,240,230],
1363
+ magenta: [255,0,255],
1364
+ maroon: [128,0,0],
1365
+ mediumaquamarine: [102,205,170],
1366
+ mediumblue: [0,0,205],
1367
+ mediumorchid: [186,85,211],
1368
+ mediumpurple: [147,112,219],
1369
+ mediumseagreen: [60,179,113],
1370
+ mediumslateblue: [123,104,238],
1371
+ mediumspringgreen: [0,250,154],
1372
+ mediumturquoise: [72,209,204],
1373
+ mediumvioletred: [199,21,133],
1374
+ midnightblue: [25,25,112],
1375
+ mintcream: [245,255,250],
1376
+ mistyrose: [255,228,225],
1377
+ moccasin: [255,228,181],
1378
+ navajowhite: [255,222,173],
1379
+ navy: [0,0,128],
1380
+ oldlace: [253,245,230],
1381
+ olive: [128,128,0],
1382
+ olivedrab: [107,142,35],
1383
+ orange: [255,165,0],
1384
+ orangered: [255,69,0],
1385
+ orchid: [218,112,214],
1386
+ palegoldenrod: [238,232,170],
1387
+ palegreen: [152,251,152],
1388
+ paleturquoise: [175,238,238],
1389
+ palevioletred: [219,112,147],
1390
+ papayawhip: [255,239,213],
1391
+ peachpuff: [255,218,185],
1392
+ peru: [205,133,63],
1393
+ pink: [255,192,203],
1394
+ plum: [221,160,221],
1395
+ powderblue: [176,224,230],
1396
+ purple: [128,0,128],
1397
+ rebeccapurple: [102, 51, 153],
1398
+ red: [255,0,0],
1399
+ rosybrown: [188,143,143],
1400
+ royalblue: [65,105,225],
1401
+ saddlebrown: [139,69,19],
1402
+ salmon: [250,128,114],
1403
+ sandybrown: [244,164,96],
1404
+ seagreen: [46,139,87],
1405
+ seashell: [255,245,238],
1406
+ sienna: [160,82,45],
1407
+ silver: [192,192,192],
1408
+ skyblue: [135,206,235],
1409
+ slateblue: [106,90,205],
1410
+ slategray: [112,128,144],
1411
+ slategrey: [112,128,144],
1412
+ snow: [255,250,250],
1413
+ springgreen: [0,255,127],
1414
+ steelblue: [70,130,180],
1415
+ tan: [210,180,140],
1416
+ teal: [0,128,128],
1417
+ thistle: [216,191,216],
1418
+ tomato: [255,99,71],
1419
+ turquoise: [64,224,208],
1420
+ violet: [238,130,238],
1421
+ wheat: [245,222,179],
1422
+ white: [255,255,255],
1423
+ whitesmoke: [245,245,245],
1424
+ yellow: [255,255,0],
1425
+ yellowgreen: [154,205,50]
1426
+ };
1427
+
1428
+ var reverseKeywords = {};
1429
+ for (var key in cssKeywords) {
1430
+ reverseKeywords[JSON.stringify(cssKeywords[key])] = key;
1431
+ }
1432
+
1433
+ },{}],5:[function(require,module,exports){
1434
+ var conversions = require(4);
1435
+
1436
+ var convert = function() {
1437
+ return new Converter();
1438
+ }
1439
+
1440
+ for (var func in conversions) {
1441
+ // export Raw versions
1442
+ convert[func + "Raw"] = (function(func) {
1443
+ // accept array or plain args
1444
+ return function(arg) {
1445
+ if (typeof arg == "number")
1446
+ arg = Array.prototype.slice.call(arguments);
1447
+ return conversions[func](arg);
1448
+ }
1449
+ })(func);
1450
+
1451
+ var pair = /(\w+)2(\w+)/.exec(func),
1452
+ from = pair[1],
1453
+ to = pair[2];
1454
+
1455
+ // export rgb2hsl and ["rgb"]["hsl"]
1456
+ convert[from] = convert[from] || {};
1457
+
1458
+ convert[from][to] = convert[func] = (function(func) {
1459
+ return function(arg) {
1460
+ if (typeof arg == "number")
1461
+ arg = Array.prototype.slice.call(arguments);
1462
+
1463
+ var val = conversions[func](arg);
1464
+ if (typeof val == "string" || val === undefined)
1465
+ return val; // keyword
1466
+
1467
+ for (var i = 0; i < val.length; i++)
1468
+ val[i] = Math.round(val[i]);
1469
+ return val;
1470
+ }
1471
+ })(func);
1472
+ }
1473
+
1474
+
1475
+ /* Converter does lazy conversion and caching */
1476
+ var Converter = function() {
1477
+ this.convs = {};
1478
+ };
1479
+
1480
+ /* Either get the values for a space or
1481
+ set the values for a space, depending on args */
1482
+ Converter.prototype.routeSpace = function(space, args) {
1483
+ var values = args[0];
1484
+ if (values === undefined) {
1485
+ // color.rgb()
1486
+ return this.getValues(space);
1487
+ }
1488
+ // color.rgb(10, 10, 10)
1489
+ if (typeof values == "number") {
1490
+ values = Array.prototype.slice.call(args);
1491
+ }
1492
+
1493
+ return this.setValues(space, values);
1494
+ };
1495
+
1496
+ /* Set the values for a space, invalidating cache */
1497
+ Converter.prototype.setValues = function(space, values) {
1498
+ this.space = space;
1499
+ this.convs = {};
1500
+ this.convs[space] = values;
1501
+ return this;
1502
+ };
1503
+
1504
+ /* Get the values for a space. If there's already
1505
+ a conversion for the space, fetch it, otherwise
1506
+ compute it */
1507
+ Converter.prototype.getValues = function(space) {
1508
+ var vals = this.convs[space];
1509
+ if (!vals) {
1510
+ var fspace = this.space,
1511
+ from = this.convs[fspace];
1512
+ vals = convert[fspace][space](from);
1513
+
1514
+ this.convs[space] = vals;
1515
+ }
1516
+ return vals;
1517
+ };
1518
+
1519
+ ["rgb", "hsl", "hsv", "cmyk", "keyword"].forEach(function(space) {
1520
+ Converter.prototype[space] = function(vals) {
1521
+ return this.routeSpace(space, arguments);
1522
+ }
1523
+ });
1524
+
1525
+ module.exports = convert;
1526
+ },{"4":4}],6:[function(require,module,exports){
1527
+ 'use strict'
1528
+
1529
+ module.exports = {
1530
+ "aliceblue": [240, 248, 255],
1531
+ "antiquewhite": [250, 235, 215],
1532
+ "aqua": [0, 255, 255],
1533
+ "aquamarine": [127, 255, 212],
1534
+ "azure": [240, 255, 255],
1535
+ "beige": [245, 245, 220],
1536
+ "bisque": [255, 228, 196],
1537
+ "black": [0, 0, 0],
1538
+ "blanchedalmond": [255, 235, 205],
1539
+ "blue": [0, 0, 255],
1540
+ "blueviolet": [138, 43, 226],
1541
+ "brown": [165, 42, 42],
1542
+ "burlywood": [222, 184, 135],
1543
+ "cadetblue": [95, 158, 160],
1544
+ "chartreuse": [127, 255, 0],
1545
+ "chocolate": [210, 105, 30],
1546
+ "coral": [255, 127, 80],
1547
+ "cornflowerblue": [100, 149, 237],
1548
+ "cornsilk": [255, 248, 220],
1549
+ "crimson": [220, 20, 60],
1550
+ "cyan": [0, 255, 255],
1551
+ "darkblue": [0, 0, 139],
1552
+ "darkcyan": [0, 139, 139],
1553
+ "darkgoldenrod": [184, 134, 11],
1554
+ "darkgray": [169, 169, 169],
1555
+ "darkgreen": [0, 100, 0],
1556
+ "darkgrey": [169, 169, 169],
1557
+ "darkkhaki": [189, 183, 107],
1558
+ "darkmagenta": [139, 0, 139],
1559
+ "darkolivegreen": [85, 107, 47],
1560
+ "darkorange": [255, 140, 0],
1561
+ "darkorchid": [153, 50, 204],
1562
+ "darkred": [139, 0, 0],
1563
+ "darksalmon": [233, 150, 122],
1564
+ "darkseagreen": [143, 188, 143],
1565
+ "darkslateblue": [72, 61, 139],
1566
+ "darkslategray": [47, 79, 79],
1567
+ "darkslategrey": [47, 79, 79],
1568
+ "darkturquoise": [0, 206, 209],
1569
+ "darkviolet": [148, 0, 211],
1570
+ "deeppink": [255, 20, 147],
1571
+ "deepskyblue": [0, 191, 255],
1572
+ "dimgray": [105, 105, 105],
1573
+ "dimgrey": [105, 105, 105],
1574
+ "dodgerblue": [30, 144, 255],
1575
+ "firebrick": [178, 34, 34],
1576
+ "floralwhite": [255, 250, 240],
1577
+ "forestgreen": [34, 139, 34],
1578
+ "fuchsia": [255, 0, 255],
1579
+ "gainsboro": [220, 220, 220],
1580
+ "ghostwhite": [248, 248, 255],
1581
+ "gold": [255, 215, 0],
1582
+ "goldenrod": [218, 165, 32],
1583
+ "gray": [128, 128, 128],
1584
+ "green": [0, 128, 0],
1585
+ "greenyellow": [173, 255, 47],
1586
+ "grey": [128, 128, 128],
1587
+ "honeydew": [240, 255, 240],
1588
+ "hotpink": [255, 105, 180],
1589
+ "indianred": [205, 92, 92],
1590
+ "indigo": [75, 0, 130],
1591
+ "ivory": [255, 255, 240],
1592
+ "khaki": [240, 230, 140],
1593
+ "lavender": [230, 230, 250],
1594
+ "lavenderblush": [255, 240, 245],
1595
+ "lawngreen": [124, 252, 0],
1596
+ "lemonchiffon": [255, 250, 205],
1597
+ "lightblue": [173, 216, 230],
1598
+ "lightcoral": [240, 128, 128],
1599
+ "lightcyan": [224, 255, 255],
1600
+ "lightgoldenrodyellow": [250, 250, 210],
1601
+ "lightgray": [211, 211, 211],
1602
+ "lightgreen": [144, 238, 144],
1603
+ "lightgrey": [211, 211, 211],
1604
+ "lightpink": [255, 182, 193],
1605
+ "lightsalmon": [255, 160, 122],
1606
+ "lightseagreen": [32, 178, 170],
1607
+ "lightskyblue": [135, 206, 250],
1608
+ "lightslategray": [119, 136, 153],
1609
+ "lightslategrey": [119, 136, 153],
1610
+ "lightsteelblue": [176, 196, 222],
1611
+ "lightyellow": [255, 255, 224],
1612
+ "lime": [0, 255, 0],
1613
+ "limegreen": [50, 205, 50],
1614
+ "linen": [250, 240, 230],
1615
+ "magenta": [255, 0, 255],
1616
+ "maroon": [128, 0, 0],
1617
+ "mediumaquamarine": [102, 205, 170],
1618
+ "mediumblue": [0, 0, 205],
1619
+ "mediumorchid": [186, 85, 211],
1620
+ "mediumpurple": [147, 112, 219],
1621
+ "mediumseagreen": [60, 179, 113],
1622
+ "mediumslateblue": [123, 104, 238],
1623
+ "mediumspringgreen": [0, 250, 154],
1624
+ "mediumturquoise": [72, 209, 204],
1625
+ "mediumvioletred": [199, 21, 133],
1626
+ "midnightblue": [25, 25, 112],
1627
+ "mintcream": [245, 255, 250],
1628
+ "mistyrose": [255, 228, 225],
1629
+ "moccasin": [255, 228, 181],
1630
+ "navajowhite": [255, 222, 173],
1631
+ "navy": [0, 0, 128],
1632
+ "oldlace": [253, 245, 230],
1633
+ "olive": [128, 128, 0],
1634
+ "olivedrab": [107, 142, 35],
1635
+ "orange": [255, 165, 0],
1636
+ "orangered": [255, 69, 0],
1637
+ "orchid": [218, 112, 214],
1638
+ "palegoldenrod": [238, 232, 170],
1639
+ "palegreen": [152, 251, 152],
1640
+ "paleturquoise": [175, 238, 238],
1641
+ "palevioletred": [219, 112, 147],
1642
+ "papayawhip": [255, 239, 213],
1643
+ "peachpuff": [255, 218, 185],
1644
+ "peru": [205, 133, 63],
1645
+ "pink": [255, 192, 203],
1646
+ "plum": [221, 160, 221],
1647
+ "powderblue": [176, 224, 230],
1648
+ "purple": [128, 0, 128],
1649
+ "rebeccapurple": [102, 51, 153],
1650
+ "red": [255, 0, 0],
1651
+ "rosybrown": [188, 143, 143],
1652
+ "royalblue": [65, 105, 225],
1653
+ "saddlebrown": [139, 69, 19],
1654
+ "salmon": [250, 128, 114],
1655
+ "sandybrown": [244, 164, 96],
1656
+ "seagreen": [46, 139, 87],
1657
+ "seashell": [255, 245, 238],
1658
+ "sienna": [160, 82, 45],
1659
+ "silver": [192, 192, 192],
1660
+ "skyblue": [135, 206, 235],
1661
+ "slateblue": [106, 90, 205],
1662
+ "slategray": [112, 128, 144],
1663
+ "slategrey": [112, 128, 144],
1664
+ "snow": [255, 250, 250],
1665
+ "springgreen": [0, 255, 127],
1666
+ "steelblue": [70, 130, 180],
1667
+ "tan": [210, 180, 140],
1668
+ "teal": [0, 128, 128],
1669
+ "thistle": [216, 191, 216],
1670
+ "tomato": [255, 99, 71],
1671
+ "turquoise": [64, 224, 208],
1672
+ "violet": [238, 130, 238],
1673
+ "wheat": [245, 222, 179],
1674
+ "white": [255, 255, 255],
1675
+ "whitesmoke": [245, 245, 245],
1676
+ "yellow": [255, 255, 0],
1677
+ "yellowgreen": [154, 205, 50]
1678
+ };
1679
+
1680
+ },{}],7:[function(require,module,exports){
1681
+ /**
1682
+ * @namespace Chart
1683
+ */
1684
+ var Chart = require(29)();
1685
+
1686
+ Chart.helpers = require(45);
1687
+
1688
+ // @todo dispatch these helpers into appropriated helpers/helpers.* file and write unit tests!
1689
+ require(27)(Chart);
1690
+
1691
+ Chart.defaults = require(25);
1692
+ Chart.Element = require(26);
1693
+ Chart.elements = require(40);
1694
+ Chart.Interaction = require(28);
1695
+ Chart.platform = require(48);
1696
+
1697
+ require(31)(Chart);
1698
+ require(22)(Chart);
1699
+ require(23)(Chart);
1700
+ require(24)(Chart);
1701
+ require(30)(Chart);
1702
+ require(33)(Chart);
1703
+ require(32)(Chart);
1704
+ require(35)(Chart);
1705
+
1706
+ require(54)(Chart);
1707
+ require(52)(Chart);
1708
+ require(53)(Chart);
1709
+ require(55)(Chart);
1710
+ require(56)(Chart);
1711
+ require(57)(Chart);
1712
+
1713
+ // Controllers must be loaded after elements
1714
+ // See Chart.core.datasetController.dataElementType
1715
+ require(15)(Chart);
1716
+ require(16)(Chart);
1717
+ require(17)(Chart);
1718
+ require(18)(Chart);
1719
+ require(19)(Chart);
1720
+ require(20)(Chart);
1721
+ require(21)(Chart);
1722
+
1723
+ require(8)(Chart);
1724
+ require(9)(Chart);
1725
+ require(10)(Chart);
1726
+ require(11)(Chart);
1727
+ require(12)(Chart);
1728
+ require(13)(Chart);
1729
+ require(14)(Chart);
1730
+
1731
+ // Loading built-it plugins
1732
+ var plugins = [];
1733
+
1734
+ plugins.push(
1735
+ require(49)(Chart),
1736
+ require(50)(Chart),
1737
+ require(51)(Chart)
1738
+ );
1739
+
1740
+ Chart.plugins.register(plugins);
1741
+
1742
+ Chart.platform.initialize();
1743
+
1744
+ module.exports = Chart;
1745
+ if (typeof window !== 'undefined') {
1746
+ window.Chart = Chart;
1747
+ }
1748
+
1749
+ // DEPRECATIONS
1750
+
1751
+ /**
1752
+ * Provided for backward compatibility, use Chart.helpers.canvas instead.
1753
+ * @namespace Chart.canvasHelpers
1754
+ * @deprecated since version 2.6.0
1755
+ * @todo remove at version 3
1756
+ * @private
1757
+ */
1758
+ Chart.canvasHelpers = Chart.helpers.canvas;
1759
+
1760
+ },{"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,"35":35,"40":40,"45":45,"48":48,"49":49,"50":50,"51":51,"52":52,"53":53,"54":54,"55":55,"56":56,"57":57,"8":8,"9":9}],8:[function(require,module,exports){
1761
+ 'use strict';
1762
+
1763
+ module.exports = function(Chart) {
1764
+
1765
+ Chart.Bar = function(context, config) {
1766
+ config.type = 'bar';
1767
+
1768
+ return new Chart(context, config);
1769
+ };
1770
+
1771
+ };
1772
+
1773
+ },{}],9:[function(require,module,exports){
1774
+ 'use strict';
1775
+
1776
+ module.exports = function(Chart) {
1777
+
1778
+ Chart.Bubble = function(context, config) {
1779
+ config.type = 'bubble';
1780
+ return new Chart(context, config);
1781
+ };
1782
+
1783
+ };
1784
+
1785
+ },{}],10:[function(require,module,exports){
1786
+ 'use strict';
1787
+
1788
+ module.exports = function(Chart) {
1789
+
1790
+ Chart.Doughnut = function(context, config) {
1791
+ config.type = 'doughnut';
1792
+
1793
+ return new Chart(context, config);
1794
+ };
1795
+
1796
+ };
1797
+
1798
+ },{}],11:[function(require,module,exports){
1799
+ 'use strict';
1800
+
1801
+ module.exports = function(Chart) {
1802
+
1803
+ Chart.Line = function(context, config) {
1804
+ config.type = 'line';
1805
+
1806
+ return new Chart(context, config);
1807
+ };
1808
+
1809
+ };
1810
+
1811
+ },{}],12:[function(require,module,exports){
1812
+ 'use strict';
1813
+
1814
+ module.exports = function(Chart) {
1815
+
1816
+ Chart.PolarArea = function(context, config) {
1817
+ config.type = 'polarArea';
1818
+
1819
+ return new Chart(context, config);
1820
+ };
1821
+
1822
+ };
1823
+
1824
+ },{}],13:[function(require,module,exports){
1825
+ 'use strict';
1826
+
1827
+ module.exports = function(Chart) {
1828
+
1829
+ Chart.Radar = function(context, config) {
1830
+ config.type = 'radar';
1831
+
1832
+ return new Chart(context, config);
1833
+ };
1834
+
1835
+ };
1836
+
1837
+ },{}],14:[function(require,module,exports){
1838
+ 'use strict';
1839
+
1840
+ module.exports = function(Chart) {
1841
+ Chart.Scatter = function(context, config) {
1842
+ config.type = 'scatter';
1843
+ return new Chart(context, config);
1844
+ };
1845
+ };
1846
+
1847
+ },{}],15:[function(require,module,exports){
1848
+ 'use strict';
1849
+
1850
+ var defaults = require(25);
1851
+ var elements = require(40);
1852
+ var helpers = require(45);
1853
+
1854
+ defaults._set('bar', {
1855
+ hover: {
1856
+ mode: 'label'
1857
+ },
1858
+
1859
+ scales: {
1860
+ xAxes: [{
1861
+ type: 'category',
1862
+
1863
+ // Specific to Bar Controller
1864
+ categoryPercentage: 0.8,
1865
+ barPercentage: 0.9,
1866
+
1867
+ // offset settings
1868
+ offset: true,
1869
+
1870
+ // grid line settings
1871
+ gridLines: {
1872
+ offsetGridLines: true
1873
+ }
1874
+ }],
1875
+
1876
+ yAxes: [{
1877
+ type: 'linear'
1878
+ }]
1879
+ }
1880
+ });
1881
+
1882
+ defaults._set('horizontalBar', {
1883
+ hover: {
1884
+ mode: 'index',
1885
+ axis: 'y'
1886
+ },
1887
+
1888
+ scales: {
1889
+ xAxes: [{
1890
+ type: 'linear',
1891
+ position: 'bottom'
1892
+ }],
1893
+
1894
+ yAxes: [{
1895
+ position: 'left',
1896
+ type: 'category',
1897
+
1898
+ // Specific to Horizontal Bar Controller
1899
+ categoryPercentage: 0.8,
1900
+ barPercentage: 0.9,
1901
+
1902
+ // offset settings
1903
+ offset: true,
1904
+
1905
+ // grid line settings
1906
+ gridLines: {
1907
+ offsetGridLines: true
1908
+ }
1909
+ }]
1910
+ },
1911
+
1912
+ elements: {
1913
+ rectangle: {
1914
+ borderSkipped: 'left'
1915
+ }
1916
+ },
1917
+
1918
+ tooltips: {
1919
+ callbacks: {
1920
+ title: function(item, data) {
1921
+ // Pick first xLabel for now
1922
+ var title = '';
1923
+
1924
+ if (item.length > 0) {
1925
+ if (item[0].yLabel) {
1926
+ title = item[0].yLabel;
1927
+ } else if (data.labels.length > 0 && item[0].index < data.labels.length) {
1928
+ title = data.labels[item[0].index];
1929
+ }
1930
+ }
1931
+
1932
+ return title;
1933
+ },
1934
+
1935
+ label: function(item, data) {
1936
+ var datasetLabel = data.datasets[item.datasetIndex].label || '';
1937
+ return datasetLabel + ': ' + item.xLabel;
1938
+ }
1939
+ },
1940
+ mode: 'index',
1941
+ axis: 'y'
1942
+ }
1943
+ });
1944
+
1945
+ module.exports = function(Chart) {
1946
+
1947
+ Chart.controllers.bar = Chart.DatasetController.extend({
1948
+
1949
+ dataElementType: elements.Rectangle,
1950
+
1951
+ initialize: function() {
1952
+ var me = this;
1953
+ var meta;
1954
+
1955
+ Chart.DatasetController.prototype.initialize.apply(me, arguments);
1956
+
1957
+ meta = me.getMeta();
1958
+ meta.stack = me.getDataset().stack;
1959
+ meta.bar = true;
1960
+ },
1961
+
1962
+ update: function(reset) {
1963
+ var me = this;
1964
+ var rects = me.getMeta().data;
1965
+ var i, ilen;
1966
+
1967
+ me._ruler = me.getRuler();
1968
+
1969
+ for (i = 0, ilen = rects.length; i < ilen; ++i) {
1970
+ me.updateElement(rects[i], i, reset);
1971
+ }
1972
+ },
1973
+
1974
+ updateElement: function(rectangle, index, reset) {
1975
+ var me = this;
1976
+ var chart = me.chart;
1977
+ var meta = me.getMeta();
1978
+ var dataset = me.getDataset();
1979
+ var custom = rectangle.custom || {};
1980
+ var rectangleOptions = chart.options.elements.rectangle;
1981
+
1982
+ rectangle._xScale = me.getScaleForId(meta.xAxisID);
1983
+ rectangle._yScale = me.getScaleForId(meta.yAxisID);
1984
+ rectangle._datasetIndex = me.index;
1985
+ rectangle._index = index;
1986
+
1987
+ rectangle._model = {
1988
+ datasetLabel: dataset.label,
1989
+ label: chart.data.labels[index],
1990
+ borderSkipped: custom.borderSkipped ? custom.borderSkipped : rectangleOptions.borderSkipped,
1991
+ backgroundColor: custom.backgroundColor ? custom.backgroundColor : helpers.valueAtIndexOrDefault(dataset.backgroundColor, index, rectangleOptions.backgroundColor),
1992
+ borderColor: custom.borderColor ? custom.borderColor : helpers.valueAtIndexOrDefault(dataset.borderColor, index, rectangleOptions.borderColor),
1993
+ borderWidth: custom.borderWidth ? custom.borderWidth : helpers.valueAtIndexOrDefault(dataset.borderWidth, index, rectangleOptions.borderWidth)
1994
+ };
1995
+
1996
+ me.updateElementGeometry(rectangle, index, reset);
1997
+
1998
+ rectangle.pivot();
1999
+ },
2000
+
2001
+ /**
2002
+ * @private
2003
+ */
2004
+ updateElementGeometry: function(rectangle, index, reset) {
2005
+ var me = this;
2006
+ var model = rectangle._model;
2007
+ var vscale = me.getValueScale();
2008
+ var base = vscale.getBasePixel();
2009
+ var horizontal = vscale.isHorizontal();
2010
+ var ruler = me._ruler || me.getRuler();
2011
+ var vpixels = me.calculateBarValuePixels(me.index, index);
2012
+ var ipixels = me.calculateBarIndexPixels(me.index, index, ruler);
2013
+
2014
+ model.horizontal = horizontal;
2015
+ model.base = reset ? base : vpixels.base;
2016
+ model.x = horizontal ? reset ? base : vpixels.head : ipixels.center;
2017
+ model.y = horizontal ? ipixels.center : reset ? base : vpixels.head;
2018
+ model.height = horizontal ? ipixels.size : undefined;
2019
+ model.width = horizontal ? undefined : ipixels.size;
2020
+ },
2021
+
2022
+ /**
2023
+ * @private
2024
+ */
2025
+ getValueScaleId: function() {
2026
+ return this.getMeta().yAxisID;
2027
+ },
2028
+
2029
+ /**
2030
+ * @private
2031
+ */
2032
+ getIndexScaleId: function() {
2033
+ return this.getMeta().xAxisID;
2034
+ },
2035
+
2036
+ /**
2037
+ * @private
2038
+ */
2039
+ getValueScale: function() {
2040
+ return this.getScaleForId(this.getValueScaleId());
2041
+ },
2042
+
2043
+ /**
2044
+ * @private
2045
+ */
2046
+ getIndexScale: function() {
2047
+ return this.getScaleForId(this.getIndexScaleId());
2048
+ },
2049
+
2050
+ /**
2051
+ * Returns the effective number of stacks based on groups and bar visibility.
2052
+ * @private
2053
+ */
2054
+ getStackCount: function(last) {
2055
+ var me = this;
2056
+ var chart = me.chart;
2057
+ var scale = me.getIndexScale();
2058
+ var stacked = scale.options.stacked;
2059
+ var ilen = last === undefined ? chart.data.datasets.length : last + 1;
2060
+ var stacks = [];
2061
+ var i, meta;
2062
+
2063
+ for (i = 0; i < ilen; ++i) {
2064
+ meta = chart.getDatasetMeta(i);
2065
+ if (meta.bar && chart.isDatasetVisible(i) &&
2066
+ (stacked === false ||
2067
+ (stacked === true && stacks.indexOf(meta.stack) === -1) ||
2068
+ (stacked === undefined && (meta.stack === undefined || stacks.indexOf(meta.stack) === -1)))) {
2069
+ stacks.push(meta.stack);
2070
+ }
2071
+ }
2072
+
2073
+ return stacks.length;
2074
+ },
2075
+
2076
+ /**
2077
+ * Returns the stack index for the given dataset based on groups and bar visibility.
2078
+ * @private
2079
+ */
2080
+ getStackIndex: function(datasetIndex) {
2081
+ return this.getStackCount(datasetIndex) - 1;
2082
+ },
2083
+
2084
+ /**
2085
+ * @private
2086
+ */
2087
+ getRuler: function() {
2088
+ var me = this;
2089
+ var scale = me.getIndexScale();
2090
+ var stackCount = me.getStackCount();
2091
+ var datasetIndex = me.index;
2092
+ var pixels = [];
2093
+ var isHorizontal = scale.isHorizontal();
2094
+ var start = isHorizontal ? scale.left : scale.top;
2095
+ var end = start + (isHorizontal ? scale.width : scale.height);
2096
+ var i, ilen;
2097
+
2098
+ for (i = 0, ilen = me.getMeta().data.length; i < ilen; ++i) {
2099
+ pixels.push(scale.getPixelForValue(null, i, datasetIndex));
2100
+ }
2101
+
2102
+ return {
2103
+ pixels: pixels,
2104
+ start: start,
2105
+ end: end,
2106
+ stackCount: stackCount,
2107
+ scale: scale
2108
+ };
2109
+ },
2110
+
2111
+ /**
2112
+ * Note: pixel values are not clamped to the scale area.
2113
+ * @private
2114
+ */
2115
+ calculateBarValuePixels: function(datasetIndex, index) {
2116
+ var me = this;
2117
+ var chart = me.chart;
2118
+ var meta = me.getMeta();
2119
+ var scale = me.getValueScale();
2120
+ var datasets = chart.data.datasets;
2121
+ var value = scale.getRightValue(datasets[datasetIndex].data[index]);
2122
+ var stacked = scale.options.stacked;
2123
+ var stack = meta.stack;
2124
+ var start = 0;
2125
+ var i, imeta, ivalue, base, head, size;
2126
+
2127
+ if (stacked || (stacked === undefined && stack !== undefined)) {
2128
+ for (i = 0; i < datasetIndex; ++i) {
2129
+ imeta = chart.getDatasetMeta(i);
2130
+
2131
+ if (imeta.bar &&
2132
+ imeta.stack === stack &&
2133
+ imeta.controller.getValueScaleId() === scale.id &&
2134
+ chart.isDatasetVisible(i)) {
2135
+
2136
+ ivalue = scale.getRightValue(datasets[i].data[index]);
2137
+ if ((value < 0 && ivalue < 0) || (value >= 0 && ivalue > 0)) {
2138
+ start += ivalue;
2139
+ }
2140
+ }
2141
+ }
2142
+ }
2143
+
2144
+ base = scale.getPixelForValue(start);
2145
+ head = scale.getPixelForValue(start + value);
2146
+ size = (head - base) / 2;
2147
+
2148
+ return {
2149
+ size: size,
2150
+ base: base,
2151
+ head: head,
2152
+ center: head + size / 2
2153
+ };
2154
+ },
2155
+
2156
+ /**
2157
+ * @private
2158
+ */
2159
+ calculateBarIndexPixels: function(datasetIndex, index, ruler) {
2160
+ var me = this;
2161
+ var options = ruler.scale.options;
2162
+ var stackIndex = me.getStackIndex(datasetIndex);
2163
+ var pixels = ruler.pixels;
2164
+ var base = pixels[index];
2165
+ var length = pixels.length;
2166
+ var start = ruler.start;
2167
+ var end = ruler.end;
2168
+ var leftSampleSize, rightSampleSize, leftCategorySize, rightCategorySize, fullBarSize, size;
2169
+
2170
+ if (length === 1) {
2171
+ leftSampleSize = base > start ? base - start : end - base;
2172
+ rightSampleSize = base < end ? end - base : base - start;
2173
+ } else {
2174
+ if (index > 0) {
2175
+ leftSampleSize = (base - pixels[index - 1]) / 2;
2176
+ if (index === length - 1) {
2177
+ rightSampleSize = leftSampleSize;
2178
+ }
2179
+ }
2180
+ if (index < length - 1) {
2181
+ rightSampleSize = (pixels[index + 1] - base) / 2;
2182
+ if (index === 0) {
2183
+ leftSampleSize = rightSampleSize;
2184
+ }
2185
+ }
2186
+ }
2187
+
2188
+ leftCategorySize = leftSampleSize * options.categoryPercentage;
2189
+ rightCategorySize = rightSampleSize * options.categoryPercentage;
2190
+ fullBarSize = (leftCategorySize + rightCategorySize) / ruler.stackCount;
2191
+ size = fullBarSize * options.barPercentage;
2192
+
2193
+ size = Math.min(
2194
+ helpers.valueOrDefault(options.barThickness, size),
2195
+ helpers.valueOrDefault(options.maxBarThickness, Infinity));
2196
+
2197
+ base -= leftCategorySize;
2198
+ base += fullBarSize * stackIndex;
2199
+ base += (fullBarSize - size) / 2;
2200
+
2201
+ return {
2202
+ size: size,
2203
+ base: base,
2204
+ head: base + size,
2205
+ center: base + size / 2
2206
+ };
2207
+ },
2208
+
2209
+ draw: function() {
2210
+ var me = this;
2211
+ var chart = me.chart;
2212
+ var scale = me.getValueScale();
2213
+ var rects = me.getMeta().data;
2214
+ var dataset = me.getDataset();
2215
+ var ilen = rects.length;
2216
+ var i = 0;
2217
+
2218
+ helpers.canvas.clipArea(chart.ctx, chart.chartArea);
2219
+
2220
+ for (; i < ilen; ++i) {
2221
+ if (!isNaN(scale.getRightValue(dataset.data[i]))) {
2222
+ rects[i].draw();
2223
+ }
2224
+ }
2225
+
2226
+ helpers.canvas.unclipArea(chart.ctx);
2227
+ },
2228
+
2229
+ setHoverStyle: function(rectangle) {
2230
+ var dataset = this.chart.data.datasets[rectangle._datasetIndex];
2231
+ var index = rectangle._index;
2232
+ var custom = rectangle.custom || {};
2233
+ var model = rectangle._model;
2234
+
2235
+ model.backgroundColor = custom.hoverBackgroundColor ? custom.hoverBackgroundColor : helpers.valueAtIndexOrDefault(dataset.hoverBackgroundColor, index, helpers.getHoverColor(model.backgroundColor));
2236
+ model.borderColor = custom.hoverBorderColor ? custom.hoverBorderColor : helpers.valueAtIndexOrDefault(dataset.hoverBorderColor, index, helpers.getHoverColor(model.borderColor));
2237
+ model.borderWidth = custom.hoverBorderWidth ? custom.hoverBorderWidth : helpers.valueAtIndexOrDefault(dataset.hoverBorderWidth, index, model.borderWidth);
2238
+ },
2239
+
2240
+ removeHoverStyle: function(rectangle) {
2241
+ var dataset = this.chart.data.datasets[rectangle._datasetIndex];
2242
+ var index = rectangle._index;
2243
+ var custom = rectangle.custom || {};
2244
+ var model = rectangle._model;
2245
+ var rectangleElementOptions = this.chart.options.elements.rectangle;
2246
+
2247
+ model.backgroundColor = custom.backgroundColor ? custom.backgroundColor : helpers.valueAtIndexOrDefault(dataset.backgroundColor, index, rectangleElementOptions.backgroundColor);
2248
+ model.borderColor = custom.borderColor ? custom.borderColor : helpers.valueAtIndexOrDefault(dataset.borderColor, index, rectangleElementOptions.borderColor);
2249
+ model.borderWidth = custom.borderWidth ? custom.borderWidth : helpers.valueAtIndexOrDefault(dataset.borderWidth, index, rectangleElementOptions.borderWidth);
2250
+ }
2251
+ });
2252
+
2253
+ Chart.controllers.horizontalBar = Chart.controllers.bar.extend({
2254
+ /**
2255
+ * @private
2256
+ */
2257
+ getValueScaleId: function() {
2258
+ return this.getMeta().xAxisID;
2259
+ },
2260
+
2261
+ /**
2262
+ * @private
2263
+ */
2264
+ getIndexScaleId: function() {
2265
+ return this.getMeta().yAxisID;
2266
+ }
2267
+ });
2268
+ };
2269
+
2270
+ },{"25":25,"40":40,"45":45}],16:[function(require,module,exports){
2271
+ 'use strict';
2272
+
2273
+ var defaults = require(25);
2274
+ var elements = require(40);
2275
+ var helpers = require(45);
2276
+
2277
+ defaults._set('bubble', {
2278
+ hover: {
2279
+ mode: 'single'
2280
+ },
2281
+
2282
+ scales: {
2283
+ xAxes: [{
2284
+ type: 'linear', // bubble should probably use a linear scale by default
2285
+ position: 'bottom',
2286
+ id: 'x-axis-0' // need an ID so datasets can reference the scale
2287
+ }],
2288
+ yAxes: [{
2289
+ type: 'linear',
2290
+ position: 'left',
2291
+ id: 'y-axis-0'
2292
+ }]
2293
+ },
2294
+
2295
+ tooltips: {
2296
+ callbacks: {
2297
+ title: function() {
2298
+ // Title doesn't make sense for scatter since we format the data as a point
2299
+ return '';
2300
+ },
2301
+ label: function(item, data) {
2302
+ var datasetLabel = data.datasets[item.datasetIndex].label || '';
2303
+ var dataPoint = data.datasets[item.datasetIndex].data[item.index];
2304
+ return datasetLabel + ': (' + item.xLabel + ', ' + item.yLabel + ', ' + dataPoint.r + ')';
2305
+ }
2306
+ }
2307
+ }
2308
+ });
2309
+
2310
+
2311
+ module.exports = function(Chart) {
2312
+
2313
+ Chart.controllers.bubble = Chart.DatasetController.extend({
2314
+ /**
2315
+ * @protected
2316
+ */
2317
+ dataElementType: elements.Point,
2318
+
2319
+ /**
2320
+ * @protected
2321
+ */
2322
+ update: function(reset) {
2323
+ var me = this;
2324
+ var meta = me.getMeta();
2325
+ var points = meta.data;
2326
+
2327
+ // Update Points
2328
+ helpers.each(points, function(point, index) {
2329
+ me.updateElement(point, index, reset);
2330
+ });
2331
+ },
2332
+
2333
+ /**
2334
+ * @protected
2335
+ */
2336
+ updateElement: function(point, index, reset) {
2337
+ var me = this;
2338
+ var meta = me.getMeta();
2339
+ var custom = point.custom || {};
2340
+ var xScale = me.getScaleForId(meta.xAxisID);
2341
+ var yScale = me.getScaleForId(meta.yAxisID);
2342
+ var options = me._resolveElementOptions(point, index);
2343
+ var data = me.getDataset().data[index];
2344
+ var dsIndex = me.index;
2345
+
2346
+ var x = reset ? xScale.getPixelForDecimal(0.5) : xScale.getPixelForValue(typeof data === 'object' ? data : NaN, index, dsIndex);
2347
+ var y = reset ? yScale.getBasePixel() : yScale.getPixelForValue(data, index, dsIndex);
2348
+
2349
+ point._xScale = xScale;
2350
+ point._yScale = yScale;
2351
+ point._options = options;
2352
+ point._datasetIndex = dsIndex;
2353
+ point._index = index;
2354
+ point._model = {
2355
+ backgroundColor: options.backgroundColor,
2356
+ borderColor: options.borderColor,
2357
+ borderWidth: options.borderWidth,
2358
+ hitRadius: options.hitRadius,
2359
+ pointStyle: options.pointStyle,
2360
+ radius: reset ? 0 : options.radius,
2361
+ skip: custom.skip || isNaN(x) || isNaN(y),
2362
+ x: x,
2363
+ y: y,
2364
+ };
2365
+
2366
+ point.pivot();
2367
+ },
2368
+
2369
+ /**
2370
+ * @protected
2371
+ */
2372
+ setHoverStyle: function(point) {
2373
+ var model = point._model;
2374
+ var options = point._options;
2375
+
2376
+ model.backgroundColor = helpers.valueOrDefault(options.hoverBackgroundColor, helpers.getHoverColor(options.backgroundColor));
2377
+ model.borderColor = helpers.valueOrDefault(options.hoverBorderColor, helpers.getHoverColor(options.borderColor));
2378
+ model.borderWidth = helpers.valueOrDefault(options.hoverBorderWidth, options.borderWidth);
2379
+ model.radius = options.radius + options.hoverRadius;
2380
+ },
2381
+
2382
+ /**
2383
+ * @protected
2384
+ */
2385
+ removeHoverStyle: function(point) {
2386
+ var model = point._model;
2387
+ var options = point._options;
2388
+
2389
+ model.backgroundColor = options.backgroundColor;
2390
+ model.borderColor = options.borderColor;
2391
+ model.borderWidth = options.borderWidth;
2392
+ model.radius = options.radius;
2393
+ },
2394
+
2395
+ /**
2396
+ * @private
2397
+ */
2398
+ _resolveElementOptions: function(point, index) {
2399
+ var me = this;
2400
+ var chart = me.chart;
2401
+ var datasets = chart.data.datasets;
2402
+ var dataset = datasets[me.index];
2403
+ var custom = point.custom || {};
2404
+ var options = chart.options.elements.point;
2405
+ var resolve = helpers.options.resolve;
2406
+ var data = dataset.data[index];
2407
+ var values = {};
2408
+ var i, ilen, key;
2409
+
2410
+ // Scriptable options
2411
+ var context = {
2412
+ chart: chart,
2413
+ dataIndex: index,
2414
+ dataset: dataset,
2415
+ datasetIndex: me.index
2416
+ };
2417
+
2418
+ var keys = [
2419
+ 'backgroundColor',
2420
+ 'borderColor',
2421
+ 'borderWidth',
2422
+ 'hoverBackgroundColor',
2423
+ 'hoverBorderColor',
2424
+ 'hoverBorderWidth',
2425
+ 'hoverRadius',
2426
+ 'hitRadius',
2427
+ 'pointStyle'
2428
+ ];
2429
+
2430
+ for (i = 0, ilen = keys.length; i < ilen; ++i) {
2431
+ key = keys[i];
2432
+ values[key] = resolve([
2433
+ custom[key],
2434
+ dataset[key],
2435
+ options[key]
2436
+ ], context, index);
2437
+ }
2438
+
2439
+ // Custom radius resolution
2440
+ values.radius = resolve([
2441
+ custom.radius,
2442
+ data ? data.r : undefined,
2443
+ dataset.radius,
2444
+ options.radius
2445
+ ], context, index);
2446
+
2447
+ return values;
2448
+ }
2449
+ });
2450
+ };
2451
+
2452
+ },{"25":25,"40":40,"45":45}],17:[function(require,module,exports){
2453
+ 'use strict';
2454
+
2455
+ var defaults = require(25);
2456
+ var elements = require(40);
2457
+ var helpers = require(45);
2458
+
2459
+ defaults._set('doughnut', {
2460
+ animation: {
2461
+ // Boolean - Whether we animate the rotation of the Doughnut
2462
+ animateRotate: true,
2463
+ // Boolean - Whether we animate scaling the Doughnut from the centre
2464
+ animateScale: false
2465
+ },
2466
+ hover: {
2467
+ mode: 'single'
2468
+ },
2469
+ legendCallback: function(chart) {
2470
+ var text = [];
2471
+ text.push('<ul class="' + chart.id + '-legend">');
2472
+
2473
+ var data = chart.data;
2474
+ var datasets = data.datasets;
2475
+ var labels = data.labels;
2476
+
2477
+ if (datasets.length) {
2478
+ for (var i = 0; i < datasets[0].data.length; ++i) {
2479
+ text.push('<li><span style="background-color:' + datasets[0].backgroundColor[i] + '"></span>');
2480
+ if (labels[i]) {
2481
+ text.push(labels[i]);
2482
+ }
2483
+ text.push('</li>');
2484
+ }
2485
+ }
2486
+
2487
+ text.push('</ul>');
2488
+ return text.join('');
2489
+ },
2490
+ legend: {
2491
+ labels: {
2492
+ generateLabels: function(chart) {
2493
+ var data = chart.data;
2494
+ if (data.labels.length && data.datasets.length) {
2495
+ return data.labels.map(function(label, i) {
2496
+ var meta = chart.getDatasetMeta(0);
2497
+ var ds = data.datasets[0];
2498
+ var arc = meta.data[i];
2499
+ var custom = arc && arc.custom || {};
2500
+ var valueAtIndexOrDefault = helpers.valueAtIndexOrDefault;
2501
+ var arcOpts = chart.options.elements.arc;
2502
+ var fill = custom.backgroundColor ? custom.backgroundColor : valueAtIndexOrDefault(ds.backgroundColor, i, arcOpts.backgroundColor);
2503
+ var stroke = custom.borderColor ? custom.borderColor : valueAtIndexOrDefault(ds.borderColor, i, arcOpts.borderColor);
2504
+ var bw = custom.borderWidth ? custom.borderWidth : valueAtIndexOrDefault(ds.borderWidth, i, arcOpts.borderWidth);
2505
+
2506
+ return {
2507
+ text: label,
2508
+ fillStyle: fill,
2509
+ strokeStyle: stroke,
2510
+ lineWidth: bw,
2511
+ hidden: isNaN(ds.data[i]) || meta.data[i].hidden,
2512
+
2513
+ // Extra data used for toggling the correct item
2514
+ index: i
2515
+ };
2516
+ });
2517
+ }
2518
+ return [];
2519
+ }
2520
+ },
2521
+
2522
+ onClick: function(e, legendItem) {
2523
+ var index = legendItem.index;
2524
+ var chart = this.chart;
2525
+ var i, ilen, meta;
2526
+
2527
+ for (i = 0, ilen = (chart.data.datasets || []).length; i < ilen; ++i) {
2528
+ meta = chart.getDatasetMeta(i);
2529
+ // toggle visibility of index if exists
2530
+ if (meta.data[index]) {
2531
+ meta.data[index].hidden = !meta.data[index].hidden;
2532
+ }
2533
+ }
2534
+
2535
+ chart.update();
2536
+ }
2537
+ },
2538
+
2539
+ // The percentage of the chart that we cut out of the middle.
2540
+ cutoutPercentage: 50,
2541
+
2542
+ // The rotation of the chart, where the first data arc begins.
2543
+ rotation: Math.PI * -0.5,
2544
+
2545
+ // The total circumference of the chart.
2546
+ circumference: Math.PI * 2.0,
2547
+
2548
+ // Need to override these to give a nice default
2549
+ tooltips: {
2550
+ callbacks: {
2551
+ title: function() {
2552
+ return '';
2553
+ },
2554
+ label: function(tooltipItem, data) {
2555
+ var dataLabel = data.labels[tooltipItem.index];
2556
+ var value = ': ' + data.datasets[tooltipItem.datasetIndex].data[tooltipItem.index];
2557
+
2558
+ if (helpers.isArray(dataLabel)) {
2559
+ // show value on first line of multiline label
2560
+ // need to clone because we are changing the value
2561
+ dataLabel = dataLabel.slice();
2562
+ dataLabel[0] += value;
2563
+ } else {
2564
+ dataLabel += value;
2565
+ }
2566
+
2567
+ return dataLabel;
2568
+ }
2569
+ }
2570
+ }
2571
+ });
2572
+
2573
+ defaults._set('pie', helpers.clone(defaults.doughnut));
2574
+ defaults._set('pie', {
2575
+ cutoutPercentage: 0
2576
+ });
2577
+
2578
+ module.exports = function(Chart) {
2579
+
2580
+ Chart.controllers.doughnut = Chart.controllers.pie = Chart.DatasetController.extend({
2581
+
2582
+ dataElementType: elements.Arc,
2583
+
2584
+ linkScales: helpers.noop,
2585
+
2586
+ // Get index of the dataset in relation to the visible datasets. This allows determining the inner and outer radius correctly
2587
+ getRingIndex: function(datasetIndex) {
2588
+ var ringIndex = 0;
2589
+
2590
+ for (var j = 0; j < datasetIndex; ++j) {
2591
+ if (this.chart.isDatasetVisible(j)) {
2592
+ ++ringIndex;
2593
+ }
2594
+ }
2595
+
2596
+ return ringIndex;
2597
+ },
2598
+
2599
+ update: function(reset) {
2600
+ var me = this;
2601
+ var chart = me.chart;
2602
+ var chartArea = chart.chartArea;
2603
+ var opts = chart.options;
2604
+ var arcOpts = opts.elements.arc;
2605
+ var availableWidth = chartArea.right - chartArea.left - arcOpts.borderWidth;
2606
+ var availableHeight = chartArea.bottom - chartArea.top - arcOpts.borderWidth;
2607
+ var minSize = Math.min(availableWidth, availableHeight);
2608
+ var offset = {x: 0, y: 0};
2609
+ var meta = me.getMeta();
2610
+ var cutoutPercentage = opts.cutoutPercentage;
2611
+ var circumference = opts.circumference;
2612
+
2613
+ // If the chart's circumference isn't a full circle, calculate minSize as a ratio of the width/height of the arc
2614
+ if (circumference < Math.PI * 2.0) {
2615
+ var startAngle = opts.rotation % (Math.PI * 2.0);
2616
+ startAngle += Math.PI * 2.0 * (startAngle >= Math.PI ? -1 : startAngle < -Math.PI ? 1 : 0);
2617
+ var endAngle = startAngle + circumference;
2618
+ var start = {x: Math.cos(startAngle), y: Math.sin(startAngle)};
2619
+ var end = {x: Math.cos(endAngle), y: Math.sin(endAngle)};
2620
+ var contains0 = (startAngle <= 0 && endAngle >= 0) || (startAngle <= Math.PI * 2.0 && Math.PI * 2.0 <= endAngle);
2621
+ var contains90 = (startAngle <= Math.PI * 0.5 && Math.PI * 0.5 <= endAngle) || (startAngle <= Math.PI * 2.5 && Math.PI * 2.5 <= endAngle);
2622
+ var contains180 = (startAngle <= -Math.PI && -Math.PI <= endAngle) || (startAngle <= Math.PI && Math.PI <= endAngle);
2623
+ var contains270 = (startAngle <= -Math.PI * 0.5 && -Math.PI * 0.5 <= endAngle) || (startAngle <= Math.PI * 1.5 && Math.PI * 1.5 <= endAngle);
2624
+ var cutout = cutoutPercentage / 100.0;
2625
+ 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))};
2626
+ 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))};
2627
+ var size = {width: (max.x - min.x) * 0.5, height: (max.y - min.y) * 0.5};
2628
+ minSize = Math.min(availableWidth / size.width, availableHeight / size.height);
2629
+ offset = {x: (max.x + min.x) * -0.5, y: (max.y + min.y) * -0.5};
2630
+ }
2631
+
2632
+ chart.borderWidth = me.getMaxBorderWidth(meta.data);
2633
+ chart.outerRadius = Math.max((minSize - chart.borderWidth) / 2, 0);
2634
+ chart.innerRadius = Math.max(cutoutPercentage ? (chart.outerRadius / 100) * (cutoutPercentage) : 0, 0);
2635
+ chart.radiusLength = (chart.outerRadius - chart.innerRadius) / chart.getVisibleDatasetCount();
2636
+ chart.offsetX = offset.x * chart.outerRadius;
2637
+ chart.offsetY = offset.y * chart.outerRadius;
2638
+
2639
+ meta.total = me.calculateTotal();
2640
+
2641
+ me.outerRadius = chart.outerRadius - (chart.radiusLength * me.getRingIndex(me.index));
2642
+ me.innerRadius = Math.max(me.outerRadius - chart.radiusLength, 0);
2643
+
2644
+ helpers.each(meta.data, function(arc, index) {
2645
+ me.updateElement(arc, index, reset);
2646
+ });
2647
+ },
2648
+
2649
+ updateElement: function(arc, index, reset) {
2650
+ var me = this;
2651
+ var chart = me.chart;
2652
+ var chartArea = chart.chartArea;
2653
+ var opts = chart.options;
2654
+ var animationOpts = opts.animation;
2655
+ var centerX = (chartArea.left + chartArea.right) / 2;
2656
+ var centerY = (chartArea.top + chartArea.bottom) / 2;
2657
+ var startAngle = opts.rotation; // non reset case handled later
2658
+ var endAngle = opts.rotation; // non reset case handled later
2659
+ var dataset = me.getDataset();
2660
+ var circumference = reset && animationOpts.animateRotate ? 0 : arc.hidden ? 0 : me.calculateCircumference(dataset.data[index]) * (opts.circumference / (2.0 * Math.PI));
2661
+ var innerRadius = reset && animationOpts.animateScale ? 0 : me.innerRadius;
2662
+ var outerRadius = reset && animationOpts.animateScale ? 0 : me.outerRadius;
2663
+ var valueAtIndexOrDefault = helpers.valueAtIndexOrDefault;
2664
+
2665
+ helpers.extend(arc, {
2666
+ // Utility
2667
+ _datasetIndex: me.index,
2668
+ _index: index,
2669
+
2670
+ // Desired view properties
2671
+ _model: {
2672
+ x: centerX + chart.offsetX,
2673
+ y: centerY + chart.offsetY,
2674
+ startAngle: startAngle,
2675
+ endAngle: endAngle,
2676
+ circumference: circumference,
2677
+ outerRadius: outerRadius,
2678
+ innerRadius: innerRadius,
2679
+ label: valueAtIndexOrDefault(dataset.label, index, chart.data.labels[index])
2680
+ }
2681
+ });
2682
+
2683
+ var model = arc._model;
2684
+ // Resets the visual styles
2685
+ this.removeHoverStyle(arc);
2686
+
2687
+ // Set correct angles if not resetting
2688
+ if (!reset || !animationOpts.animateRotate) {
2689
+ if (index === 0) {
2690
+ model.startAngle = opts.rotation;
2691
+ } else {
2692
+ model.startAngle = me.getMeta().data[index - 1]._model.endAngle;
2693
+ }
2694
+
2695
+ model.endAngle = model.startAngle + model.circumference;
2696
+ }
2697
+
2698
+ arc.pivot();
2699
+ },
2700
+
2701
+ removeHoverStyle: function(arc) {
2702
+ Chart.DatasetController.prototype.removeHoverStyle.call(this, arc, this.chart.options.elements.arc);
2703
+ },
2704
+
2705
+ calculateTotal: function() {
2706
+ var dataset = this.getDataset();
2707
+ var meta = this.getMeta();
2708
+ var total = 0;
2709
+ var value;
2710
+
2711
+ helpers.each(meta.data, function(element, index) {
2712
+ value = dataset.data[index];
2713
+ if (!isNaN(value) && !element.hidden) {
2714
+ total += Math.abs(value);
2715
+ }
2716
+ });
2717
+
2718
+ /* if (total === 0) {
2719
+ total = NaN;
2720
+ }*/
2721
+
2722
+ return total;
2723
+ },
2724
+
2725
+ calculateCircumference: function(value) {
2726
+ var total = this.getMeta().total;
2727
+ if (total > 0 && !isNaN(value)) {
2728
+ return (Math.PI * 2.0) * (value / total);
2729
+ }
2730
+ return 0;
2731
+ },
2732
+
2733
+ // gets the max border or hover width to properly scale pie charts
2734
+ getMaxBorderWidth: function(arcs) {
2735
+ var max = 0;
2736
+ var index = this.index;
2737
+ var length = arcs.length;
2738
+ var borderWidth;
2739
+ var hoverWidth;
2740
+
2741
+ for (var i = 0; i < length; i++) {
2742
+ borderWidth = arcs[i]._model ? arcs[i]._model.borderWidth : 0;
2743
+ hoverWidth = arcs[i]._chart ? arcs[i]._chart.config.data.datasets[index].hoverBorderWidth : 0;
2744
+
2745
+ max = borderWidth > max ? borderWidth : max;
2746
+ max = hoverWidth > max ? hoverWidth : max;
2747
+ }
2748
+ return max;
2749
+ }
2750
+ });
2751
+ };
2752
+
2753
+ },{"25":25,"40":40,"45":45}],18:[function(require,module,exports){
2754
+ 'use strict';
2755
+
2756
+ var defaults = require(25);
2757
+ var elements = require(40);
2758
+ var helpers = require(45);
2759
+
2760
+ defaults._set('line', {
2761
+ showLines: true,
2762
+ spanGaps: false,
2763
+
2764
+ hover: {
2765
+ mode: 'label'
2766
+ },
2767
+
2768
+ scales: {
2769
+ xAxes: [{
2770
+ type: 'category',
2771
+ id: 'x-axis-0'
2772
+ }],
2773
+ yAxes: [{
2774
+ type: 'linear',
2775
+ id: 'y-axis-0'
2776
+ }]
2777
+ }
2778
+ });
2779
+
2780
+ module.exports = function(Chart) {
2781
+
2782
+ function lineEnabled(dataset, options) {
2783
+ return helpers.valueOrDefault(dataset.showLine, options.showLines);
2784
+ }
2785
+
2786
+ Chart.controllers.line = Chart.DatasetController.extend({
2787
+
2788
+ datasetElementType: elements.Line,
2789
+
2790
+ dataElementType: elements.Point,
2791
+
2792
+ update: function(reset) {
2793
+ var me = this;
2794
+ var meta = me.getMeta();
2795
+ var line = meta.dataset;
2796
+ var points = meta.data || [];
2797
+ var options = me.chart.options;
2798
+ var lineElementOptions = options.elements.line;
2799
+ var scale = me.getScaleForId(meta.yAxisID);
2800
+ var i, ilen, custom;
2801
+ var dataset = me.getDataset();
2802
+ var showLine = lineEnabled(dataset, options);
2803
+
2804
+ // Update Line
2805
+ if (showLine) {
2806
+ custom = line.custom || {};
2807
+
2808
+ // Compatibility: If the properties are defined with only the old name, use those values
2809
+ if ((dataset.tension !== undefined) && (dataset.lineTension === undefined)) {
2810
+ dataset.lineTension = dataset.tension;
2811
+ }
2812
+
2813
+ // Utility
2814
+ line._scale = scale;
2815
+ line._datasetIndex = me.index;
2816
+ // Data
2817
+ line._children = points;
2818
+ // Model
2819
+ line._model = {
2820
+ // Appearance
2821
+ // The default behavior of lines is to break at null values, according
2822
+ // to https://github.com/chartjs/Chart.js/issues/2435#issuecomment-216718158
2823
+ // This option gives lines the ability to span gaps
2824
+ spanGaps: dataset.spanGaps ? dataset.spanGaps : options.spanGaps,
2825
+ tension: custom.tension ? custom.tension : helpers.valueOrDefault(dataset.lineTension, lineElementOptions.tension),
2826
+ backgroundColor: custom.backgroundColor ? custom.backgroundColor : (dataset.backgroundColor || lineElementOptions.backgroundColor),
2827
+ borderWidth: custom.borderWidth ? custom.borderWidth : (dataset.borderWidth || lineElementOptions.borderWidth),
2828
+ borderColor: custom.borderColor ? custom.borderColor : (dataset.borderColor || lineElementOptions.borderColor),
2829
+ borderCapStyle: custom.borderCapStyle ? custom.borderCapStyle : (dataset.borderCapStyle || lineElementOptions.borderCapStyle),
2830
+ borderDash: custom.borderDash ? custom.borderDash : (dataset.borderDash || lineElementOptions.borderDash),
2831
+ borderDashOffset: custom.borderDashOffset ? custom.borderDashOffset : (dataset.borderDashOffset || lineElementOptions.borderDashOffset),
2832
+ borderJoinStyle: custom.borderJoinStyle ? custom.borderJoinStyle : (dataset.borderJoinStyle || lineElementOptions.borderJoinStyle),
2833
+ fill: custom.fill ? custom.fill : (dataset.fill !== undefined ? dataset.fill : lineElementOptions.fill),
2834
+ steppedLine: custom.steppedLine ? custom.steppedLine : helpers.valueOrDefault(dataset.steppedLine, lineElementOptions.stepped),
2835
+ cubicInterpolationMode: custom.cubicInterpolationMode ? custom.cubicInterpolationMode : helpers.valueOrDefault(dataset.cubicInterpolationMode, lineElementOptions.cubicInterpolationMode),
2836
+ };
2837
+
2838
+ line.pivot();
2839
+ }
2840
+
2841
+ // Update Points
2842
+ for (i = 0, ilen = points.length; i < ilen; ++i) {
2843
+ me.updateElement(points[i], i, reset);
2844
+ }
2845
+
2846
+ if (showLine && line._model.tension !== 0) {
2847
+ me.updateBezierControlPoints();
2848
+ }
2849
+
2850
+ // Now pivot the point for animation
2851
+ for (i = 0, ilen = points.length; i < ilen; ++i) {
2852
+ points[i].pivot();
2853
+ }
2854
+ },
2855
+
2856
+ getPointBackgroundColor: function(point, index) {
2857
+ var backgroundColor = this.chart.options.elements.point.backgroundColor;
2858
+ var dataset = this.getDataset();
2859
+ var custom = point.custom || {};
2860
+
2861
+ if (custom.backgroundColor) {
2862
+ backgroundColor = custom.backgroundColor;
2863
+ } else if (dataset.pointBackgroundColor) {
2864
+ backgroundColor = helpers.valueAtIndexOrDefault(dataset.pointBackgroundColor, index, backgroundColor);
2865
+ } else if (dataset.backgroundColor) {
2866
+ backgroundColor = dataset.backgroundColor;
2867
+ }
2868
+
2869
+ return backgroundColor;
2870
+ },
2871
+
2872
+ getPointBorderColor: function(point, index) {
2873
+ var borderColor = this.chart.options.elements.point.borderColor;
2874
+ var dataset = this.getDataset();
2875
+ var custom = point.custom || {};
2876
+
2877
+ if (custom.borderColor) {
2878
+ borderColor = custom.borderColor;
2879
+ } else if (dataset.pointBorderColor) {
2880
+ borderColor = helpers.valueAtIndexOrDefault(dataset.pointBorderColor, index, borderColor);
2881
+ } else if (dataset.borderColor) {
2882
+ borderColor = dataset.borderColor;
2883
+ }
2884
+
2885
+ return borderColor;
2886
+ },
2887
+
2888
+ getPointBorderWidth: function(point, index) {
2889
+ var borderWidth = this.chart.options.elements.point.borderWidth;
2890
+ var dataset = this.getDataset();
2891
+ var custom = point.custom || {};
2892
+
2893
+ if (!isNaN(custom.borderWidth)) {
2894
+ borderWidth = custom.borderWidth;
2895
+ } else if (!isNaN(dataset.pointBorderWidth) || helpers.isArray(dataset.pointBorderWidth)) {
2896
+ borderWidth = helpers.valueAtIndexOrDefault(dataset.pointBorderWidth, index, borderWidth);
2897
+ } else if (!isNaN(dataset.borderWidth)) {
2898
+ borderWidth = dataset.borderWidth;
2899
+ }
2900
+
2901
+ return borderWidth;
2902
+ },
2903
+
2904
+ updateElement: function(point, index, reset) {
2905
+ var me = this;
2906
+ var meta = me.getMeta();
2907
+ var custom = point.custom || {};
2908
+ var dataset = me.getDataset();
2909
+ var datasetIndex = me.index;
2910
+ var value = dataset.data[index];
2911
+ var yScale = me.getScaleForId(meta.yAxisID);
2912
+ var xScale = me.getScaleForId(meta.xAxisID);
2913
+ var pointOptions = me.chart.options.elements.point;
2914
+ var x, y;
2915
+
2916
+ // Compatibility: If the properties are defined with only the old name, use those values
2917
+ if ((dataset.radius !== undefined) && (dataset.pointRadius === undefined)) {
2918
+ dataset.pointRadius = dataset.radius;
2919
+ }
2920
+ if ((dataset.hitRadius !== undefined) && (dataset.pointHitRadius === undefined)) {
2921
+ dataset.pointHitRadius = dataset.hitRadius;
2922
+ }
2923
+
2924
+ x = xScale.getPixelForValue(typeof value === 'object' ? value : NaN, index, datasetIndex);
2925
+ y = reset ? yScale.getBasePixel() : me.calculatePointY(value, index, datasetIndex);
2926
+
2927
+ // Utility
2928
+ point._xScale = xScale;
2929
+ point._yScale = yScale;
2930
+ point._datasetIndex = datasetIndex;
2931
+ point._index = index;
2932
+
2933
+ // Desired view properties
2934
+ point._model = {
2935
+ x: x,
2936
+ y: y,
2937
+ skip: custom.skip || isNaN(x) || isNaN(y),
2938
+ // Appearance
2939
+ radius: custom.radius || helpers.valueAtIndexOrDefault(dataset.pointRadius, index, pointOptions.radius),
2940
+ pointStyle: custom.pointStyle || helpers.valueAtIndexOrDefault(dataset.pointStyle, index, pointOptions.pointStyle),
2941
+ backgroundColor: me.getPointBackgroundColor(point, index),
2942
+ borderColor: me.getPointBorderColor(point, index),
2943
+ borderWidth: me.getPointBorderWidth(point, index),
2944
+ tension: meta.dataset._model ? meta.dataset._model.tension : 0,
2945
+ steppedLine: meta.dataset._model ? meta.dataset._model.steppedLine : false,
2946
+ // Tooltip
2947
+ hitRadius: custom.hitRadius || helpers.valueAtIndexOrDefault(dataset.pointHitRadius, index, pointOptions.hitRadius)
2948
+ };
2949
+ },
2950
+
2951
+ calculatePointY: function(value, index, datasetIndex) {
2952
+ var me = this;
2953
+ var chart = me.chart;
2954
+ var meta = me.getMeta();
2955
+ var yScale = me.getScaleForId(meta.yAxisID);
2956
+ var sumPos = 0;
2957
+ var sumNeg = 0;
2958
+ var i, ds, dsMeta;
2959
+
2960
+ if (yScale.options.stacked) {
2961
+ for (i = 0; i < datasetIndex; i++) {
2962
+ ds = chart.data.datasets[i];
2963
+ dsMeta = chart.getDatasetMeta(i);
2964
+ if (dsMeta.type === 'line' && dsMeta.yAxisID === yScale.id && chart.isDatasetVisible(i)) {
2965
+ var stackedRightValue = Number(yScale.getRightValue(ds.data[index]));
2966
+ if (stackedRightValue < 0) {
2967
+ sumNeg += stackedRightValue || 0;
2968
+ } else {
2969
+ sumPos += stackedRightValue || 0;
2970
+ }
2971
+ }
2972
+ }
2973
+
2974
+ var rightValue = Number(yScale.getRightValue(value));
2975
+ if (rightValue < 0) {
2976
+ return yScale.getPixelForValue(sumNeg + rightValue);
2977
+ }
2978
+ return yScale.getPixelForValue(sumPos + rightValue);
2979
+ }
2980
+
2981
+ return yScale.getPixelForValue(value);
2982
+ },
2983
+
2984
+ updateBezierControlPoints: function() {
2985
+ var me = this;
2986
+ var meta = me.getMeta();
2987
+ var area = me.chart.chartArea;
2988
+ var points = (meta.data || []);
2989
+ var i, ilen, point, model, controlPoints;
2990
+
2991
+ // Only consider points that are drawn in case the spanGaps option is used
2992
+ if (meta.dataset._model.spanGaps) {
2993
+ points = points.filter(function(pt) {
2994
+ return !pt._model.skip;
2995
+ });
2996
+ }
2997
+
2998
+ function capControlPoint(pt, min, max) {
2999
+ return Math.max(Math.min(pt, max), min);
3000
+ }
3001
+
3002
+ if (meta.dataset._model.cubicInterpolationMode === 'monotone') {
3003
+ helpers.splineCurveMonotone(points);
3004
+ } else {
3005
+ for (i = 0, ilen = points.length; i < ilen; ++i) {
3006
+ point = points[i];
3007
+ model = point._model;
3008
+ controlPoints = helpers.splineCurve(
3009
+ helpers.previousItem(points, i)._model,
3010
+ model,
3011
+ helpers.nextItem(points, i)._model,
3012
+ meta.dataset._model.tension
3013
+ );
3014
+ model.controlPointPreviousX = controlPoints.previous.x;
3015
+ model.controlPointPreviousY = controlPoints.previous.y;
3016
+ model.controlPointNextX = controlPoints.next.x;
3017
+ model.controlPointNextY = controlPoints.next.y;
3018
+ }
3019
+ }
3020
+
3021
+ if (me.chart.options.elements.line.capBezierPoints) {
3022
+ for (i = 0, ilen = points.length; i < ilen; ++i) {
3023
+ model = points[i]._model;
3024
+ model.controlPointPreviousX = capControlPoint(model.controlPointPreviousX, area.left, area.right);
3025
+ model.controlPointPreviousY = capControlPoint(model.controlPointPreviousY, area.top, area.bottom);
3026
+ model.controlPointNextX = capControlPoint(model.controlPointNextX, area.left, area.right);
3027
+ model.controlPointNextY = capControlPoint(model.controlPointNextY, area.top, area.bottom);
3028
+ }
3029
+ }
3030
+ },
3031
+
3032
+ draw: function() {
3033
+ var me = this;
3034
+ var chart = me.chart;
3035
+ var meta = me.getMeta();
3036
+ var points = meta.data || [];
3037
+ var area = chart.chartArea;
3038
+ var ilen = points.length;
3039
+ var i = 0;
3040
+
3041
+ helpers.canvas.clipArea(chart.ctx, area);
3042
+
3043
+ if (lineEnabled(me.getDataset(), chart.options)) {
3044
+ meta.dataset.draw();
3045
+ }
3046
+
3047
+ helpers.canvas.unclipArea(chart.ctx);
3048
+
3049
+ // Draw the points
3050
+ for (; i < ilen; ++i) {
3051
+ points[i].draw(area);
3052
+ }
3053
+ },
3054
+
3055
+ setHoverStyle: function(point) {
3056
+ // Point
3057
+ var dataset = this.chart.data.datasets[point._datasetIndex];
3058
+ var index = point._index;
3059
+ var custom = point.custom || {};
3060
+ var model = point._model;
3061
+
3062
+ model.radius = custom.hoverRadius || helpers.valueAtIndexOrDefault(dataset.pointHoverRadius, index, this.chart.options.elements.point.hoverRadius);
3063
+ model.backgroundColor = custom.hoverBackgroundColor || helpers.valueAtIndexOrDefault(dataset.pointHoverBackgroundColor, index, helpers.getHoverColor(model.backgroundColor));
3064
+ model.borderColor = custom.hoverBorderColor || helpers.valueAtIndexOrDefault(dataset.pointHoverBorderColor, index, helpers.getHoverColor(model.borderColor));
3065
+ model.borderWidth = custom.hoverBorderWidth || helpers.valueAtIndexOrDefault(dataset.pointHoverBorderWidth, index, model.borderWidth);
3066
+ },
3067
+
3068
+ removeHoverStyle: function(point) {
3069
+ var me = this;
3070
+ var dataset = me.chart.data.datasets[point._datasetIndex];
3071
+ var index = point._index;
3072
+ var custom = point.custom || {};
3073
+ var model = point._model;
3074
+
3075
+ // Compatibility: If the properties are defined with only the old name, use those values
3076
+ if ((dataset.radius !== undefined) && (dataset.pointRadius === undefined)) {
3077
+ dataset.pointRadius = dataset.radius;
3078
+ }
3079
+
3080
+ model.radius = custom.radius || helpers.valueAtIndexOrDefault(dataset.pointRadius, index, me.chart.options.elements.point.radius);
3081
+ model.backgroundColor = me.getPointBackgroundColor(point, index);
3082
+ model.borderColor = me.getPointBorderColor(point, index);
3083
+ model.borderWidth = me.getPointBorderWidth(point, index);
3084
+ }
3085
+ });
3086
+ };
3087
+
3088
+ },{"25":25,"40":40,"45":45}],19:[function(require,module,exports){
3089
+ 'use strict';
3090
+
3091
+ var defaults = require(25);
3092
+ var elements = require(40);
3093
+ var helpers = require(45);
3094
+
3095
+ defaults._set('polarArea', {
3096
+ scale: {
3097
+ type: 'radialLinear',
3098
+ angleLines: {
3099
+ display: false
3100
+ },
3101
+ gridLines: {
3102
+ circular: true
3103
+ },
3104
+ pointLabels: {
3105
+ display: false
3106
+ },
3107
+ ticks: {
3108
+ beginAtZero: true
3109
+ }
3110
+ },
3111
+
3112
+ // Boolean - Whether to animate the rotation of the chart
3113
+ animation: {
3114
+ animateRotate: true,
3115
+ animateScale: true
3116
+ },
3117
+
3118
+ startAngle: -0.5 * Math.PI,
3119
+ legendCallback: function(chart) {
3120
+ var text = [];
3121
+ text.push('<ul class="' + chart.id + '-legend">');
3122
+
3123
+ var data = chart.data;
3124
+ var datasets = data.datasets;
3125
+ var labels = data.labels;
3126
+
3127
+ if (datasets.length) {
3128
+ for (var i = 0; i < datasets[0].data.length; ++i) {
3129
+ text.push('<li><span style="background-color:' + datasets[0].backgroundColor[i] + '"></span>');
3130
+ if (labels[i]) {
3131
+ text.push(labels[i]);
3132
+ }
3133
+ text.push('</li>');
3134
+ }
3135
+ }
3136
+
3137
+ text.push('</ul>');
3138
+ return text.join('');
3139
+ },
3140
+ legend: {
3141
+ labels: {
3142
+ generateLabels: function(chart) {
3143
+ var data = chart.data;
3144
+ if (data.labels.length && data.datasets.length) {
3145
+ return data.labels.map(function(label, i) {
3146
+ var meta = chart.getDatasetMeta(0);
3147
+ var ds = data.datasets[0];
3148
+ var arc = meta.data[i];
3149
+ var custom = arc.custom || {};
3150
+ var valueAtIndexOrDefault = helpers.valueAtIndexOrDefault;
3151
+ var arcOpts = chart.options.elements.arc;
3152
+ var fill = custom.backgroundColor ? custom.backgroundColor : valueAtIndexOrDefault(ds.backgroundColor, i, arcOpts.backgroundColor);
3153
+ var stroke = custom.borderColor ? custom.borderColor : valueAtIndexOrDefault(ds.borderColor, i, arcOpts.borderColor);
3154
+ var bw = custom.borderWidth ? custom.borderWidth : valueAtIndexOrDefault(ds.borderWidth, i, arcOpts.borderWidth);
3155
+
3156
+ return {
3157
+ text: label,
3158
+ fillStyle: fill,
3159
+ strokeStyle: stroke,
3160
+ lineWidth: bw,
3161
+ hidden: isNaN(ds.data[i]) || meta.data[i].hidden,
3162
+
3163
+ // Extra data used for toggling the correct item
3164
+ index: i
3165
+ };
3166
+ });
3167
+ }
3168
+ return [];
3169
+ }
3170
+ },
3171
+
3172
+ onClick: function(e, legendItem) {
3173
+ var index = legendItem.index;
3174
+ var chart = this.chart;
3175
+ var i, ilen, meta;
3176
+
3177
+ for (i = 0, ilen = (chart.data.datasets || []).length; i < ilen; ++i) {
3178
+ meta = chart.getDatasetMeta(i);
3179
+ meta.data[index].hidden = !meta.data[index].hidden;
3180
+ }
3181
+
3182
+ chart.update();
3183
+ }
3184
+ },
3185
+
3186
+ // Need to override these to give a nice default
3187
+ tooltips: {
3188
+ callbacks: {
3189
+ title: function() {
3190
+ return '';
3191
+ },
3192
+ label: function(item, data) {
3193
+ return data.labels[item.index] + ': ' + item.yLabel;
3194
+ }
3195
+ }
3196
+ }
3197
+ });
3198
+
3199
+ module.exports = function(Chart) {
3200
+
3201
+ Chart.controllers.polarArea = Chart.DatasetController.extend({
3202
+
3203
+ dataElementType: elements.Arc,
3204
+
3205
+ linkScales: helpers.noop,
3206
+
3207
+ update: function(reset) {
3208
+ var me = this;
3209
+ var chart = me.chart;
3210
+ var chartArea = chart.chartArea;
3211
+ var meta = me.getMeta();
3212
+ var opts = chart.options;
3213
+ var arcOpts = opts.elements.arc;
3214
+ var minSize = Math.min(chartArea.right - chartArea.left, chartArea.bottom - chartArea.top);
3215
+ chart.outerRadius = Math.max((minSize - arcOpts.borderWidth / 2) / 2, 0);
3216
+ chart.innerRadius = Math.max(opts.cutoutPercentage ? (chart.outerRadius / 100) * (opts.cutoutPercentage) : 1, 0);
3217
+ chart.radiusLength = (chart.outerRadius - chart.innerRadius) / chart.getVisibleDatasetCount();
3218
+
3219
+ me.outerRadius = chart.outerRadius - (chart.radiusLength * me.index);
3220
+ me.innerRadius = me.outerRadius - chart.radiusLength;
3221
+
3222
+ meta.count = me.countVisibleElements();
3223
+
3224
+ helpers.each(meta.data, function(arc, index) {
3225
+ me.updateElement(arc, index, reset);
3226
+ });
3227
+ },
3228
+
3229
+ updateElement: function(arc, index, reset) {
3230
+ var me = this;
3231
+ var chart = me.chart;
3232
+ var dataset = me.getDataset();
3233
+ var opts = chart.options;
3234
+ var animationOpts = opts.animation;
3235
+ var scale = chart.scale;
3236
+ var labels = chart.data.labels;
3237
+
3238
+ var circumference = me.calculateCircumference(dataset.data[index]);
3239
+ var centerX = scale.xCenter;
3240
+ var centerY = scale.yCenter;
3241
+
3242
+ // If there is NaN data before us, we need to calculate the starting angle correctly.
3243
+ // We could be way more efficient here, but its unlikely that the polar area chart will have a lot of data
3244
+ var visibleCount = 0;
3245
+ var meta = me.getMeta();
3246
+ for (var i = 0; i < index; ++i) {
3247
+ if (!isNaN(dataset.data[i]) && !meta.data[i].hidden) {
3248
+ ++visibleCount;
3249
+ }
3250
+ }
3251
+
3252
+ // var negHalfPI = -0.5 * Math.PI;
3253
+ var datasetStartAngle = opts.startAngle;
3254
+ var distance = arc.hidden ? 0 : scale.getDistanceFromCenterForValue(dataset.data[index]);
3255
+ var startAngle = datasetStartAngle + (circumference * visibleCount);
3256
+ var endAngle = startAngle + (arc.hidden ? 0 : circumference);
3257
+
3258
+ var resetRadius = animationOpts.animateScale ? 0 : scale.getDistanceFromCenterForValue(dataset.data[index]);
3259
+
3260
+ helpers.extend(arc, {
3261
+ // Utility
3262
+ _datasetIndex: me.index,
3263
+ _index: index,
3264
+ _scale: scale,
3265
+
3266
+ // Desired view properties
3267
+ _model: {
3268
+ x: centerX,
3269
+ y: centerY,
3270
+ innerRadius: 0,
3271
+ outerRadius: reset ? resetRadius : distance,
3272
+ startAngle: reset && animationOpts.animateRotate ? datasetStartAngle : startAngle,
3273
+ endAngle: reset && animationOpts.animateRotate ? datasetStartAngle : endAngle,
3274
+ label: helpers.valueAtIndexOrDefault(labels, index, labels[index])
3275
+ }
3276
+ });
3277
+
3278
+ // Apply border and fill style
3279
+ me.removeHoverStyle(arc);
3280
+
3281
+ arc.pivot();
3282
+ },
3283
+
3284
+ removeHoverStyle: function(arc) {
3285
+ Chart.DatasetController.prototype.removeHoverStyle.call(this, arc, this.chart.options.elements.arc);
3286
+ },
3287
+
3288
+ countVisibleElements: function() {
3289
+ var dataset = this.getDataset();
3290
+ var meta = this.getMeta();
3291
+ var count = 0;
3292
+
3293
+ helpers.each(meta.data, function(element, index) {
3294
+ if (!isNaN(dataset.data[index]) && !element.hidden) {
3295
+ count++;
3296
+ }
3297
+ });
3298
+
3299
+ return count;
3300
+ },
3301
+
3302
+ calculateCircumference: function(value) {
3303
+ var count = this.getMeta().count;
3304
+ if (count > 0 && !isNaN(value)) {
3305
+ return (2 * Math.PI) / count;
3306
+ }
3307
+ return 0;
3308
+ }
3309
+ });
3310
+ };
3311
+
3312
+ },{"25":25,"40":40,"45":45}],20:[function(require,module,exports){
3313
+ 'use strict';
3314
+
3315
+ var defaults = require(25);
3316
+ var elements = require(40);
3317
+ var helpers = require(45);
3318
+
3319
+ defaults._set('radar', {
3320
+ scale: {
3321
+ type: 'radialLinear'
3322
+ },
3323
+ elements: {
3324
+ line: {
3325
+ tension: 0 // no bezier in radar
3326
+ }
3327
+ }
3328
+ });
3329
+
3330
+ module.exports = function(Chart) {
3331
+
3332
+ Chart.controllers.radar = Chart.DatasetController.extend({
3333
+
3334
+ datasetElementType: elements.Line,
3335
+
3336
+ dataElementType: elements.Point,
3337
+
3338
+ linkScales: helpers.noop,
3339
+
3340
+ update: function(reset) {
3341
+ var me = this;
3342
+ var meta = me.getMeta();
3343
+ var line = meta.dataset;
3344
+ var points = meta.data;
3345
+ var custom = line.custom || {};
3346
+ var dataset = me.getDataset();
3347
+ var lineElementOptions = me.chart.options.elements.line;
3348
+ var scale = me.chart.scale;
3349
+
3350
+ // Compatibility: If the properties are defined with only the old name, use those values
3351
+ if ((dataset.tension !== undefined) && (dataset.lineTension === undefined)) {
3352
+ dataset.lineTension = dataset.tension;
3353
+ }
3354
+
3355
+ helpers.extend(meta.dataset, {
3356
+ // Utility
3357
+ _datasetIndex: me.index,
3358
+ _scale: scale,
3359
+ // Data
3360
+ _children: points,
3361
+ _loop: true,
3362
+ // Model
3363
+ _model: {
3364
+ // Appearance
3365
+ tension: custom.tension ? custom.tension : helpers.valueOrDefault(dataset.lineTension, lineElementOptions.tension),
3366
+ backgroundColor: custom.backgroundColor ? custom.backgroundColor : (dataset.backgroundColor || lineElementOptions.backgroundColor),
3367
+ borderWidth: custom.borderWidth ? custom.borderWidth : (dataset.borderWidth || lineElementOptions.borderWidth),
3368
+ borderColor: custom.borderColor ? custom.borderColor : (dataset.borderColor || lineElementOptions.borderColor),
3369
+ fill: custom.fill ? custom.fill : (dataset.fill !== undefined ? dataset.fill : lineElementOptions.fill),
3370
+ borderCapStyle: custom.borderCapStyle ? custom.borderCapStyle : (dataset.borderCapStyle || lineElementOptions.borderCapStyle),
3371
+ borderDash: custom.borderDash ? custom.borderDash : (dataset.borderDash || lineElementOptions.borderDash),
3372
+ borderDashOffset: custom.borderDashOffset ? custom.borderDashOffset : (dataset.borderDashOffset || lineElementOptions.borderDashOffset),
3373
+ borderJoinStyle: custom.borderJoinStyle ? custom.borderJoinStyle : (dataset.borderJoinStyle || lineElementOptions.borderJoinStyle),
3374
+ }
3375
+ });
3376
+
3377
+ meta.dataset.pivot();
3378
+
3379
+ // Update Points
3380
+ helpers.each(points, function(point, index) {
3381
+ me.updateElement(point, index, reset);
3382
+ }, me);
3383
+
3384
+ // Update bezier control points
3385
+ me.updateBezierControlPoints();
3386
+ },
3387
+ updateElement: function(point, index, reset) {
3388
+ var me = this;
3389
+ var custom = point.custom || {};
3390
+ var dataset = me.getDataset();
3391
+ var scale = me.chart.scale;
3392
+ var pointElementOptions = me.chart.options.elements.point;
3393
+ var pointPosition = scale.getPointPositionForValue(index, dataset.data[index]);
3394
+
3395
+ // Compatibility: If the properties are defined with only the old name, use those values
3396
+ if ((dataset.radius !== undefined) && (dataset.pointRadius === undefined)) {
3397
+ dataset.pointRadius = dataset.radius;
3398
+ }
3399
+ if ((dataset.hitRadius !== undefined) && (dataset.pointHitRadius === undefined)) {
3400
+ dataset.pointHitRadius = dataset.hitRadius;
3401
+ }
3402
+
3403
+ helpers.extend(point, {
3404
+ // Utility
3405
+ _datasetIndex: me.index,
3406
+ _index: index,
3407
+ _scale: scale,
3408
+
3409
+ // Desired view properties
3410
+ _model: {
3411
+ x: reset ? scale.xCenter : pointPosition.x, // value not used in dataset scale, but we want a consistent API between scales
3412
+ y: reset ? scale.yCenter : pointPosition.y,
3413
+
3414
+ // Appearance
3415
+ tension: custom.tension ? custom.tension : helpers.valueOrDefault(dataset.lineTension, me.chart.options.elements.line.tension),
3416
+ radius: custom.radius ? custom.radius : helpers.valueAtIndexOrDefault(dataset.pointRadius, index, pointElementOptions.radius),
3417
+ backgroundColor: custom.backgroundColor ? custom.backgroundColor : helpers.valueAtIndexOrDefault(dataset.pointBackgroundColor, index, pointElementOptions.backgroundColor),
3418
+ borderColor: custom.borderColor ? custom.borderColor : helpers.valueAtIndexOrDefault(dataset.pointBorderColor, index, pointElementOptions.borderColor),
3419
+ borderWidth: custom.borderWidth ? custom.borderWidth : helpers.valueAtIndexOrDefault(dataset.pointBorderWidth, index, pointElementOptions.borderWidth),
3420
+ pointStyle: custom.pointStyle ? custom.pointStyle : helpers.valueAtIndexOrDefault(dataset.pointStyle, index, pointElementOptions.pointStyle),
3421
+
3422
+ // Tooltip
3423
+ hitRadius: custom.hitRadius ? custom.hitRadius : helpers.valueAtIndexOrDefault(dataset.pointHitRadius, index, pointElementOptions.hitRadius)
3424
+ }
3425
+ });
3426
+
3427
+ point._model.skip = custom.skip ? custom.skip : (isNaN(point._model.x) || isNaN(point._model.y));
3428
+ },
3429
+ updateBezierControlPoints: function() {
3430
+ var chartArea = this.chart.chartArea;
3431
+ var meta = this.getMeta();
3432
+
3433
+ helpers.each(meta.data, function(point, index) {
3434
+ var model = point._model;
3435
+ var controlPoints = helpers.splineCurve(
3436
+ helpers.previousItem(meta.data, index, true)._model,
3437
+ model,
3438
+ helpers.nextItem(meta.data, index, true)._model,
3439
+ model.tension
3440
+ );
3441
+
3442
+ // Prevent the bezier going outside of the bounds of the graph
3443
+ model.controlPointPreviousX = Math.max(Math.min(controlPoints.previous.x, chartArea.right), chartArea.left);
3444
+ model.controlPointPreviousY = Math.max(Math.min(controlPoints.previous.y, chartArea.bottom), chartArea.top);
3445
+
3446
+ model.controlPointNextX = Math.max(Math.min(controlPoints.next.x, chartArea.right), chartArea.left);
3447
+ model.controlPointNextY = Math.max(Math.min(controlPoints.next.y, chartArea.bottom), chartArea.top);
3448
+
3449
+ // Now pivot the point for animation
3450
+ point.pivot();
3451
+ });
3452
+ },
3453
+
3454
+ setHoverStyle: function(point) {
3455
+ // Point
3456
+ var dataset = this.chart.data.datasets[point._datasetIndex];
3457
+ var custom = point.custom || {};
3458
+ var index = point._index;
3459
+ var model = point._model;
3460
+
3461
+ model.radius = custom.hoverRadius ? custom.hoverRadius : helpers.valueAtIndexOrDefault(dataset.pointHoverRadius, index, this.chart.options.elements.point.hoverRadius);
3462
+ model.backgroundColor = custom.hoverBackgroundColor ? custom.hoverBackgroundColor : helpers.valueAtIndexOrDefault(dataset.pointHoverBackgroundColor, index, helpers.getHoverColor(model.backgroundColor));
3463
+ model.borderColor = custom.hoverBorderColor ? custom.hoverBorderColor : helpers.valueAtIndexOrDefault(dataset.pointHoverBorderColor, index, helpers.getHoverColor(model.borderColor));
3464
+ model.borderWidth = custom.hoverBorderWidth ? custom.hoverBorderWidth : helpers.valueAtIndexOrDefault(dataset.pointHoverBorderWidth, index, model.borderWidth);
3465
+ },
3466
+
3467
+ removeHoverStyle: function(point) {
3468
+ var dataset = this.chart.data.datasets[point._datasetIndex];
3469
+ var custom = point.custom || {};
3470
+ var index = point._index;
3471
+ var model = point._model;
3472
+ var pointElementOptions = this.chart.options.elements.point;
3473
+
3474
+ model.radius = custom.radius ? custom.radius : helpers.valueAtIndexOrDefault(dataset.pointRadius, index, pointElementOptions.radius);
3475
+ model.backgroundColor = custom.backgroundColor ? custom.backgroundColor : helpers.valueAtIndexOrDefault(dataset.pointBackgroundColor, index, pointElementOptions.backgroundColor);
3476
+ model.borderColor = custom.borderColor ? custom.borderColor : helpers.valueAtIndexOrDefault(dataset.pointBorderColor, index, pointElementOptions.borderColor);
3477
+ model.borderWidth = custom.borderWidth ? custom.borderWidth : helpers.valueAtIndexOrDefault(dataset.pointBorderWidth, index, pointElementOptions.borderWidth);
3478
+ }
3479
+ });
3480
+ };
3481
+
3482
+ },{"25":25,"40":40,"45":45}],21:[function(require,module,exports){
3483
+ 'use strict';
3484
+
3485
+ var defaults = require(25);
3486
+
3487
+ defaults._set('scatter', {
3488
+ hover: {
3489
+ mode: 'single'
3490
+ },
3491
+
3492
+ scales: {
3493
+ xAxes: [{
3494
+ id: 'x-axis-1', // need an ID so datasets can reference the scale
3495
+ type: 'linear', // scatter should not use a category axis
3496
+ position: 'bottom'
3497
+ }],
3498
+ yAxes: [{
3499
+ id: 'y-axis-1',
3500
+ type: 'linear',
3501
+ position: 'left'
3502
+ }]
3503
+ },
3504
+
3505
+ showLines: false,
3506
+
3507
+ tooltips: {
3508
+ callbacks: {
3509
+ title: function() {
3510
+ return ''; // doesn't make sense for scatter since data are formatted as a point
3511
+ },
3512
+ label: function(item) {
3513
+ return '(' + item.xLabel + ', ' + item.yLabel + ')';
3514
+ }
3515
+ }
3516
+ }
3517
+ });
3518
+
3519
+ module.exports = function(Chart) {
3520
+
3521
+ // Scatter charts use line controllers
3522
+ Chart.controllers.scatter = Chart.controllers.line;
3523
+
3524
+ };
3525
+
3526
+ },{"25":25}],22:[function(require,module,exports){
3527
+ /* global window: false */
3528
+ 'use strict';
3529
+
3530
+ var defaults = require(25);
3531
+ var Element = require(26);
3532
+ var helpers = require(45);
3533
+
3534
+ defaults._set('global', {
3535
+ animation: {
3536
+ duration: 1000,
3537
+ easing: 'easeOutQuart',
3538
+ onProgress: helpers.noop,
3539
+ onComplete: helpers.noop
3540
+ }
3541
+ });
3542
+
3543
+ module.exports = function(Chart) {
3544
+
3545
+ Chart.Animation = Element.extend({
3546
+ chart: null, // the animation associated chart instance
3547
+ currentStep: 0, // the current animation step
3548
+ numSteps: 60, // default number of steps
3549
+ easing: '', // the easing to use for this animation
3550
+ render: null, // render function used by the animation service
3551
+
3552
+ onAnimationProgress: null, // user specified callback to fire on each step of the animation
3553
+ onAnimationComplete: null, // user specified callback to fire when the animation finishes
3554
+ });
3555
+
3556
+ Chart.animationService = {
3557
+ frameDuration: 17,
3558
+ animations: [],
3559
+ dropFrames: 0,
3560
+ request: null,
3561
+
3562
+ /**
3563
+ * @param {Chart} chart - The chart to animate.
3564
+ * @param {Chart.Animation} animation - The animation that we will animate.
3565
+ * @param {Number} duration - The animation duration in ms.
3566
+ * @param {Boolean} lazy - if true, the chart is not marked as animating to enable more responsive interactions
3567
+ */
3568
+ addAnimation: function(chart, animation, duration, lazy) {
3569
+ var animations = this.animations;
3570
+ var i, ilen;
3571
+
3572
+ animation.chart = chart;
3573
+
3574
+ if (!lazy) {
3575
+ chart.animating = true;
3576
+ }
3577
+
3578
+ for (i = 0, ilen = animations.length; i < ilen; ++i) {
3579
+ if (animations[i].chart === chart) {
3580
+ animations[i] = animation;
3581
+ return;
3582
+ }
3583
+ }
3584
+
3585
+ animations.push(animation);
3586
+
3587
+ // If there are no animations queued, manually kickstart a digest, for lack of a better word
3588
+ if (animations.length === 1) {
3589
+ this.requestAnimationFrame();
3590
+ }
3591
+ },
3592
+
3593
+ cancelAnimation: function(chart) {
3594
+ var index = helpers.findIndex(this.animations, function(animation) {
3595
+ return animation.chart === chart;
3596
+ });
3597
+
3598
+ if (index !== -1) {
3599
+ this.animations.splice(index, 1);
3600
+ chart.animating = false;
3601
+ }
3602
+ },
3603
+
3604
+ requestAnimationFrame: function() {
3605
+ var me = this;
3606
+ if (me.request === null) {
3607
+ // Skip animation frame requests until the active one is executed.
3608
+ // This can happen when processing mouse events, e.g. 'mousemove'
3609
+ // and 'mouseout' events will trigger multiple renders.
3610
+ me.request = helpers.requestAnimFrame.call(window, function() {
3611
+ me.request = null;
3612
+ me.startDigest();
3613
+ });
3614
+ }
3615
+ },
3616
+
3617
+ /**
3618
+ * @private
3619
+ */
3620
+ startDigest: function() {
3621
+ var me = this;
3622
+ var startTime = Date.now();
3623
+ var framesToDrop = 0;
3624
+
3625
+ if (me.dropFrames > 1) {
3626
+ framesToDrop = Math.floor(me.dropFrames);
3627
+ me.dropFrames = me.dropFrames % 1;
3628
+ }
3629
+
3630
+ me.advance(1 + framesToDrop);
3631
+
3632
+ var endTime = Date.now();
3633
+
3634
+ me.dropFrames += (endTime - startTime) / me.frameDuration;
3635
+
3636
+ // Do we have more stuff to animate?
3637
+ if (me.animations.length > 0) {
3638
+ me.requestAnimationFrame();
3639
+ }
3640
+ },
3641
+
3642
+ /**
3643
+ * @private
3644
+ */
3645
+ advance: function(count) {
3646
+ var animations = this.animations;
3647
+ var animation, chart;
3648
+ var i = 0;
3649
+
3650
+ while (i < animations.length) {
3651
+ animation = animations[i];
3652
+ chart = animation.chart;
3653
+
3654
+ animation.currentStep = (animation.currentStep || 0) + count;
3655
+ animation.currentStep = Math.min(animation.currentStep, animation.numSteps);
3656
+
3657
+ helpers.callback(animation.render, [chart, animation], chart);
3658
+ helpers.callback(animation.onAnimationProgress, [animation], chart);
3659
+
3660
+ if (animation.currentStep >= animation.numSteps) {
3661
+ helpers.callback(animation.onAnimationComplete, [animation], chart);
3662
+ chart.animating = false;
3663
+ animations.splice(i, 1);
3664
+ } else {
3665
+ ++i;
3666
+ }
3667
+ }
3668
+ }
3669
+ };
3670
+
3671
+ /**
3672
+ * Provided for backward compatibility, use Chart.Animation instead
3673
+ * @prop Chart.Animation#animationObject
3674
+ * @deprecated since version 2.6.0
3675
+ * @todo remove at version 3
3676
+ */
3677
+ Object.defineProperty(Chart.Animation.prototype, 'animationObject', {
3678
+ get: function() {
3679
+ return this;
3680
+ }
3681
+ });
3682
+
3683
+ /**
3684
+ * Provided for backward compatibility, use Chart.Animation#chart instead
3685
+ * @prop Chart.Animation#chartInstance
3686
+ * @deprecated since version 2.6.0
3687
+ * @todo remove at version 3
3688
+ */
3689
+ Object.defineProperty(Chart.Animation.prototype, 'chartInstance', {
3690
+ get: function() {
3691
+ return this.chart;
3692
+ },
3693
+ set: function(value) {
3694
+ this.chart = value;
3695
+ }
3696
+ });
3697
+
3698
+ };
3699
+
3700
+ },{"25":25,"26":26,"45":45}],23:[function(require,module,exports){
3701
+ 'use strict';
3702
+
3703
+ var defaults = require(25);
3704
+ var helpers = require(45);
3705
+ var Interaction = require(28);
3706
+ var platform = require(48);
3707
+
3708
+ module.exports = function(Chart) {
3709
+ var plugins = Chart.plugins;
3710
+
3711
+ // Create a dictionary of chart types, to allow for extension of existing types
3712
+ Chart.types = {};
3713
+
3714
+ // Store a reference to each instance - allowing us to globally resize chart instances on window resize.
3715
+ // Destroy method on the chart will remove the instance of the chart from this reference.
3716
+ Chart.instances = {};
3717
+
3718
+ // Controllers available for dataset visualization eg. bar, line, slice, etc.
3719
+ Chart.controllers = {};
3720
+
3721
+ /**
3722
+ * Initializes the given config with global and chart default values.
3723
+ */
3724
+ function initConfig(config) {
3725
+ config = config || {};
3726
+
3727
+ // Do NOT use configMerge() for the data object because this method merges arrays
3728
+ // and so would change references to labels and datasets, preventing data updates.
3729
+ var data = config.data = config.data || {};
3730
+ data.datasets = data.datasets || [];
3731
+ data.labels = data.labels || [];
3732
+
3733
+ config.options = helpers.configMerge(
3734
+ defaults.global,
3735
+ defaults[config.type],
3736
+ config.options || {});
3737
+
3738
+ return config;
3739
+ }
3740
+
3741
+ /**
3742
+ * Updates the config of the chart
3743
+ * @param chart {Chart} chart to update the options for
3744
+ */
3745
+ function updateConfig(chart) {
3746
+ var newOptions = chart.options;
3747
+
3748
+ // Update Scale(s) with options
3749
+ if (newOptions.scale) {
3750
+ chart.scale.options = newOptions.scale;
3751
+ } else if (newOptions.scales) {
3752
+ newOptions.scales.xAxes.concat(newOptions.scales.yAxes).forEach(function(scaleOptions) {
3753
+ chart.scales[scaleOptions.id].options = scaleOptions;
3754
+ });
3755
+ }
3756
+
3757
+ // Tooltip
3758
+ chart.tooltip._options = newOptions.tooltips;
3759
+ }
3760
+
3761
+ function positionIsHorizontal(position) {
3762
+ return position === 'top' || position === 'bottom';
3763
+ }
3764
+
3765
+ helpers.extend(Chart.prototype, /** @lends Chart */ {
3766
+ /**
3767
+ * @private
3768
+ */
3769
+ construct: function(item, config) {
3770
+ var me = this;
3771
+
3772
+ config = initConfig(config);
3773
+
3774
+ var context = platform.acquireContext(item, config);
3775
+ var canvas = context && context.canvas;
3776
+ var height = canvas && canvas.height;
3777
+ var width = canvas && canvas.width;
3778
+
3779
+ me.id = helpers.uid();
3780
+ me.ctx = context;
3781
+ me.canvas = canvas;
3782
+ me.config = config;
3783
+ me.width = width;
3784
+ me.height = height;
3785
+ me.aspectRatio = height ? width / height : null;
3786
+ me.options = config.options;
3787
+ me._bufferedRender = false;
3788
+
3789
+ /**
3790
+ * Provided for backward compatibility, Chart and Chart.Controller have been merged,
3791
+ * the "instance" still need to be defined since it might be called from plugins.
3792
+ * @prop Chart#chart
3793
+ * @deprecated since version 2.6.0
3794
+ * @todo remove at version 3
3795
+ * @private
3796
+ */
3797
+ me.chart = me;
3798
+ me.controller = me; // chart.chart.controller #inception
3799
+
3800
+ // Add the chart instance to the global namespace
3801
+ Chart.instances[me.id] = me;
3802
+
3803
+ // Define alias to the config data: `chart.data === chart.config.data`
3804
+ Object.defineProperty(me, 'data', {
3805
+ get: function() {
3806
+ return me.config.data;
3807
+ },
3808
+ set: function(value) {
3809
+ me.config.data = value;
3810
+ }
3811
+ });
3812
+
3813
+ if (!context || !canvas) {
3814
+ // The given item is not a compatible context2d element, let's return before finalizing
3815
+ // the chart initialization but after setting basic chart / controller properties that
3816
+ // can help to figure out that the chart is not valid (e.g chart.canvas !== null);
3817
+ // https://github.com/chartjs/Chart.js/issues/2807
3818
+ console.error("Failed to create chart: can't acquire context from the given item");
3819
+ return;
3820
+ }
3821
+
3822
+ me.initialize();
3823
+ me.update();
3824
+ },
3825
+
3826
+ /**
3827
+ * @private
3828
+ */
3829
+ initialize: function() {
3830
+ var me = this;
3831
+
3832
+ // Before init plugin notification
3833
+ plugins.notify(me, 'beforeInit');
3834
+
3835
+ helpers.retinaScale(me, me.options.devicePixelRatio);
3836
+
3837
+ me.bindEvents();
3838
+
3839
+ if (me.options.responsive) {
3840
+ // Initial resize before chart draws (must be silent to preserve initial animations).
3841
+ me.resize(true);
3842
+ }
3843
+
3844
+ // Make sure scales have IDs and are built before we build any controllers.
3845
+ me.ensureScalesHaveIDs();
3846
+ me.buildScales();
3847
+ me.initToolTip();
3848
+
3849
+ // After init plugin notification
3850
+ plugins.notify(me, 'afterInit');
3851
+
3852
+ return me;
3853
+ },
3854
+
3855
+ clear: function() {
3856
+ helpers.canvas.clear(this);
3857
+ return this;
3858
+ },
3859
+
3860
+ stop: function() {
3861
+ // Stops any current animation loop occurring
3862
+ Chart.animationService.cancelAnimation(this);
3863
+ return this;
3864
+ },
3865
+
3866
+ resize: function(silent) {
3867
+ var me = this;
3868
+ var options = me.options;
3869
+ var canvas = me.canvas;
3870
+ var aspectRatio = (options.maintainAspectRatio && me.aspectRatio) || null;
3871
+
3872
+ // the canvas render width and height will be casted to integers so make sure that
3873
+ // the canvas display style uses the same integer values to avoid blurring effect.
3874
+
3875
+ // Set to 0 instead of canvas.size because the size defaults to 300x150 if the element is collased
3876
+ var newWidth = Math.max(0, Math.floor(helpers.getMaximumWidth(canvas)));
3877
+ var newHeight = Math.max(0, Math.floor(aspectRatio ? newWidth / aspectRatio : helpers.getMaximumHeight(canvas)));
3878
+
3879
+ if (me.width === newWidth && me.height === newHeight) {
3880
+ return;
3881
+ }
3882
+
3883
+ canvas.width = me.width = newWidth;
3884
+ canvas.height = me.height = newHeight;
3885
+ canvas.style.width = newWidth + 'px';
3886
+ canvas.style.height = newHeight + 'px';
3887
+
3888
+ helpers.retinaScale(me, options.devicePixelRatio);
3889
+
3890
+ if (!silent) {
3891
+ // Notify any plugins about the resize
3892
+ var newSize = {width: newWidth, height: newHeight};
3893
+ plugins.notify(me, 'resize', [newSize]);
3894
+
3895
+ // Notify of resize
3896
+ if (me.options.onResize) {
3897
+ me.options.onResize(me, newSize);
3898
+ }
3899
+
3900
+ me.stop();
3901
+ me.update(me.options.responsiveAnimationDuration);
3902
+ }
3903
+ },
3904
+
3905
+ ensureScalesHaveIDs: function() {
3906
+ var options = this.options;
3907
+ var scalesOptions = options.scales || {};
3908
+ var scaleOptions = options.scale;
3909
+
3910
+ helpers.each(scalesOptions.xAxes, function(xAxisOptions, index) {
3911
+ xAxisOptions.id = xAxisOptions.id || ('x-axis-' + index);
3912
+ });
3913
+
3914
+ helpers.each(scalesOptions.yAxes, function(yAxisOptions, index) {
3915
+ yAxisOptions.id = yAxisOptions.id || ('y-axis-' + index);
3916
+ });
3917
+
3918
+ if (scaleOptions) {
3919
+ scaleOptions.id = scaleOptions.id || 'scale';
3920
+ }
3921
+ },
3922
+
3923
+ /**
3924
+ * Builds a map of scale ID to scale object for future lookup.
3925
+ */
3926
+ buildScales: function() {
3927
+ var me = this;
3928
+ var options = me.options;
3929
+ var scales = me.scales = {};
3930
+ var items = [];
3931
+
3932
+ if (options.scales) {
3933
+ items = items.concat(
3934
+ (options.scales.xAxes || []).map(function(xAxisOptions) {
3935
+ return {options: xAxisOptions, dtype: 'category', dposition: 'bottom'};
3936
+ }),
3937
+ (options.scales.yAxes || []).map(function(yAxisOptions) {
3938
+ return {options: yAxisOptions, dtype: 'linear', dposition: 'left'};
3939
+ })
3940
+ );
3941
+ }
3942
+
3943
+ if (options.scale) {
3944
+ items.push({
3945
+ options: options.scale,
3946
+ dtype: 'radialLinear',
3947
+ isDefault: true,
3948
+ dposition: 'chartArea'
3949
+ });
3950
+ }
3951
+
3952
+ helpers.each(items, function(item) {
3953
+ var scaleOptions = item.options;
3954
+ var scaleType = helpers.valueOrDefault(scaleOptions.type, item.dtype);
3955
+ var scaleClass = Chart.scaleService.getScaleConstructor(scaleType);
3956
+ if (!scaleClass) {
3957
+ return;
3958
+ }
3959
+
3960
+ if (positionIsHorizontal(scaleOptions.position) !== positionIsHorizontal(item.dposition)) {
3961
+ scaleOptions.position = item.dposition;
3962
+ }
3963
+
3964
+ var scale = new scaleClass({
3965
+ id: scaleOptions.id,
3966
+ options: scaleOptions,
3967
+ ctx: me.ctx,
3968
+ chart: me
3969
+ });
3970
+
3971
+ scales[scale.id] = scale;
3972
+ scale.mergeTicksOptions();
3973
+
3974
+ // TODO(SB): I think we should be able to remove this custom case (options.scale)
3975
+ // and consider it as a regular scale part of the "scales"" map only! This would
3976
+ // make the logic easier and remove some useless? custom code.
3977
+ if (item.isDefault) {
3978
+ me.scale = scale;
3979
+ }
3980
+ });
3981
+
3982
+ Chart.scaleService.addScalesToLayout(this);
3983
+ },
3984
+
3985
+ buildOrUpdateControllers: function() {
3986
+ var me = this;
3987
+ var types = [];
3988
+ var newControllers = [];
3989
+
3990
+ helpers.each(me.data.datasets, function(dataset, datasetIndex) {
3991
+ var meta = me.getDatasetMeta(datasetIndex);
3992
+ var type = dataset.type || me.config.type;
3993
+
3994
+ if (meta.type && meta.type !== type) {
3995
+ me.destroyDatasetMeta(datasetIndex);
3996
+ meta = me.getDatasetMeta(datasetIndex);
3997
+ }
3998
+ meta.type = type;
3999
+
4000
+ types.push(meta.type);
4001
+
4002
+ if (meta.controller) {
4003
+ meta.controller.updateIndex(datasetIndex);
4004
+ } else {
4005
+ var ControllerClass = Chart.controllers[meta.type];
4006
+ if (ControllerClass === undefined) {
4007
+ throw new Error('"' + meta.type + '" is not a chart type.');
4008
+ }
4009
+
4010
+ meta.controller = new ControllerClass(me, datasetIndex);
4011
+ newControllers.push(meta.controller);
4012
+ }
4013
+ }, me);
4014
+
4015
+ return newControllers;
4016
+ },
4017
+
4018
+ /**
4019
+ * Reset the elements of all datasets
4020
+ * @private
4021
+ */
4022
+ resetElements: function() {
4023
+ var me = this;
4024
+ helpers.each(me.data.datasets, function(dataset, datasetIndex) {
4025
+ me.getDatasetMeta(datasetIndex).controller.reset();
4026
+ }, me);
4027
+ },
4028
+
4029
+ /**
4030
+ * Resets the chart back to it's state before the initial animation
4031
+ */
4032
+ reset: function() {
4033
+ this.resetElements();
4034
+ this.tooltip.initialize();
4035
+ },
4036
+
4037
+ update: function(config) {
4038
+ var me = this;
4039
+
4040
+ if (!config || typeof config !== 'object') {
4041
+ // backwards compatibility
4042
+ config = {
4043
+ duration: config,
4044
+ lazy: arguments[1]
4045
+ };
4046
+ }
4047
+
4048
+ updateConfig(me);
4049
+
4050
+ if (plugins.notify(me, 'beforeUpdate') === false) {
4051
+ return;
4052
+ }
4053
+
4054
+ // In case the entire data object changed
4055
+ me.tooltip._data = me.data;
4056
+
4057
+ // Make sure dataset controllers are updated and new controllers are reset
4058
+ var newControllers = me.buildOrUpdateControllers();
4059
+
4060
+ // Make sure all dataset controllers have correct meta data counts
4061
+ helpers.each(me.data.datasets, function(dataset, datasetIndex) {
4062
+ me.getDatasetMeta(datasetIndex).controller.buildOrUpdateElements();
4063
+ }, me);
4064
+
4065
+ me.updateLayout();
4066
+
4067
+ // Can only reset the new controllers after the scales have been updated
4068
+ helpers.each(newControllers, function(controller) {
4069
+ controller.reset();
4070
+ });
4071
+
4072
+ me.updateDatasets();
4073
+
4074
+ // Need to reset tooltip in case it is displayed with elements that are removed
4075
+ // after update.
4076
+ me.tooltip.initialize();
4077
+
4078
+ // Last active contains items that were previously in the tooltip.
4079
+ // When we reset the tooltip, we need to clear it
4080
+ me.lastActive = [];
4081
+
4082
+ // Do this before render so that any plugins that need final scale updates can use it
4083
+ plugins.notify(me, 'afterUpdate');
4084
+
4085
+ if (me._bufferedRender) {
4086
+ me._bufferedRequest = {
4087
+ duration: config.duration,
4088
+ easing: config.easing,
4089
+ lazy: config.lazy
4090
+ };
4091
+ } else {
4092
+ me.render(config);
4093
+ }
4094
+ },
4095
+
4096
+ /**
4097
+ * Updates the chart layout unless a plugin returns `false` to the `beforeLayout`
4098
+ * hook, in which case, plugins will not be called on `afterLayout`.
4099
+ * @private
4100
+ */
4101
+ updateLayout: function() {
4102
+ var me = this;
4103
+
4104
+ if (plugins.notify(me, 'beforeLayout') === false) {
4105
+ return;
4106
+ }
4107
+
4108
+ Chart.layoutService.update(this, this.width, this.height);
4109
+
4110
+ /**
4111
+ * Provided for backward compatibility, use `afterLayout` instead.
4112
+ * @method IPlugin#afterScaleUpdate
4113
+ * @deprecated since version 2.5.0
4114
+ * @todo remove at version 3
4115
+ * @private
4116
+ */
4117
+ plugins.notify(me, 'afterScaleUpdate');
4118
+ plugins.notify(me, 'afterLayout');
4119
+ },
4120
+
4121
+ /**
4122
+ * Updates all datasets unless a plugin returns `false` to the `beforeDatasetsUpdate`
4123
+ * hook, in which case, plugins will not be called on `afterDatasetsUpdate`.
4124
+ * @private
4125
+ */
4126
+ updateDatasets: function() {
4127
+ var me = this;
4128
+
4129
+ if (plugins.notify(me, 'beforeDatasetsUpdate') === false) {
4130
+ return;
4131
+ }
4132
+
4133
+ for (var i = 0, ilen = me.data.datasets.length; i < ilen; ++i) {
4134
+ me.updateDataset(i);
4135
+ }
4136
+
4137
+ plugins.notify(me, 'afterDatasetsUpdate');
4138
+ },
4139
+
4140
+ /**
4141
+ * Updates dataset at index unless a plugin returns `false` to the `beforeDatasetUpdate`
4142
+ * hook, in which case, plugins will not be called on `afterDatasetUpdate`.
4143
+ * @private
4144
+ */
4145
+ updateDataset: function(index) {
4146
+ var me = this;
4147
+ var meta = me.getDatasetMeta(index);
4148
+ var args = {
4149
+ meta: meta,
4150
+ index: index
4151
+ };
4152
+
4153
+ if (plugins.notify(me, 'beforeDatasetUpdate', [args]) === false) {
4154
+ return;
4155
+ }
4156
+
4157
+ meta.controller.update();
4158
+
4159
+ plugins.notify(me, 'afterDatasetUpdate', [args]);
4160
+ },
4161
+
4162
+ render: function(config) {
4163
+ var me = this;
4164
+
4165
+ if (!config || typeof config !== 'object') {
4166
+ // backwards compatibility
4167
+ config = {
4168
+ duration: config,
4169
+ lazy: arguments[1]
4170
+ };
4171
+ }
4172
+
4173
+ var duration = config.duration;
4174
+ var lazy = config.lazy;
4175
+
4176
+ if (plugins.notify(me, 'beforeRender') === false) {
4177
+ return;
4178
+ }
4179
+
4180
+ var animationOptions = me.options.animation;
4181
+ var onComplete = function(animation) {
4182
+ plugins.notify(me, 'afterRender');
4183
+ helpers.callback(animationOptions && animationOptions.onComplete, [animation], me);
4184
+ };
4185
+
4186
+ if (animationOptions && ((typeof duration !== 'undefined' && duration !== 0) || (typeof duration === 'undefined' && animationOptions.duration !== 0))) {
4187
+ var animation = new Chart.Animation({
4188
+ numSteps: (duration || animationOptions.duration) / 16.66, // 60 fps
4189
+ easing: config.easing || animationOptions.easing,
4190
+
4191
+ render: function(chart, animationObject) {
4192
+ var easingFunction = helpers.easing.effects[animationObject.easing];
4193
+ var currentStep = animationObject.currentStep;
4194
+ var stepDecimal = currentStep / animationObject.numSteps;
4195
+
4196
+ chart.draw(easingFunction(stepDecimal), stepDecimal, currentStep);
4197
+ },
4198
+
4199
+ onAnimationProgress: animationOptions.onProgress,
4200
+ onAnimationComplete: onComplete
4201
+ });
4202
+
4203
+ Chart.animationService.addAnimation(me, animation, duration, lazy);
4204
+ } else {
4205
+ me.draw();
4206
+
4207
+ // See https://github.com/chartjs/Chart.js/issues/3781
4208
+ onComplete(new Chart.Animation({numSteps: 0, chart: me}));
4209
+ }
4210
+
4211
+ return me;
4212
+ },
4213
+
4214
+ draw: function(easingValue) {
4215
+ var me = this;
4216
+
4217
+ me.clear();
4218
+
4219
+ if (helpers.isNullOrUndef(easingValue)) {
4220
+ easingValue = 1;
4221
+ }
4222
+
4223
+ me.transition(easingValue);
4224
+
4225
+ if (plugins.notify(me, 'beforeDraw', [easingValue]) === false) {
4226
+ return;
4227
+ }
4228
+
4229
+ // Draw all the scales
4230
+ helpers.each(me.boxes, function(box) {
4231
+ box.draw(me.chartArea);
4232
+ }, me);
4233
+
4234
+ if (me.scale) {
4235
+ me.scale.draw();
4236
+ }
4237
+
4238
+ me.drawDatasets(easingValue);
4239
+ me._drawTooltip(easingValue);
4240
+
4241
+ plugins.notify(me, 'afterDraw', [easingValue]);
4242
+ },
4243
+
4244
+ /**
4245
+ * @private
4246
+ */
4247
+ transition: function(easingValue) {
4248
+ var me = this;
4249
+
4250
+ for (var i = 0, ilen = (me.data.datasets || []).length; i < ilen; ++i) {
4251
+ if (me.isDatasetVisible(i)) {
4252
+ me.getDatasetMeta(i).controller.transition(easingValue);
4253
+ }
4254
+ }
4255
+
4256
+ me.tooltip.transition(easingValue);
4257
+ },
4258
+
4259
+ /**
4260
+ * Draws all datasets unless a plugin returns `false` to the `beforeDatasetsDraw`
4261
+ * hook, in which case, plugins will not be called on `afterDatasetsDraw`.
4262
+ * @private
4263
+ */
4264
+ drawDatasets: function(easingValue) {
4265
+ var me = this;
4266
+
4267
+ if (plugins.notify(me, 'beforeDatasetsDraw', [easingValue]) === false) {
4268
+ return;
4269
+ }
4270
+
4271
+ // Draw datasets reversed to support proper line stacking
4272
+ for (var i = (me.data.datasets || []).length - 1; i >= 0; --i) {
4273
+ if (me.isDatasetVisible(i)) {
4274
+ me.drawDataset(i, easingValue);
4275
+ }
4276
+ }
4277
+
4278
+ plugins.notify(me, 'afterDatasetsDraw', [easingValue]);
4279
+ },
4280
+
4281
+ /**
4282
+ * Draws dataset at index unless a plugin returns `false` to the `beforeDatasetDraw`
4283
+ * hook, in which case, plugins will not be called on `afterDatasetDraw`.
4284
+ * @private
4285
+ */
4286
+ drawDataset: function(index, easingValue) {
4287
+ var me = this;
4288
+ var meta = me.getDatasetMeta(index);
4289
+ var args = {
4290
+ meta: meta,
4291
+ index: index,
4292
+ easingValue: easingValue
4293
+ };
4294
+
4295
+ if (plugins.notify(me, 'beforeDatasetDraw', [args]) === false) {
4296
+ return;
4297
+ }
4298
+
4299
+ meta.controller.draw(easingValue);
4300
+
4301
+ plugins.notify(me, 'afterDatasetDraw', [args]);
4302
+ },
4303
+
4304
+ /**
4305
+ * Draws tooltip unless a plugin returns `false` to the `beforeTooltipDraw`
4306
+ * hook, in which case, plugins will not be called on `afterTooltipDraw`.
4307
+ * @private
4308
+ */
4309
+ _drawTooltip: function(easingValue) {
4310
+ var me = this;
4311
+ var tooltip = me.tooltip;
4312
+ var args = {
4313
+ tooltip: tooltip,
4314
+ easingValue: easingValue
4315
+ };
4316
+
4317
+ if (plugins.notify(me, 'beforeTooltipDraw', [args]) === false) {
4318
+ return;
4319
+ }
4320
+
4321
+ tooltip.draw();
4322
+
4323
+ plugins.notify(me, 'afterTooltipDraw', [args]);
4324
+ },
4325
+
4326
+ // Get the single element that was clicked on
4327
+ // @return : An object containing the dataset index and element index of the matching element. Also contains the rectangle that was draw
4328
+ getElementAtEvent: function(e) {
4329
+ return Interaction.modes.single(this, e);
4330
+ },
4331
+
4332
+ getElementsAtEvent: function(e) {
4333
+ return Interaction.modes.label(this, e, {intersect: true});
4334
+ },
4335
+
4336
+ getElementsAtXAxis: function(e) {
4337
+ return Interaction.modes['x-axis'](this, e, {intersect: true});
4338
+ },
4339
+
4340
+ getElementsAtEventForMode: function(e, mode, options) {
4341
+ var method = Interaction.modes[mode];
4342
+ if (typeof method === 'function') {
4343
+ return method(this, e, options);
4344
+ }
4345
+
4346
+ return [];
4347
+ },
4348
+
4349
+ getDatasetAtEvent: function(e) {
4350
+ return Interaction.modes.dataset(this, e, {intersect: true});
4351
+ },
4352
+
4353
+ getDatasetMeta: function(datasetIndex) {
4354
+ var me = this;
4355
+ var dataset = me.data.datasets[datasetIndex];
4356
+ if (!dataset._meta) {
4357
+ dataset._meta = {};
4358
+ }
4359
+
4360
+ var meta = dataset._meta[me.id];
4361
+ if (!meta) {
4362
+ meta = dataset._meta[me.id] = {
4363
+ type: null,
4364
+ data: [],
4365
+ dataset: null,
4366
+ controller: null,
4367
+ hidden: null, // See isDatasetVisible() comment
4368
+ xAxisID: null,
4369
+ yAxisID: null
4370
+ };
4371
+ }
4372
+
4373
+ return meta;
4374
+ },
4375
+
4376
+ getVisibleDatasetCount: function() {
4377
+ var count = 0;
4378
+ for (var i = 0, ilen = this.data.datasets.length; i < ilen; ++i) {
4379
+ if (this.isDatasetVisible(i)) {
4380
+ count++;
4381
+ }
4382
+ }
4383
+ return count;
4384
+ },
4385
+
4386
+ isDatasetVisible: function(datasetIndex) {
4387
+ var meta = this.getDatasetMeta(datasetIndex);
4388
+
4389
+ // meta.hidden is a per chart dataset hidden flag override with 3 states: if true or false,
4390
+ // the dataset.hidden value is ignored, else if null, the dataset hidden state is returned.
4391
+ return typeof meta.hidden === 'boolean' ? !meta.hidden : !this.data.datasets[datasetIndex].hidden;
4392
+ },
4393
+
4394
+ generateLegend: function() {
4395
+ return this.options.legendCallback(this);
4396
+ },
4397
+
4398
+ /**
4399
+ * @private
4400
+ */
4401
+ destroyDatasetMeta: function(datasetIndex) {
4402
+ var id = this.id;
4403
+ var dataset = this.data.datasets[datasetIndex];
4404
+ var meta = dataset._meta && dataset._meta[id];
4405
+
4406
+ if (meta) {
4407
+ meta.controller.destroy();
4408
+ delete dataset._meta[id];
4409
+ }
4410
+ },
4411
+
4412
+ destroy: function() {
4413
+ var me = this;
4414
+ var canvas = me.canvas;
4415
+ var i, ilen;
4416
+
4417
+ me.stop();
4418
+
4419
+ // dataset controllers need to cleanup associated data
4420
+ for (i = 0, ilen = me.data.datasets.length; i < ilen; ++i) {
4421
+ me.destroyDatasetMeta(i);
4422
+ }
4423
+
4424
+ if (canvas) {
4425
+ me.unbindEvents();
4426
+ helpers.canvas.clear(me);
4427
+ platform.releaseContext(me.ctx);
4428
+ me.canvas = null;
4429
+ me.ctx = null;
4430
+ }
4431
+
4432
+ plugins.notify(me, 'destroy');
4433
+
4434
+ delete Chart.instances[me.id];
4435
+ },
4436
+
4437
+ toBase64Image: function() {
4438
+ return this.canvas.toDataURL.apply(this.canvas, arguments);
4439
+ },
4440
+
4441
+ initToolTip: function() {
4442
+ var me = this;
4443
+ me.tooltip = new Chart.Tooltip({
4444
+ _chart: me,
4445
+ _chartInstance: me, // deprecated, backward compatibility
4446
+ _data: me.data,
4447
+ _options: me.options.tooltips
4448
+ }, me);
4449
+ },
4450
+
4451
+ /**
4452
+ * @private
4453
+ */
4454
+ bindEvents: function() {
4455
+ var me = this;
4456
+ var listeners = me._listeners = {};
4457
+ var listener = function() {
4458
+ me.eventHandler.apply(me, arguments);
4459
+ };
4460
+
4461
+ helpers.each(me.options.events, function(type) {
4462
+ platform.addEventListener(me, type, listener);
4463
+ listeners[type] = listener;
4464
+ });
4465
+
4466
+ // Elements used to detect size change should not be injected for non responsive charts.
4467
+ // See https://github.com/chartjs/Chart.js/issues/2210
4468
+ if (me.options.responsive) {
4469
+ listener = function() {
4470
+ me.resize();
4471
+ };
4472
+
4473
+ platform.addEventListener(me, 'resize', listener);
4474
+ listeners.resize = listener;
4475
+ }
4476
+ },
4477
+
4478
+ /**
4479
+ * @private
4480
+ */
4481
+ unbindEvents: function() {
4482
+ var me = this;
4483
+ var listeners = me._listeners;
4484
+ if (!listeners) {
4485
+ return;
4486
+ }
4487
+
4488
+ delete me._listeners;
4489
+ helpers.each(listeners, function(listener, type) {
4490
+ platform.removeEventListener(me, type, listener);
4491
+ });
4492
+ },
4493
+
4494
+ updateHoverStyle: function(elements, mode, enabled) {
4495
+ var method = enabled ? 'setHoverStyle' : 'removeHoverStyle';
4496
+ var element, i, ilen;
4497
+
4498
+ for (i = 0, ilen = elements.length; i < ilen; ++i) {
4499
+ element = elements[i];
4500
+ if (element) {
4501
+ this.getDatasetMeta(element._datasetIndex).controller[method](element);
4502
+ }
4503
+ }
4504
+ },
4505
+
4506
+ /**
4507
+ * @private
4508
+ */
4509
+ eventHandler: function(e) {
4510
+ var me = this;
4511
+ var tooltip = me.tooltip;
4512
+
4513
+ if (plugins.notify(me, 'beforeEvent', [e]) === false) {
4514
+ return;
4515
+ }
4516
+
4517
+ // Buffer any update calls so that renders do not occur
4518
+ me._bufferedRender = true;
4519
+ me._bufferedRequest = null;
4520
+
4521
+ var changed = me.handleEvent(e);
4522
+ changed |= tooltip && tooltip.handleEvent(e);
4523
+
4524
+ plugins.notify(me, 'afterEvent', [e]);
4525
+
4526
+ var bufferedRequest = me._bufferedRequest;
4527
+ if (bufferedRequest) {
4528
+ // If we have an update that was triggered, we need to do a normal render
4529
+ me.render(bufferedRequest);
4530
+ } else if (changed && !me.animating) {
4531
+ // If entering, leaving, or changing elements, animate the change via pivot
4532
+ me.stop();
4533
+
4534
+ // We only need to render at this point. Updating will cause scales to be
4535
+ // recomputed generating flicker & using more memory than necessary.
4536
+ me.render(me.options.hover.animationDuration, true);
4537
+ }
4538
+
4539
+ me._bufferedRender = false;
4540
+ me._bufferedRequest = null;
4541
+
4542
+ return me;
4543
+ },
4544
+
4545
+ /**
4546
+ * Handle an event
4547
+ * @private
4548
+ * @param {IEvent} event the event to handle
4549
+ * @return {Boolean} true if the chart needs to re-render
4550
+ */
4551
+ handleEvent: function(e) {
4552
+ var me = this;
4553
+ var options = me.options || {};
4554
+ var hoverOptions = options.hover;
4555
+ var changed = false;
4556
+
4557
+ me.lastActive = me.lastActive || [];
4558
+
4559
+ // Find Active Elements for hover and tooltips
4560
+ if (e.type === 'mouseout') {
4561
+ me.active = [];
4562
+ } else {
4563
+ me.active = me.getElementsAtEventForMode(e, hoverOptions.mode, hoverOptions);
4564
+ }
4565
+
4566
+ // Invoke onHover hook
4567
+ // Need to call with native event here to not break backwards compatibility
4568
+ helpers.callback(options.onHover || options.hover.onHover, [e.native, me.active], me);
4569
+
4570
+ if (e.type === 'mouseup' || e.type === 'click') {
4571
+ if (options.onClick) {
4572
+ // Use e.native here for backwards compatibility
4573
+ options.onClick.call(me, e.native, me.active);
4574
+ }
4575
+ }
4576
+
4577
+ // Remove styling for last active (even if it may still be active)
4578
+ if (me.lastActive.length) {
4579
+ me.updateHoverStyle(me.lastActive, hoverOptions.mode, false);
4580
+ }
4581
+
4582
+ // Built in hover styling
4583
+ if (me.active.length && hoverOptions.mode) {
4584
+ me.updateHoverStyle(me.active, hoverOptions.mode, true);
4585
+ }
4586
+
4587
+ changed = !helpers.arrayEquals(me.active, me.lastActive);
4588
+
4589
+ // Remember Last Actives
4590
+ me.lastActive = me.active;
4591
+
4592
+ return changed;
4593
+ }
4594
+ });
4595
+
4596
+ /**
4597
+ * Provided for backward compatibility, use Chart instead.
4598
+ * @class Chart.Controller
4599
+ * @deprecated since version 2.6.0
4600
+ * @todo remove at version 3
4601
+ * @private
4602
+ */
4603
+ Chart.Controller = Chart;
4604
+ };
4605
+
4606
+ },{"25":25,"28":28,"45":45,"48":48}],24:[function(require,module,exports){
4607
+ 'use strict';
4608
+
4609
+ var helpers = require(45);
4610
+
4611
+ module.exports = function(Chart) {
4612
+
4613
+ var arrayEvents = ['push', 'pop', 'shift', 'splice', 'unshift'];
4614
+
4615
+ /**
4616
+ * Hooks the array methods that add or remove values ('push', pop', 'shift', 'splice',
4617
+ * 'unshift') and notify the listener AFTER the array has been altered. Listeners are
4618
+ * called on the 'onData*' callbacks (e.g. onDataPush, etc.) with same arguments.
4619
+ */
4620
+ function listenArrayEvents(array, listener) {
4621
+ if (array._chartjs) {
4622
+ array._chartjs.listeners.push(listener);
4623
+ return;
4624
+ }
4625
+
4626
+ Object.defineProperty(array, '_chartjs', {
4627
+ configurable: true,
4628
+ enumerable: false,
4629
+ value: {
4630
+ listeners: [listener]
4631
+ }
4632
+ });
4633
+
4634
+ arrayEvents.forEach(function(key) {
4635
+ var method = 'onData' + key.charAt(0).toUpperCase() + key.slice(1);
4636
+ var base = array[key];
4637
+
4638
+ Object.defineProperty(array, key, {
4639
+ configurable: true,
4640
+ enumerable: false,
4641
+ value: function() {
4642
+ var args = Array.prototype.slice.call(arguments);
4643
+ var res = base.apply(this, args);
4644
+
4645
+ helpers.each(array._chartjs.listeners, function(object) {
4646
+ if (typeof object[method] === 'function') {
4647
+ object[method].apply(object, args);
4648
+ }
4649
+ });
4650
+
4651
+ return res;
4652
+ }
4653
+ });
4654
+ });
4655
+ }
4656
+
4657
+ /**
4658
+ * Removes the given array event listener and cleanup extra attached properties (such as
4659
+ * the _chartjs stub and overridden methods) if array doesn't have any more listeners.
4660
+ */
4661
+ function unlistenArrayEvents(array, listener) {
4662
+ var stub = array._chartjs;
4663
+ if (!stub) {
4664
+ return;
4665
+ }
4666
+
4667
+ var listeners = stub.listeners;
4668
+ var index = listeners.indexOf(listener);
4669
+ if (index !== -1) {
4670
+ listeners.splice(index, 1);
4671
+ }
4672
+
4673
+ if (listeners.length > 0) {
4674
+ return;
4675
+ }
4676
+
4677
+ arrayEvents.forEach(function(key) {
4678
+ delete array[key];
4679
+ });
4680
+
4681
+ delete array._chartjs;
4682
+ }
4683
+
4684
+ // Base class for all dataset controllers (line, bar, etc)
4685
+ Chart.DatasetController = function(chart, datasetIndex) {
4686
+ this.initialize(chart, datasetIndex);
4687
+ };
4688
+
4689
+ helpers.extend(Chart.DatasetController.prototype, {
4690
+
4691
+ /**
4692
+ * Element type used to generate a meta dataset (e.g. Chart.element.Line).
4693
+ * @type {Chart.core.element}
4694
+ */
4695
+ datasetElementType: null,
4696
+
4697
+ /**
4698
+ * Element type used to generate a meta data (e.g. Chart.element.Point).
4699
+ * @type {Chart.core.element}
4700
+ */
4701
+ dataElementType: null,
4702
+
4703
+ initialize: function(chart, datasetIndex) {
4704
+ var me = this;
4705
+ me.chart = chart;
4706
+ me.index = datasetIndex;
4707
+ me.linkScales();
4708
+ me.addElements();
4709
+ },
4710
+
4711
+ updateIndex: function(datasetIndex) {
4712
+ this.index = datasetIndex;
4713
+ },
4714
+
4715
+ linkScales: function() {
4716
+ var me = this;
4717
+ var meta = me.getMeta();
4718
+ var dataset = me.getDataset();
4719
+
4720
+ if (meta.xAxisID === null) {
4721
+ meta.xAxisID = dataset.xAxisID || me.chart.options.scales.xAxes[0].id;
4722
+ }
4723
+ if (meta.yAxisID === null) {
4724
+ meta.yAxisID = dataset.yAxisID || me.chart.options.scales.yAxes[0].id;
4725
+ }
4726
+ },
4727
+
4728
+ getDataset: function() {
4729
+ return this.chart.data.datasets[this.index];
4730
+ },
4731
+
4732
+ getMeta: function() {
4733
+ return this.chart.getDatasetMeta(this.index);
4734
+ },
4735
+
4736
+ getScaleForId: function(scaleID) {
4737
+ return this.chart.scales[scaleID];
4738
+ },
4739
+
4740
+ reset: function() {
4741
+ this.update(true);
4742
+ },
4743
+
4744
+ /**
4745
+ * @private
4746
+ */
4747
+ destroy: function() {
4748
+ if (this._data) {
4749
+ unlistenArrayEvents(this._data, this);
4750
+ }
4751
+ },
4752
+
4753
+ createMetaDataset: function() {
4754
+ var me = this;
4755
+ var type = me.datasetElementType;
4756
+ return type && new type({
4757
+ _chart: me.chart,
4758
+ _datasetIndex: me.index
4759
+ });
4760
+ },
4761
+
4762
+ createMetaData: function(index) {
4763
+ var me = this;
4764
+ var type = me.dataElementType;
4765
+ return type && new type({
4766
+ _chart: me.chart,
4767
+ _datasetIndex: me.index,
4768
+ _index: index
4769
+ });
4770
+ },
4771
+
4772
+ addElements: function() {
4773
+ var me = this;
4774
+ var meta = me.getMeta();
4775
+ var data = me.getDataset().data || [];
4776
+ var metaData = meta.data;
4777
+ var i, ilen;
4778
+
4779
+ for (i = 0, ilen = data.length; i < ilen; ++i) {
4780
+ metaData[i] = metaData[i] || me.createMetaData(i);
4781
+ }
4782
+
4783
+ meta.dataset = meta.dataset || me.createMetaDataset();
4784
+ },
4785
+
4786
+ addElementAndReset: function(index) {
4787
+ var element = this.createMetaData(index);
4788
+ this.getMeta().data.splice(index, 0, element);
4789
+ this.updateElement(element, index, true);
4790
+ },
4791
+
4792
+ buildOrUpdateElements: function() {
4793
+ var me = this;
4794
+ var dataset = me.getDataset();
4795
+ var data = dataset.data || (dataset.data = []);
4796
+
4797
+ // In order to correctly handle data addition/deletion animation (an thus simulate
4798
+ // real-time charts), we need to monitor these data modifications and synchronize
4799
+ // the internal meta data accordingly.
4800
+ if (me._data !== data) {
4801
+ if (me._data) {
4802
+ // This case happens when the user replaced the data array instance.
4803
+ unlistenArrayEvents(me._data, me);
4804
+ }
4805
+
4806
+ listenArrayEvents(data, me);
4807
+ me._data = data;
4808
+ }
4809
+
4810
+ // Re-sync meta data in case the user replaced the data array or if we missed
4811
+ // any updates and so make sure that we handle number of datapoints changing.
4812
+ me.resyncElements();
4813
+ },
4814
+
4815
+ update: helpers.noop,
4816
+
4817
+ transition: function(easingValue) {
4818
+ var meta = this.getMeta();
4819
+ var elements = meta.data || [];
4820
+ var ilen = elements.length;
4821
+ var i = 0;
4822
+
4823
+ for (; i < ilen; ++i) {
4824
+ elements[i].transition(easingValue);
4825
+ }
4826
+
4827
+ if (meta.dataset) {
4828
+ meta.dataset.transition(easingValue);
4829
+ }
4830
+ },
4831
+
4832
+ draw: function() {
4833
+ var meta = this.getMeta();
4834
+ var elements = meta.data || [];
4835
+ var ilen = elements.length;
4836
+ var i = 0;
4837
+
4838
+ if (meta.dataset) {
4839
+ meta.dataset.draw();
4840
+ }
4841
+
4842
+ for (; i < ilen; ++i) {
4843
+ elements[i].draw();
4844
+ }
4845
+ },
4846
+
4847
+ removeHoverStyle: function(element, elementOpts) {
4848
+ var dataset = this.chart.data.datasets[element._datasetIndex];
4849
+ var index = element._index;
4850
+ var custom = element.custom || {};
4851
+ var valueOrDefault = helpers.valueAtIndexOrDefault;
4852
+ var model = element._model;
4853
+
4854
+ model.backgroundColor = custom.backgroundColor ? custom.backgroundColor : valueOrDefault(dataset.backgroundColor, index, elementOpts.backgroundColor);
4855
+ model.borderColor = custom.borderColor ? custom.borderColor : valueOrDefault(dataset.borderColor, index, elementOpts.borderColor);
4856
+ model.borderWidth = custom.borderWidth ? custom.borderWidth : valueOrDefault(dataset.borderWidth, index, elementOpts.borderWidth);
4857
+ },
4858
+
4859
+ setHoverStyle: function(element) {
4860
+ var dataset = this.chart.data.datasets[element._datasetIndex];
4861
+ var index = element._index;
4862
+ var custom = element.custom || {};
4863
+ var valueOrDefault = helpers.valueAtIndexOrDefault;
4864
+ var getHoverColor = helpers.getHoverColor;
4865
+ var model = element._model;
4866
+
4867
+ model.backgroundColor = custom.hoverBackgroundColor ? custom.hoverBackgroundColor : valueOrDefault(dataset.hoverBackgroundColor, index, getHoverColor(model.backgroundColor));
4868
+ model.borderColor = custom.hoverBorderColor ? custom.hoverBorderColor : valueOrDefault(dataset.hoverBorderColor, index, getHoverColor(model.borderColor));
4869
+ model.borderWidth = custom.hoverBorderWidth ? custom.hoverBorderWidth : valueOrDefault(dataset.hoverBorderWidth, index, model.borderWidth);
4870
+ },
4871
+
4872
+ /**
4873
+ * @private
4874
+ */
4875
+ resyncElements: function() {
4876
+ var me = this;
4877
+ var meta = me.getMeta();
4878
+ var data = me.getDataset().data;
4879
+ var numMeta = meta.data.length;
4880
+ var numData = data.length;
4881
+
4882
+ if (numData < numMeta) {
4883
+ meta.data.splice(numData, numMeta - numData);
4884
+ } else if (numData > numMeta) {
4885
+ me.insertElements(numMeta, numData - numMeta);
4886
+ }
4887
+ },
4888
+
4889
+ /**
4890
+ * @private
4891
+ */
4892
+ insertElements: function(start, count) {
4893
+ for (var i = 0; i < count; ++i) {
4894
+ this.addElementAndReset(start + i);
4895
+ }
4896
+ },
4897
+
4898
+ /**
4899
+ * @private
4900
+ */
4901
+ onDataPush: function() {
4902
+ this.insertElements(this.getDataset().data.length - 1, arguments.length);
4903
+ },
4904
+
4905
+ /**
4906
+ * @private
4907
+ */
4908
+ onDataPop: function() {
4909
+ this.getMeta().data.pop();
4910
+ },
4911
+
4912
+ /**
4913
+ * @private
4914
+ */
4915
+ onDataShift: function() {
4916
+ this.getMeta().data.shift();
4917
+ },
4918
+
4919
+ /**
4920
+ * @private
4921
+ */
4922
+ onDataSplice: function(start, count) {
4923
+ this.getMeta().data.splice(start, count);
4924
+ this.insertElements(start, arguments.length - 2);
4925
+ },
4926
+
4927
+ /**
4928
+ * @private
4929
+ */
4930
+ onDataUnshift: function() {
4931
+ this.insertElements(0, arguments.length);
4932
+ }
4933
+ });
4934
+
4935
+ Chart.DatasetController.extend = helpers.inherits;
4936
+ };
4937
+
4938
+ },{"45":45}],25:[function(require,module,exports){
4939
+ 'use strict';
4940
+
4941
+ var helpers = require(45);
4942
+
4943
+ module.exports = {
4944
+ /**
4945
+ * @private
4946
+ */
4947
+ _set: function(scope, values) {
4948
+ return helpers.merge(this[scope] || (this[scope] = {}), values);
4949
+ }
4950
+ };
4951
+
4952
+ },{"45":45}],26:[function(require,module,exports){
4953
+ 'use strict';
4954
+
4955
+ var color = require(3);
4956
+ var helpers = require(45);
4957
+
4958
+ function interpolate(start, view, model, ease) {
4959
+ var keys = Object.keys(model);
4960
+ var i, ilen, key, actual, origin, target, type, c0, c1;
4961
+
4962
+ for (i = 0, ilen = keys.length; i < ilen; ++i) {
4963
+ key = keys[i];
4964
+
4965
+ target = model[key];
4966
+
4967
+ // if a value is added to the model after pivot() has been called, the view
4968
+ // doesn't contain it, so let's initialize the view to the target value.
4969
+ if (!view.hasOwnProperty(key)) {
4970
+ view[key] = target;
4971
+ }
4972
+
4973
+ actual = view[key];
4974
+
4975
+ if (actual === target || key[0] === '_') {
4976
+ continue;
4977
+ }
4978
+
4979
+ if (!start.hasOwnProperty(key)) {
4980
+ start[key] = actual;
4981
+ }
4982
+
4983
+ origin = start[key];
4984
+
4985
+ type = typeof target;
4986
+
4987
+ if (type === typeof origin) {
4988
+ if (type === 'string') {
4989
+ c0 = color(origin);
4990
+ if (c0.valid) {
4991
+ c1 = color(target);
4992
+ if (c1.valid) {
4993
+ view[key] = c1.mix(c0, ease).rgbString();
4994
+ continue;
4995
+ }
4996
+ }
4997
+ } else if (type === 'number' && isFinite(origin) && isFinite(target)) {
4998
+ view[key] = origin + (target - origin) * ease;
4999
+ continue;
5000
+ }
5001
+ }
5002
+
5003
+ view[key] = target;
5004
+ }
5005
+ }
5006
+
5007
+ var Element = function(configuration) {
5008
+ helpers.extend(this, configuration);
5009
+ this.initialize.apply(this, arguments);
5010
+ };
5011
+
5012
+ helpers.extend(Element.prototype, {
5013
+
5014
+ initialize: function() {
5015
+ this.hidden = false;
5016
+ },
5017
+
5018
+ pivot: function() {
5019
+ var me = this;
5020
+ if (!me._view) {
5021
+ me._view = helpers.clone(me._model);
5022
+ }
5023
+ me._start = {};
5024
+ return me;
5025
+ },
5026
+
5027
+ transition: function(ease) {
5028
+ var me = this;
5029
+ var model = me._model;
5030
+ var start = me._start;
5031
+ var view = me._view;
5032
+
5033
+ // No animation -> No Transition
5034
+ if (!model || ease === 1) {
5035
+ me._view = model;
5036
+ me._start = null;
5037
+ return me;
5038
+ }
5039
+
5040
+ if (!view) {
5041
+ view = me._view = {};
5042
+ }
5043
+
5044
+ if (!start) {
5045
+ start = me._start = {};
5046
+ }
5047
+
5048
+ interpolate(start, view, model, ease);
5049
+
5050
+ return me;
5051
+ },
5052
+
5053
+ tooltipPosition: function() {
5054
+ return {
5055
+ x: this._model.x,
5056
+ y: this._model.y
5057
+ };
5058
+ },
5059
+
5060
+ hasValue: function() {
5061
+ return helpers.isNumber(this._model.x) && helpers.isNumber(this._model.y);
5062
+ }
5063
+ });
5064
+
5065
+ Element.extend = helpers.inherits;
5066
+
5067
+ module.exports = Element;
5068
+
5069
+ },{"3":3,"45":45}],27:[function(require,module,exports){
5070
+ /* global window: false */
5071
+ /* global document: false */
5072
+ 'use strict';
5073
+
5074
+ var color = require(3);
5075
+ var defaults = require(25);
5076
+ var helpers = require(45);
5077
+
5078
+ module.exports = function(Chart) {
5079
+
5080
+ // -- Basic js utility methods
5081
+
5082
+ helpers.configMerge = function(/* objects ... */) {
5083
+ return helpers.merge(helpers.clone(arguments[0]), [].slice.call(arguments, 1), {
5084
+ merger: function(key, target, source, options) {
5085
+ var tval = target[key] || {};
5086
+ var sval = source[key];
5087
+
5088
+ if (key === 'scales') {
5089
+ // scale config merging is complex. Add our own function here for that
5090
+ target[key] = helpers.scaleMerge(tval, sval);
5091
+ } else if (key === 'scale') {
5092
+ // used in polar area & radar charts since there is only one scale
5093
+ target[key] = helpers.merge(tval, [Chart.scaleService.getScaleDefaults(sval.type), sval]);
5094
+ } else {
5095
+ helpers._merger(key, target, source, options);
5096
+ }
5097
+ }
5098
+ });
5099
+ };
5100
+
5101
+ helpers.scaleMerge = function(/* objects ... */) {
5102
+ return helpers.merge(helpers.clone(arguments[0]), [].slice.call(arguments, 1), {
5103
+ merger: function(key, target, source, options) {
5104
+ if (key === 'xAxes' || key === 'yAxes') {
5105
+ var slen = source[key].length;
5106
+ var i, type, scale;
5107
+
5108
+ if (!target[key]) {
5109
+ target[key] = [];
5110
+ }
5111
+
5112
+ for (i = 0; i < slen; ++i) {
5113
+ scale = source[key][i];
5114
+ type = helpers.valueOrDefault(scale.type, key === 'xAxes' ? 'category' : 'linear');
5115
+
5116
+ if (i >= target[key].length) {
5117
+ target[key].push({});
5118
+ }
5119
+
5120
+ if (!target[key][i].type || (scale.type && scale.type !== target[key][i].type)) {
5121
+ // new/untyped scale or type changed: let's apply the new defaults
5122
+ // then merge source scale to correctly overwrite the defaults.
5123
+ helpers.merge(target[key][i], [Chart.scaleService.getScaleDefaults(type), scale]);
5124
+ } else {
5125
+ // scales type are the same
5126
+ helpers.merge(target[key][i], scale);
5127
+ }
5128
+ }
5129
+ } else {
5130
+ helpers._merger(key, target, source, options);
5131
+ }
5132
+ }
5133
+ });
5134
+ };
5135
+
5136
+ helpers.where = function(collection, filterCallback) {
5137
+ if (helpers.isArray(collection) && Array.prototype.filter) {
5138
+ return collection.filter(filterCallback);
5139
+ }
5140
+ var filtered = [];
5141
+
5142
+ helpers.each(collection, function(item) {
5143
+ if (filterCallback(item)) {
5144
+ filtered.push(item);
5145
+ }
5146
+ });
5147
+
5148
+ return filtered;
5149
+ };
5150
+ helpers.findIndex = Array.prototype.findIndex ?
5151
+ function(array, callback, scope) {
5152
+ return array.findIndex(callback, scope);
5153
+ } :
5154
+ function(array, callback, scope) {
5155
+ scope = scope === undefined ? array : scope;
5156
+ for (var i = 0, ilen = array.length; i < ilen; ++i) {
5157
+ if (callback.call(scope, array[i], i, array)) {
5158
+ return i;
5159
+ }
5160
+ }
5161
+ return -1;
5162
+ };
5163
+ helpers.findNextWhere = function(arrayToSearch, filterCallback, startIndex) {
5164
+ // Default to start of the array
5165
+ if (helpers.isNullOrUndef(startIndex)) {
5166
+ startIndex = -1;
5167
+ }
5168
+ for (var i = startIndex + 1; i < arrayToSearch.length; i++) {
5169
+ var currentItem = arrayToSearch[i];
5170
+ if (filterCallback(currentItem)) {
5171
+ return currentItem;
5172
+ }
5173
+ }
5174
+ };
5175
+ helpers.findPreviousWhere = function(arrayToSearch, filterCallback, startIndex) {
5176
+ // Default to end of the array
5177
+ if (helpers.isNullOrUndef(startIndex)) {
5178
+ startIndex = arrayToSearch.length;
5179
+ }
5180
+ for (var i = startIndex - 1; i >= 0; i--) {
5181
+ var currentItem = arrayToSearch[i];
5182
+ if (filterCallback(currentItem)) {
5183
+ return currentItem;
5184
+ }
5185
+ }
5186
+ };
5187
+
5188
+ // -- Math methods
5189
+ helpers.isNumber = function(n) {
5190
+ return !isNaN(parseFloat(n)) && isFinite(n);
5191
+ };
5192
+ helpers.almostEquals = function(x, y, epsilon) {
5193
+ return Math.abs(x - y) < epsilon;
5194
+ };
5195
+ helpers.almostWhole = function(x, epsilon) {
5196
+ var rounded = Math.round(x);
5197
+ return (((rounded - epsilon) < x) && ((rounded + epsilon) > x));
5198
+ };
5199
+ helpers.max = function(array) {
5200
+ return array.reduce(function(max, value) {
5201
+ if (!isNaN(value)) {
5202
+ return Math.max(max, value);
5203
+ }
5204
+ return max;
5205
+ }, Number.NEGATIVE_INFINITY);
5206
+ };
5207
+ helpers.min = function(array) {
5208
+ return array.reduce(function(min, value) {
5209
+ if (!isNaN(value)) {
5210
+ return Math.min(min, value);
5211
+ }
5212
+ return min;
5213
+ }, Number.POSITIVE_INFINITY);
5214
+ };
5215
+ helpers.sign = Math.sign ?
5216
+ function(x) {
5217
+ return Math.sign(x);
5218
+ } :
5219
+ function(x) {
5220
+ x = +x; // convert to a number
5221
+ if (x === 0 || isNaN(x)) {
5222
+ return x;
5223
+ }
5224
+ return x > 0 ? 1 : -1;
5225
+ };
5226
+ helpers.log10 = Math.log10 ?
5227
+ function(x) {
5228
+ return Math.log10(x);
5229
+ } :
5230
+ function(x) {
5231
+ return Math.log(x) / Math.LN10;
5232
+ };
5233
+ helpers.toRadians = function(degrees) {
5234
+ return degrees * (Math.PI / 180);
5235
+ };
5236
+ helpers.toDegrees = function(radians) {
5237
+ return radians * (180 / Math.PI);
5238
+ };
5239
+ // Gets the angle from vertical upright to the point about a centre.
5240
+ helpers.getAngleFromPoint = function(centrePoint, anglePoint) {
5241
+ var distanceFromXCenter = anglePoint.x - centrePoint.x;
5242
+ var distanceFromYCenter = anglePoint.y - centrePoint.y;
5243
+ var radialDistanceFromCenter = Math.sqrt(distanceFromXCenter * distanceFromXCenter + distanceFromYCenter * distanceFromYCenter);
5244
+
5245
+ var angle = Math.atan2(distanceFromYCenter, distanceFromXCenter);
5246
+
5247
+ if (angle < (-0.5 * Math.PI)) {
5248
+ angle += 2.0 * Math.PI; // make sure the returned angle is in the range of (-PI/2, 3PI/2]
5249
+ }
5250
+
5251
+ return {
5252
+ angle: angle,
5253
+ distance: radialDistanceFromCenter
5254
+ };
5255
+ };
5256
+ helpers.distanceBetweenPoints = function(pt1, pt2) {
5257
+ return Math.sqrt(Math.pow(pt2.x - pt1.x, 2) + Math.pow(pt2.y - pt1.y, 2));
5258
+ };
5259
+ helpers.aliasPixel = function(pixelWidth) {
5260
+ return (pixelWidth % 2 === 0) ? 0 : 0.5;
5261
+ };
5262
+ helpers.splineCurve = function(firstPoint, middlePoint, afterPoint, t) {
5263
+ // Props to Rob Spencer at scaled innovation for his post on splining between points
5264
+ // http://scaledinnovation.com/analytics/splines/aboutSplines.html
5265
+
5266
+ // This function must also respect "skipped" points
5267
+
5268
+ var previous = firstPoint.skip ? middlePoint : firstPoint;
5269
+ var current = middlePoint;
5270
+ var next = afterPoint.skip ? middlePoint : afterPoint;
5271
+
5272
+ var d01 = Math.sqrt(Math.pow(current.x - previous.x, 2) + Math.pow(current.y - previous.y, 2));
5273
+ var d12 = Math.sqrt(Math.pow(next.x - current.x, 2) + Math.pow(next.y - current.y, 2));
5274
+
5275
+ var s01 = d01 / (d01 + d12);
5276
+ var s12 = d12 / (d01 + d12);
5277
+
5278
+ // If all points are the same, s01 & s02 will be inf
5279
+ s01 = isNaN(s01) ? 0 : s01;
5280
+ s12 = isNaN(s12) ? 0 : s12;
5281
+
5282
+ var fa = t * s01; // scaling factor for triangle Ta
5283
+ var fb = t * s12;
5284
+
5285
+ return {
5286
+ previous: {
5287
+ x: current.x - fa * (next.x - previous.x),
5288
+ y: current.y - fa * (next.y - previous.y)
5289
+ },
5290
+ next: {
5291
+ x: current.x + fb * (next.x - previous.x),
5292
+ y: current.y + fb * (next.y - previous.y)
5293
+ }
5294
+ };
5295
+ };
5296
+ helpers.EPSILON = Number.EPSILON || 1e-14;
5297
+ helpers.splineCurveMonotone = function(points) {
5298
+ // This function calculates Bézier control points in a similar way than |splineCurve|,
5299
+ // but preserves monotonicity of the provided data and ensures no local extremums are added
5300
+ // between the dataset discrete points due to the interpolation.
5301
+ // See : https://en.wikipedia.org/wiki/Monotone_cubic_interpolation
5302
+
5303
+ var pointsWithTangents = (points || []).map(function(point) {
5304
+ return {
5305
+ model: point._model,
5306
+ deltaK: 0,
5307
+ mK: 0
5308
+ };
5309
+ });
5310
+
5311
+ // Calculate slopes (deltaK) and initialize tangents (mK)
5312
+ var pointsLen = pointsWithTangents.length;
5313
+ var i, pointBefore, pointCurrent, pointAfter;
5314
+ for (i = 0; i < pointsLen; ++i) {
5315
+ pointCurrent = pointsWithTangents[i];
5316
+ if (pointCurrent.model.skip) {
5317
+ continue;
5318
+ }
5319
+
5320
+ pointBefore = i > 0 ? pointsWithTangents[i - 1] : null;
5321
+ pointAfter = i < pointsLen - 1 ? pointsWithTangents[i + 1] : null;
5322
+ if (pointAfter && !pointAfter.model.skip) {
5323
+ var slopeDeltaX = (pointAfter.model.x - pointCurrent.model.x);
5324
+
5325
+ // In the case of two points that appear at the same x pixel, slopeDeltaX is 0
5326
+ pointCurrent.deltaK = slopeDeltaX !== 0 ? (pointAfter.model.y - pointCurrent.model.y) / slopeDeltaX : 0;
5327
+ }
5328
+
5329
+ if (!pointBefore || pointBefore.model.skip) {
5330
+ pointCurrent.mK = pointCurrent.deltaK;
5331
+ } else if (!pointAfter || pointAfter.model.skip) {
5332
+ pointCurrent.mK = pointBefore.deltaK;
5333
+ } else if (this.sign(pointBefore.deltaK) !== this.sign(pointCurrent.deltaK)) {
5334
+ pointCurrent.mK = 0;
5335
+ } else {
5336
+ pointCurrent.mK = (pointBefore.deltaK + pointCurrent.deltaK) / 2;
5337
+ }
5338
+ }
5339
+
5340
+ // Adjust tangents to ensure monotonic properties
5341
+ var alphaK, betaK, tauK, squaredMagnitude;
5342
+ for (i = 0; i < pointsLen - 1; ++i) {
5343
+ pointCurrent = pointsWithTangents[i];
5344
+ pointAfter = pointsWithTangents[i + 1];
5345
+ if (pointCurrent.model.skip || pointAfter.model.skip) {
5346
+ continue;
5347
+ }
5348
+
5349
+ if (helpers.almostEquals(pointCurrent.deltaK, 0, this.EPSILON)) {
5350
+ pointCurrent.mK = pointAfter.mK = 0;
5351
+ continue;
5352
+ }
5353
+
5354
+ alphaK = pointCurrent.mK / pointCurrent.deltaK;
5355
+ betaK = pointAfter.mK / pointCurrent.deltaK;
5356
+ squaredMagnitude = Math.pow(alphaK, 2) + Math.pow(betaK, 2);
5357
+ if (squaredMagnitude <= 9) {
5358
+ continue;
5359
+ }
5360
+
5361
+ tauK = 3 / Math.sqrt(squaredMagnitude);
5362
+ pointCurrent.mK = alphaK * tauK * pointCurrent.deltaK;
5363
+ pointAfter.mK = betaK * tauK * pointCurrent.deltaK;
5364
+ }
5365
+
5366
+ // Compute control points
5367
+ var deltaX;
5368
+ for (i = 0; i < pointsLen; ++i) {
5369
+ pointCurrent = pointsWithTangents[i];
5370
+ if (pointCurrent.model.skip) {
5371
+ continue;
5372
+ }
5373
+
5374
+ pointBefore = i > 0 ? pointsWithTangents[i - 1] : null;
5375
+ pointAfter = i < pointsLen - 1 ? pointsWithTangents[i + 1] : null;
5376
+ if (pointBefore && !pointBefore.model.skip) {
5377
+ deltaX = (pointCurrent.model.x - pointBefore.model.x) / 3;
5378
+ pointCurrent.model.controlPointPreviousX = pointCurrent.model.x - deltaX;
5379
+ pointCurrent.model.controlPointPreviousY = pointCurrent.model.y - deltaX * pointCurrent.mK;
5380
+ }
5381
+ if (pointAfter && !pointAfter.model.skip) {
5382
+ deltaX = (pointAfter.model.x - pointCurrent.model.x) / 3;
5383
+ pointCurrent.model.controlPointNextX = pointCurrent.model.x + deltaX;
5384
+ pointCurrent.model.controlPointNextY = pointCurrent.model.y + deltaX * pointCurrent.mK;
5385
+ }
5386
+ }
5387
+ };
5388
+ helpers.nextItem = function(collection, index, loop) {
5389
+ if (loop) {
5390
+ return index >= collection.length - 1 ? collection[0] : collection[index + 1];
5391
+ }
5392
+ return index >= collection.length - 1 ? collection[collection.length - 1] : collection[index + 1];
5393
+ };
5394
+ helpers.previousItem = function(collection, index, loop) {
5395
+ if (loop) {
5396
+ return index <= 0 ? collection[collection.length - 1] : collection[index - 1];
5397
+ }
5398
+ return index <= 0 ? collection[0] : collection[index - 1];
5399
+ };
5400
+ // Implementation of the nice number algorithm used in determining where axis labels will go
5401
+ helpers.niceNum = function(range, round) {
5402
+ var exponent = Math.floor(helpers.log10(range));
5403
+ var fraction = range / Math.pow(10, exponent);
5404
+ var niceFraction;
5405
+
5406
+ if (round) {
5407
+ if (fraction < 1.5) {
5408
+ niceFraction = 1;
5409
+ } else if (fraction < 3) {
5410
+ niceFraction = 2;
5411
+ } else if (fraction < 7) {
5412
+ niceFraction = 5;
5413
+ } else {
5414
+ niceFraction = 10;
5415
+ }
5416
+ } else if (fraction <= 1.0) {
5417
+ niceFraction = 1;
5418
+ } else if (fraction <= 2) {
5419
+ niceFraction = 2;
5420
+ } else if (fraction <= 5) {
5421
+ niceFraction = 5;
5422
+ } else {
5423
+ niceFraction = 10;
5424
+ }
5425
+
5426
+ return niceFraction * Math.pow(10, exponent);
5427
+ };
5428
+ // Request animation polyfill - http://www.paulirish.com/2011/requestanimationframe-for-smart-animating/
5429
+ helpers.requestAnimFrame = (function() {
5430
+ if (typeof window === 'undefined') {
5431
+ return function(callback) {
5432
+ callback();
5433
+ };
5434
+ }
5435
+ return window.requestAnimationFrame ||
5436
+ window.webkitRequestAnimationFrame ||
5437
+ window.mozRequestAnimationFrame ||
5438
+ window.oRequestAnimationFrame ||
5439
+ window.msRequestAnimationFrame ||
5440
+ function(callback) {
5441
+ return window.setTimeout(callback, 1000 / 60);
5442
+ };
5443
+ }());
5444
+ // -- DOM methods
5445
+ helpers.getRelativePosition = function(evt, chart) {
5446
+ var mouseX, mouseY;
5447
+ var e = evt.originalEvent || evt;
5448
+ var canvas = evt.currentTarget || evt.srcElement;
5449
+ var boundingRect = canvas.getBoundingClientRect();
5450
+
5451
+ var touches = e.touches;
5452
+ if (touches && touches.length > 0) {
5453
+ mouseX = touches[0].clientX;
5454
+ mouseY = touches[0].clientY;
5455
+
5456
+ } else {
5457
+ mouseX = e.clientX;
5458
+ mouseY = e.clientY;
5459
+ }
5460
+
5461
+ // Scale mouse coordinates into canvas coordinates
5462
+ // by following the pattern laid out by 'jerryj' in the comments of
5463
+ // http://www.html5canvastutorials.com/advanced/html5-canvas-mouse-coordinates/
5464
+ var paddingLeft = parseFloat(helpers.getStyle(canvas, 'padding-left'));
5465
+ var paddingTop = parseFloat(helpers.getStyle(canvas, 'padding-top'));
5466
+ var paddingRight = parseFloat(helpers.getStyle(canvas, 'padding-right'));
5467
+ var paddingBottom = parseFloat(helpers.getStyle(canvas, 'padding-bottom'));
5468
+ var width = boundingRect.right - boundingRect.left - paddingLeft - paddingRight;
5469
+ var height = boundingRect.bottom - boundingRect.top - paddingTop - paddingBottom;
5470
+
5471
+ // We divide by the current device pixel ratio, because the canvas is scaled up by that amount in each direction. However
5472
+ // the backend model is in unscaled coordinates. Since we are going to deal with our model coordinates, we go back here
5473
+ mouseX = Math.round((mouseX - boundingRect.left - paddingLeft) / (width) * canvas.width / chart.currentDevicePixelRatio);
5474
+ mouseY = Math.round((mouseY - boundingRect.top - paddingTop) / (height) * canvas.height / chart.currentDevicePixelRatio);
5475
+
5476
+ return {
5477
+ x: mouseX,
5478
+ y: mouseY
5479
+ };
5480
+
5481
+ };
5482
+
5483
+ // Private helper function to convert max-width/max-height values that may be percentages into a number
5484
+ function parseMaxStyle(styleValue, node, parentProperty) {
5485
+ var valueInPixels;
5486
+ if (typeof styleValue === 'string') {
5487
+ valueInPixels = parseInt(styleValue, 10);
5488
+
5489
+ if (styleValue.indexOf('%') !== -1) {
5490
+ // percentage * size in dimension
5491
+ valueInPixels = valueInPixels / 100 * node.parentNode[parentProperty];
5492
+ }
5493
+ } else {
5494
+ valueInPixels = styleValue;
5495
+ }
5496
+
5497
+ return valueInPixels;
5498
+ }
5499
+
5500
+ /**
5501
+ * Returns if the given value contains an effective constraint.
5502
+ * @private
5503
+ */
5504
+ function isConstrainedValue(value) {
5505
+ return value !== undefined && value !== null && value !== 'none';
5506
+ }
5507
+
5508
+ // Private helper to get a constraint dimension
5509
+ // @param domNode : the node to check the constraint on
5510
+ // @param maxStyle : the style that defines the maximum for the direction we are using (maxWidth / maxHeight)
5511
+ // @param percentageProperty : property of parent to use when calculating width as a percentage
5512
+ // @see http://www.nathanaeljones.com/blog/2013/reading-max-width-cross-browser
5513
+ function getConstraintDimension(domNode, maxStyle, percentageProperty) {
5514
+ var view = document.defaultView;
5515
+ var parentNode = domNode.parentNode;
5516
+ var constrainedNode = view.getComputedStyle(domNode)[maxStyle];
5517
+ var constrainedContainer = view.getComputedStyle(parentNode)[maxStyle];
5518
+ var hasCNode = isConstrainedValue(constrainedNode);
5519
+ var hasCContainer = isConstrainedValue(constrainedContainer);
5520
+ var infinity = Number.POSITIVE_INFINITY;
5521
+
5522
+ if (hasCNode || hasCContainer) {
5523
+ return Math.min(
5524
+ hasCNode ? parseMaxStyle(constrainedNode, domNode, percentageProperty) : infinity,
5525
+ hasCContainer ? parseMaxStyle(constrainedContainer, parentNode, percentageProperty) : infinity);
5526
+ }
5527
+
5528
+ return 'none';
5529
+ }
5530
+ // returns Number or undefined if no constraint
5531
+ helpers.getConstraintWidth = function(domNode) {
5532
+ return getConstraintDimension(domNode, 'max-width', 'clientWidth');
5533
+ };
5534
+ // returns Number or undefined if no constraint
5535
+ helpers.getConstraintHeight = function(domNode) {
5536
+ return getConstraintDimension(domNode, 'max-height', 'clientHeight');
5537
+ };
5538
+ helpers.getMaximumWidth = function(domNode) {
5539
+ var container = domNode.parentNode;
5540
+ if (!container) {
5541
+ return domNode.clientWidth;
5542
+ }
5543
+
5544
+ var paddingLeft = parseInt(helpers.getStyle(container, 'padding-left'), 10);
5545
+ var paddingRight = parseInt(helpers.getStyle(container, 'padding-right'), 10);
5546
+ var w = container.clientWidth - paddingLeft - paddingRight;
5547
+ var cw = helpers.getConstraintWidth(domNode);
5548
+ return isNaN(cw) ? w : Math.min(w, cw);
5549
+ };
5550
+ helpers.getMaximumHeight = function(domNode) {
5551
+ var container = domNode.parentNode;
5552
+ if (!container) {
5553
+ return domNode.clientHeight;
5554
+ }
5555
+
5556
+ var paddingTop = parseInt(helpers.getStyle(container, 'padding-top'), 10);
5557
+ var paddingBottom = parseInt(helpers.getStyle(container, 'padding-bottom'), 10);
5558
+ var h = container.clientHeight - paddingTop - paddingBottom;
5559
+ var ch = helpers.getConstraintHeight(domNode);
5560
+ return isNaN(ch) ? h : Math.min(h, ch);
5561
+ };
5562
+ helpers.getStyle = function(el, property) {
5563
+ return el.currentStyle ?
5564
+ el.currentStyle[property] :
5565
+ document.defaultView.getComputedStyle(el, null).getPropertyValue(property);
5566
+ };
5567
+ helpers.retinaScale = function(chart, forceRatio) {
5568
+ var pixelRatio = chart.currentDevicePixelRatio = forceRatio || window.devicePixelRatio || 1;
5569
+ if (pixelRatio === 1) {
5570
+ return;
5571
+ }
5572
+
5573
+ var canvas = chart.canvas;
5574
+ var height = chart.height;
5575
+ var width = chart.width;
5576
+
5577
+ canvas.height = height * pixelRatio;
5578
+ canvas.width = width * pixelRatio;
5579
+ chart.ctx.scale(pixelRatio, pixelRatio);
5580
+
5581
+ // If no style has been set on the canvas, the render size is used as display size,
5582
+ // making the chart visually bigger, so let's enforce it to the "correct" values.
5583
+ // See https://github.com/chartjs/Chart.js/issues/3575
5584
+ canvas.style.height = height + 'px';
5585
+ canvas.style.width = width + 'px';
5586
+ };
5587
+ // -- Canvas methods
5588
+ helpers.fontString = function(pixelSize, fontStyle, fontFamily) {
5589
+ return fontStyle + ' ' + pixelSize + 'px ' + fontFamily;
5590
+ };
5591
+ helpers.longestText = function(ctx, font, arrayOfThings, cache) {
5592
+ cache = cache || {};
5593
+ var data = cache.data = cache.data || {};
5594
+ var gc = cache.garbageCollect = cache.garbageCollect || [];
5595
+
5596
+ if (cache.font !== font) {
5597
+ data = cache.data = {};
5598
+ gc = cache.garbageCollect = [];
5599
+ cache.font = font;
5600
+ }
5601
+
5602
+ ctx.font = font;
5603
+ var longest = 0;
5604
+ helpers.each(arrayOfThings, function(thing) {
5605
+ // Undefined strings and arrays should not be measured
5606
+ if (thing !== undefined && thing !== null && helpers.isArray(thing) !== true) {
5607
+ longest = helpers.measureText(ctx, data, gc, longest, thing);
5608
+ } else if (helpers.isArray(thing)) {
5609
+ // if it is an array lets measure each element
5610
+ // to do maybe simplify this function a bit so we can do this more recursively?
5611
+ helpers.each(thing, function(nestedThing) {
5612
+ // Undefined strings and arrays should not be measured
5613
+ if (nestedThing !== undefined && nestedThing !== null && !helpers.isArray(nestedThing)) {
5614
+ longest = helpers.measureText(ctx, data, gc, longest, nestedThing);
5615
+ }
5616
+ });
5617
+ }
5618
+ });
5619
+
5620
+ var gcLen = gc.length / 2;
5621
+ if (gcLen > arrayOfThings.length) {
5622
+ for (var i = 0; i < gcLen; i++) {
5623
+ delete data[gc[i]];
5624
+ }
5625
+ gc.splice(0, gcLen);
5626
+ }
5627
+ return longest;
5628
+ };
5629
+ helpers.measureText = function(ctx, data, gc, longest, string) {
5630
+ var textWidth = data[string];
5631
+ if (!textWidth) {
5632
+ textWidth = data[string] = ctx.measureText(string).width;
5633
+ gc.push(string);
5634
+ }
5635
+ if (textWidth > longest) {
5636
+ longest = textWidth;
5637
+ }
5638
+ return longest;
5639
+ };
5640
+ helpers.numberOfLabelLines = function(arrayOfThings) {
5641
+ var numberOfLines = 1;
5642
+ helpers.each(arrayOfThings, function(thing) {
5643
+ if (helpers.isArray(thing)) {
5644
+ if (thing.length > numberOfLines) {
5645
+ numberOfLines = thing.length;
5646
+ }
5647
+ }
5648
+ });
5649
+ return numberOfLines;
5650
+ };
5651
+
5652
+ helpers.color = !color ?
5653
+ function(value) {
5654
+ console.error('Color.js not found!');
5655
+ return value;
5656
+ } :
5657
+ function(value) {
5658
+ /* global CanvasGradient */
5659
+ if (value instanceof CanvasGradient) {
5660
+ value = defaults.global.defaultColor;
5661
+ }
5662
+
5663
+ return color(value);
5664
+ };
5665
+
5666
+ helpers.getHoverColor = function(colorValue) {
5667
+ /* global CanvasPattern */
5668
+ return (colorValue instanceof CanvasPattern) ?
5669
+ colorValue :
5670
+ helpers.color(colorValue).saturate(0.5).darken(0.1).rgbString();
5671
+ };
5672
+ };
5673
+
5674
+ },{"25":25,"3":3,"45":45}],28:[function(require,module,exports){
5675
+ 'use strict';
5676
+
5677
+ var helpers = require(45);
5678
+
5679
+ /**
5680
+ * Helper function to get relative position for an event
5681
+ * @param {Event|IEvent} event - The event to get the position for
5682
+ * @param {Chart} chart - The chart
5683
+ * @returns {Point} the event position
5684
+ */
5685
+ function getRelativePosition(e, chart) {
5686
+ if (e.native) {
5687
+ return {
5688
+ x: e.x,
5689
+ y: e.y
5690
+ };
5691
+ }
5692
+
5693
+ return helpers.getRelativePosition(e, chart);
5694
+ }
5695
+
5696
+ /**
5697
+ * Helper function to traverse all of the visible elements in the chart
5698
+ * @param chart {chart} the chart
5699
+ * @param handler {Function} the callback to execute for each visible item
5700
+ */
5701
+ function parseVisibleItems(chart, handler) {
5702
+ var datasets = chart.data.datasets;
5703
+ var meta, i, j, ilen, jlen;
5704
+
5705
+ for (i = 0, ilen = datasets.length; i < ilen; ++i) {
5706
+ if (!chart.isDatasetVisible(i)) {
5707
+ continue;
5708
+ }
5709
+
5710
+ meta = chart.getDatasetMeta(i);
5711
+ for (j = 0, jlen = meta.data.length; j < jlen; ++j) {
5712
+ var element = meta.data[j];
5713
+ if (!element._view.skip) {
5714
+ handler(element);
5715
+ }
5716
+ }
5717
+ }
5718
+ }
5719
+
5720
+ /**
5721
+ * Helper function to get the items that intersect the event position
5722
+ * @param items {ChartElement[]} elements to filter
5723
+ * @param position {Point} the point to be nearest to
5724
+ * @return {ChartElement[]} the nearest items
5725
+ */
5726
+ function getIntersectItems(chart, position) {
5727
+ var elements = [];
5728
+
5729
+ parseVisibleItems(chart, function(element) {
5730
+ if (element.inRange(position.x, position.y)) {
5731
+ elements.push(element);
5732
+ }
5733
+ });
5734
+
5735
+ return elements;
5736
+ }
5737
+
5738
+ /**
5739
+ * Helper function to get the items nearest to the event position considering all visible items in teh chart
5740
+ * @param chart {Chart} the chart to look at elements from
5741
+ * @param position {Point} the point to be nearest to
5742
+ * @param intersect {Boolean} if true, only consider items that intersect the position
5743
+ * @param distanceMetric {Function} function to provide the distance between points
5744
+ * @return {ChartElement[]} the nearest items
5745
+ */
5746
+ function getNearestItems(chart, position, intersect, distanceMetric) {
5747
+ var minDistance = Number.POSITIVE_INFINITY;
5748
+ var nearestItems = [];
5749
+
5750
+ parseVisibleItems(chart, function(element) {
5751
+ if (intersect && !element.inRange(position.x, position.y)) {
5752
+ return;
5753
+ }
5754
+
5755
+ var center = element.getCenterPoint();
5756
+ var distance = distanceMetric(position, center);
5757
+
5758
+ if (distance < minDistance) {
5759
+ nearestItems = [element];
5760
+ minDistance = distance;
5761
+ } else if (distance === minDistance) {
5762
+ // Can have multiple items at the same distance in which case we sort by size
5763
+ nearestItems.push(element);
5764
+ }
5765
+ });
5766
+
5767
+ return nearestItems;
5768
+ }
5769
+
5770
+ /**
5771
+ * Get a distance metric function for two points based on the
5772
+ * axis mode setting
5773
+ * @param {String} axis the axis mode. x|y|xy
5774
+ */
5775
+ function getDistanceMetricForAxis(axis) {
5776
+ var useX = axis.indexOf('x') !== -1;
5777
+ var useY = axis.indexOf('y') !== -1;
5778
+
5779
+ return function(pt1, pt2) {
5780
+ var deltaX = useX ? Math.abs(pt1.x - pt2.x) : 0;
5781
+ var deltaY = useY ? Math.abs(pt1.y - pt2.y) : 0;
5782
+ return Math.sqrt(Math.pow(deltaX, 2) + Math.pow(deltaY, 2));
5783
+ };
5784
+ }
5785
+
5786
+ function indexMode(chart, e, options) {
5787
+ var position = getRelativePosition(e, chart);
5788
+ // Default axis for index mode is 'x' to match old behaviour
5789
+ options.axis = options.axis || 'x';
5790
+ var distanceMetric = getDistanceMetricForAxis(options.axis);
5791
+ var items = options.intersect ? getIntersectItems(chart, position) : getNearestItems(chart, position, false, distanceMetric);
5792
+ var elements = [];
5793
+
5794
+ if (!items.length) {
5795
+ return [];
5796
+ }
5797
+
5798
+ chart.data.datasets.forEach(function(dataset, datasetIndex) {
5799
+ if (chart.isDatasetVisible(datasetIndex)) {
5800
+ var meta = chart.getDatasetMeta(datasetIndex);
5801
+ var element = meta.data[items[0]._index];
5802
+
5803
+ // don't count items that are skipped (null data)
5804
+ if (element && !element._view.skip) {
5805
+ elements.push(element);
5806
+ }
5807
+ }
5808
+ });
5809
+
5810
+ return elements;
5811
+ }
5812
+
5813
+ /**
5814
+ * @interface IInteractionOptions
5815
+ */
5816
+ /**
5817
+ * If true, only consider items that intersect the point
5818
+ * @name IInterfaceOptions#boolean
5819
+ * @type Boolean
5820
+ */
5821
+
5822
+ /**
5823
+ * Contains interaction related functions
5824
+ * @namespace Chart.Interaction
5825
+ */
5826
+ module.exports = {
5827
+ // Helper function for different modes
5828
+ modes: {
5829
+ single: function(chart, e) {
5830
+ var position = getRelativePosition(e, chart);
5831
+ var elements = [];
5832
+
5833
+ parseVisibleItems(chart, function(element) {
5834
+ if (element.inRange(position.x, position.y)) {
5835
+ elements.push(element);
5836
+ return elements;
5837
+ }
5838
+ });
5839
+
5840
+ return elements.slice(0, 1);
5841
+ },
5842
+
5843
+ /**
5844
+ * @function Chart.Interaction.modes.label
5845
+ * @deprecated since version 2.4.0
5846
+ * @todo remove at version 3
5847
+ * @private
5848
+ */
5849
+ label: indexMode,
5850
+
5851
+ /**
5852
+ * Returns items at the same index. If the options.intersect parameter is true, we only return items if we intersect something
5853
+ * If the options.intersect mode is false, we find the nearest item and return the items at the same index as that item
5854
+ * @function Chart.Interaction.modes.index
5855
+ * @since v2.4.0
5856
+ * @param chart {chart} the chart we are returning items from
5857
+ * @param e {Event} the event we are find things at
5858
+ * @param options {IInteractionOptions} options to use during interaction
5859
+ * @return {Chart.Element[]} Array of elements that are under the point. If none are found, an empty array is returned
5860
+ */
5861
+ index: indexMode,
5862
+
5863
+ /**
5864
+ * Returns items in the same dataset. If the options.intersect parameter is true, we only return items if we intersect something
5865
+ * If the options.intersect is false, we find the nearest item and return the items in that dataset
5866
+ * @function Chart.Interaction.modes.dataset
5867
+ * @param chart {chart} the chart we are returning items from
5868
+ * @param e {Event} the event we are find things at
5869
+ * @param options {IInteractionOptions} options to use during interaction
5870
+ * @return {Chart.Element[]} Array of elements that are under the point. If none are found, an empty array is returned
5871
+ */
5872
+ dataset: function(chart, e, options) {
5873
+ var position = getRelativePosition(e, chart);
5874
+ options.axis = options.axis || 'xy';
5875
+ var distanceMetric = getDistanceMetricForAxis(options.axis);
5876
+ var items = options.intersect ? getIntersectItems(chart, position) : getNearestItems(chart, position, false, distanceMetric);
5877
+
5878
+ if (items.length > 0) {
5879
+ items = chart.getDatasetMeta(items[0]._datasetIndex).data;
5880
+ }
5881
+
5882
+ return items;
5883
+ },
5884
+
5885
+ /**
5886
+ * @function Chart.Interaction.modes.x-axis
5887
+ * @deprecated since version 2.4.0. Use index mode and intersect == true
5888
+ * @todo remove at version 3
5889
+ * @private
5890
+ */
5891
+ 'x-axis': function(chart, e) {
5892
+ return indexMode(chart, e, {intersect: false});
5893
+ },
5894
+
5895
+ /**
5896
+ * Point mode returns all elements that hit test based on the event position
5897
+ * of the event
5898
+ * @function Chart.Interaction.modes.intersect
5899
+ * @param chart {chart} the chart we are returning items from
5900
+ * @param e {Event} the event we are find things at
5901
+ * @return {Chart.Element[]} Array of elements that are under the point. If none are found, an empty array is returned
5902
+ */
5903
+ point: function(chart, e) {
5904
+ var position = getRelativePosition(e, chart);
5905
+ return getIntersectItems(chart, position);
5906
+ },
5907
+
5908
+ /**
5909
+ * nearest mode returns the element closest to the point
5910
+ * @function Chart.Interaction.modes.intersect
5911
+ * @param chart {chart} the chart we are returning items from
5912
+ * @param e {Event} the event we are find things at
5913
+ * @param options {IInteractionOptions} options to use
5914
+ * @return {Chart.Element[]} Array of elements that are under the point. If none are found, an empty array is returned
5915
+ */
5916
+ nearest: function(chart, e, options) {
5917
+ var position = getRelativePosition(e, chart);
5918
+ options.axis = options.axis || 'xy';
5919
+ var distanceMetric = getDistanceMetricForAxis(options.axis);
5920
+ var nearestItems = getNearestItems(chart, position, options.intersect, distanceMetric);
5921
+
5922
+ // We have multiple items at the same distance from the event. Now sort by smallest
5923
+ if (nearestItems.length > 1) {
5924
+ nearestItems.sort(function(a, b) {
5925
+ var sizeA = a.getArea();
5926
+ var sizeB = b.getArea();
5927
+ var ret = sizeA - sizeB;
5928
+
5929
+ if (ret === 0) {
5930
+ // if equal sort by dataset index
5931
+ ret = a._datasetIndex - b._datasetIndex;
5932
+ }
5933
+
5934
+ return ret;
5935
+ });
5936
+ }
5937
+
5938
+ // Return only 1 item
5939
+ return nearestItems.slice(0, 1);
5940
+ },
5941
+
5942
+ /**
5943
+ * x mode returns the elements that hit-test at the current x coordinate
5944
+ * @function Chart.Interaction.modes.x
5945
+ * @param chart {chart} the chart we are returning items from
5946
+ * @param e {Event} the event we are find things at
5947
+ * @param options {IInteractionOptions} options to use
5948
+ * @return {Chart.Element[]} Array of elements that are under the point. If none are found, an empty array is returned
5949
+ */
5950
+ x: function(chart, e, options) {
5951
+ var position = getRelativePosition(e, chart);
5952
+ var items = [];
5953
+ var intersectsItem = false;
5954
+
5955
+ parseVisibleItems(chart, function(element) {
5956
+ if (element.inXRange(position.x)) {
5957
+ items.push(element);
5958
+ }
5959
+
5960
+ if (element.inRange(position.x, position.y)) {
5961
+ intersectsItem = true;
5962
+ }
5963
+ });
5964
+
5965
+ // If we want to trigger on an intersect and we don't have any items
5966
+ // that intersect the position, return nothing
5967
+ if (options.intersect && !intersectsItem) {
5968
+ items = [];
5969
+ }
5970
+ return items;
5971
+ },
5972
+
5973
+ /**
5974
+ * y mode returns the elements that hit-test at the current y coordinate
5975
+ * @function Chart.Interaction.modes.y
5976
+ * @param chart {chart} the chart we are returning items from
5977
+ * @param e {Event} the event we are find things at
5978
+ * @param options {IInteractionOptions} options to use
5979
+ * @return {Chart.Element[]} Array of elements that are under the point. If none are found, an empty array is returned
5980
+ */
5981
+ y: function(chart, e, options) {
5982
+ var position = getRelativePosition(e, chart);
5983
+ var items = [];
5984
+ var intersectsItem = false;
5985
+
5986
+ parseVisibleItems(chart, function(element) {
5987
+ if (element.inYRange(position.y)) {
5988
+ items.push(element);
5989
+ }
5990
+
5991
+ if (element.inRange(position.x, position.y)) {
5992
+ intersectsItem = true;
5993
+ }
5994
+ });
5995
+
5996
+ // If we want to trigger on an intersect and we don't have any items
5997
+ // that intersect the position, return nothing
5998
+ if (options.intersect && !intersectsItem) {
5999
+ items = [];
6000
+ }
6001
+ return items;
6002
+ }
6003
+ }
6004
+ };
6005
+
6006
+ },{"45":45}],29:[function(require,module,exports){
6007
+ 'use strict';
6008
+
6009
+ var defaults = require(25);
6010
+
6011
+ defaults._set('global', {
6012
+ responsive: true,
6013
+ responsiveAnimationDuration: 0,
6014
+ maintainAspectRatio: true,
6015
+ events: ['mousemove', 'mouseout', 'click', 'touchstart', 'touchmove'],
6016
+ hover: {
6017
+ onHover: null,
6018
+ mode: 'nearest',
6019
+ intersect: true,
6020
+ animationDuration: 400
6021
+ },
6022
+ onClick: null,
6023
+ defaultColor: 'rgba(0,0,0,0.1)',
6024
+ defaultFontColor: '#666',
6025
+ defaultFontFamily: "'Helvetica Neue', 'Helvetica', 'Arial', sans-serif",
6026
+ defaultFontSize: 12,
6027
+ defaultFontStyle: 'normal',
6028
+ showLines: true,
6029
+
6030
+ // Element defaults defined in element extensions
6031
+ elements: {},
6032
+
6033
+ // Layout options such as padding
6034
+ layout: {
6035
+ padding: {
6036
+ top: 0,
6037
+ right: 0,
6038
+ bottom: 0,
6039
+ left: 0
6040
+ }
6041
+ }
6042
+ });
6043
+
6044
+ module.exports = function() {
6045
+
6046
+ // Occupy the global variable of Chart, and create a simple base class
6047
+ var Chart = function(item, config) {
6048
+ this.construct(item, config);
6049
+ return this;
6050
+ };
6051
+
6052
+ Chart.Chart = Chart;
6053
+
6054
+ return Chart;
6055
+ };
6056
+
6057
+ },{"25":25}],30:[function(require,module,exports){
6058
+ 'use strict';
6059
+
6060
+ var helpers = require(45);
6061
+
6062
+ module.exports = function(Chart) {
6063
+
6064
+ function filterByPosition(array, position) {
6065
+ return helpers.where(array, function(v) {
6066
+ return v.position === position;
6067
+ });
6068
+ }
6069
+
6070
+ function sortByWeight(array, reverse) {
6071
+ array.forEach(function(v, i) {
6072
+ v._tmpIndex_ = i;
6073
+ return v;
6074
+ });
6075
+ array.sort(function(a, b) {
6076
+ var v0 = reverse ? b : a;
6077
+ var v1 = reverse ? a : b;
6078
+ return v0.weight === v1.weight ?
6079
+ v0._tmpIndex_ - v1._tmpIndex_ :
6080
+ v0.weight - v1.weight;
6081
+ });
6082
+ array.forEach(function(v) {
6083
+ delete v._tmpIndex_;
6084
+ });
6085
+ }
6086
+
6087
+ /**
6088
+ * @interface ILayoutItem
6089
+ * @prop {String} position - The position of the item in the chart layout. Possible values are
6090
+ * 'left', 'top', 'right', 'bottom', and 'chartArea'
6091
+ * @prop {Number} weight - The weight used to sort the item. Higher weights are further away from the chart area
6092
+ * @prop {Boolean} fullWidth - if true, and the item is horizontal, then push vertical boxes down
6093
+ * @prop {Function} isHorizontal - returns true if the layout item is horizontal (ie. top or bottom)
6094
+ * @prop {Function} update - Takes two parameters: width and height. Returns size of item
6095
+ * @prop {Function} getPadding - Returns an object with padding on the edges
6096
+ * @prop {Number} width - Width of item. Must be valid after update()
6097
+ * @prop {Number} height - Height of item. Must be valid after update()
6098
+ * @prop {Number} left - Left edge of the item. Set by layout system and cannot be used in update
6099
+ * @prop {Number} top - Top edge of the item. Set by layout system and cannot be used in update
6100
+ * @prop {Number} right - Right edge of the item. Set by layout system and cannot be used in update
6101
+ * @prop {Number} bottom - Bottom edge of the item. Set by layout system and cannot be used in update
6102
+ */
6103
+
6104
+ // The layout service is very self explanatory. It's responsible for the layout within a chart.
6105
+ // Scales, Legends and Plugins all rely on the layout service and can easily register to be placed anywhere they need
6106
+ // It is this service's responsibility of carrying out that layout.
6107
+ Chart.layoutService = {
6108
+ defaults: {},
6109
+
6110
+ /**
6111
+ * Register a box to a chart.
6112
+ * A box is simply a reference to an object that requires layout. eg. Scales, Legend, Title.
6113
+ * @param {Chart} chart - the chart to use
6114
+ * @param {ILayoutItem} item - the item to add to be layed out
6115
+ */
6116
+ addBox: function(chart, item) {
6117
+ if (!chart.boxes) {
6118
+ chart.boxes = [];
6119
+ }
6120
+
6121
+ // initialize item with default values
6122
+ item.fullWidth = item.fullWidth || false;
6123
+ item.position = item.position || 'top';
6124
+ item.weight = item.weight || 0;
6125
+
6126
+ chart.boxes.push(item);
6127
+ },
6128
+
6129
+ /**
6130
+ * Remove a layoutItem from a chart
6131
+ * @param {Chart} chart - the chart to remove the box from
6132
+ * @param {Object} layoutItem - the item to remove from the layout
6133
+ */
6134
+ removeBox: function(chart, layoutItem) {
6135
+ var index = chart.boxes ? chart.boxes.indexOf(layoutItem) : -1;
6136
+ if (index !== -1) {
6137
+ chart.boxes.splice(index, 1);
6138
+ }
6139
+ },
6140
+
6141
+ /**
6142
+ * Sets (or updates) options on the given `item`.
6143
+ * @param {Chart} chart - the chart in which the item lives (or will be added to)
6144
+ * @param {Object} item - the item to configure with the given options
6145
+ * @param {Object} options - the new item options.
6146
+ */
6147
+ configure: function(chart, item, options) {
6148
+ var props = ['fullWidth', 'position', 'weight'];
6149
+ var ilen = props.length;
6150
+ var i = 0;
6151
+ var prop;
6152
+
6153
+ for (; i < ilen; ++i) {
6154
+ prop = props[i];
6155
+ if (options.hasOwnProperty(prop)) {
6156
+ item[prop] = options[prop];
6157
+ }
6158
+ }
6159
+ },
6160
+
6161
+ /**
6162
+ * Fits boxes of the given chart into the given size by having each box measure itself
6163
+ * then running a fitting algorithm
6164
+ * @param {Chart} chart - the chart
6165
+ * @param {Number} width - the width to fit into
6166
+ * @param {Number} height - the height to fit into
6167
+ */
6168
+ update: function(chart, width, height) {
6169
+ if (!chart) {
6170
+ return;
6171
+ }
6172
+
6173
+ var layoutOptions = chart.options.layout || {};
6174
+ var padding = helpers.options.toPadding(layoutOptions.padding);
6175
+ var leftPadding = padding.left;
6176
+ var rightPadding = padding.right;
6177
+ var topPadding = padding.top;
6178
+ var bottomPadding = padding.bottom;
6179
+
6180
+ var leftBoxes = filterByPosition(chart.boxes, 'left');
6181
+ var rightBoxes = filterByPosition(chart.boxes, 'right');
6182
+ var topBoxes = filterByPosition(chart.boxes, 'top');
6183
+ var bottomBoxes = filterByPosition(chart.boxes, 'bottom');
6184
+ var chartAreaBoxes = filterByPosition(chart.boxes, 'chartArea');
6185
+
6186
+ // Sort boxes by weight. A higher weight is further away from the chart area
6187
+ sortByWeight(leftBoxes, true);
6188
+ sortByWeight(rightBoxes, false);
6189
+ sortByWeight(topBoxes, true);
6190
+ sortByWeight(bottomBoxes, false);
6191
+
6192
+ // Essentially we now have any number of boxes on each of the 4 sides.
6193
+ // Our canvas looks like the following.
6194
+ // The areas L1 and L2 are the left axes. R1 is the right axis, T1 is the top axis and
6195
+ // B1 is the bottom axis
6196
+ // There are also 4 quadrant-like locations (left to right instead of clockwise) reserved for chart overlays
6197
+ // These locations are single-box locations only, when trying to register a chartArea location that is already taken,
6198
+ // an error will be thrown.
6199
+ //
6200
+ // |----------------------------------------------------|
6201
+ // | T1 (Full Width) |
6202
+ // |----------------------------------------------------|
6203
+ // | | | T2 | |
6204
+ // | |----|-------------------------------------|----|
6205
+ // | | | C1 | | C2 | |
6206
+ // | | |----| |----| |
6207
+ // | | | | |
6208
+ // | L1 | L2 | ChartArea (C0) | R1 |
6209
+ // | | | | |
6210
+ // | | |----| |----| |
6211
+ // | | | C3 | | C4 | |
6212
+ // | |----|-------------------------------------|----|
6213
+ // | | | B1 | |
6214
+ // |----------------------------------------------------|
6215
+ // | B2 (Full Width) |
6216
+ // |----------------------------------------------------|
6217
+ //
6218
+ // What we do to find the best sizing, we do the following
6219
+ // 1. Determine the minimum size of the chart area.
6220
+ // 2. Split the remaining width equally between each vertical axis
6221
+ // 3. Split the remaining height equally between each horizontal axis
6222
+ // 4. Give each layout the maximum size it can be. The layout will return it's minimum size
6223
+ // 5. Adjust the sizes of each axis based on it's minimum reported size.
6224
+ // 6. Refit each axis
6225
+ // 7. Position each axis in the final location
6226
+ // 8. Tell the chart the final location of the chart area
6227
+ // 9. Tell any axes that overlay the chart area the positions of the chart area
6228
+
6229
+ // Step 1
6230
+ var chartWidth = width - leftPadding - rightPadding;
6231
+ var chartHeight = height - topPadding - bottomPadding;
6232
+ var chartAreaWidth = chartWidth / 2; // min 50%
6233
+ var chartAreaHeight = chartHeight / 2; // min 50%
6234
+
6235
+ // Step 2
6236
+ var verticalBoxWidth = (width - chartAreaWidth) / (leftBoxes.length + rightBoxes.length);
6237
+
6238
+ // Step 3
6239
+ var horizontalBoxHeight = (height - chartAreaHeight) / (topBoxes.length + bottomBoxes.length);
6240
+
6241
+ // Step 4
6242
+ var maxChartAreaWidth = chartWidth;
6243
+ var maxChartAreaHeight = chartHeight;
6244
+ var minBoxSizes = [];
6245
+
6246
+ function getMinimumBoxSize(box) {
6247
+ var minSize;
6248
+ var isHorizontal = box.isHorizontal();
6249
+
6250
+ if (isHorizontal) {
6251
+ minSize = box.update(box.fullWidth ? chartWidth : maxChartAreaWidth, horizontalBoxHeight);
6252
+ maxChartAreaHeight -= minSize.height;
6253
+ } else {
6254
+ minSize = box.update(verticalBoxWidth, chartAreaHeight);
6255
+ maxChartAreaWidth -= minSize.width;
6256
+ }
6257
+
6258
+ minBoxSizes.push({
6259
+ horizontal: isHorizontal,
6260
+ minSize: minSize,
6261
+ box: box,
6262
+ });
6263
+ }
6264
+
6265
+ helpers.each(leftBoxes.concat(rightBoxes, topBoxes, bottomBoxes), getMinimumBoxSize);
6266
+
6267
+ // If a horizontal box has padding, we move the left boxes over to avoid ugly charts (see issue #2478)
6268
+ var maxHorizontalLeftPadding = 0;
6269
+ var maxHorizontalRightPadding = 0;
6270
+ var maxVerticalTopPadding = 0;
6271
+ var maxVerticalBottomPadding = 0;
6272
+
6273
+ helpers.each(topBoxes.concat(bottomBoxes), function(horizontalBox) {
6274
+ if (horizontalBox.getPadding) {
6275
+ var boxPadding = horizontalBox.getPadding();
6276
+ maxHorizontalLeftPadding = Math.max(maxHorizontalLeftPadding, boxPadding.left);
6277
+ maxHorizontalRightPadding = Math.max(maxHorizontalRightPadding, boxPadding.right);
6278
+ }
6279
+ });
6280
+
6281
+ helpers.each(leftBoxes.concat(rightBoxes), function(verticalBox) {
6282
+ if (verticalBox.getPadding) {
6283
+ var boxPadding = verticalBox.getPadding();
6284
+ maxVerticalTopPadding = Math.max(maxVerticalTopPadding, boxPadding.top);
6285
+ maxVerticalBottomPadding = Math.max(maxVerticalBottomPadding, boxPadding.bottom);
6286
+ }
6287
+ });
6288
+
6289
+ // At this point, maxChartAreaHeight and maxChartAreaWidth are the size the chart area could
6290
+ // be if the axes are drawn at their minimum sizes.
6291
+ // Steps 5 & 6
6292
+ var totalLeftBoxesWidth = leftPadding;
6293
+ var totalRightBoxesWidth = rightPadding;
6294
+ var totalTopBoxesHeight = topPadding;
6295
+ var totalBottomBoxesHeight = bottomPadding;
6296
+
6297
+ // Function to fit a box
6298
+ function fitBox(box) {
6299
+ var minBoxSize = helpers.findNextWhere(minBoxSizes, function(minBox) {
6300
+ return minBox.box === box;
6301
+ });
6302
+
6303
+ if (minBoxSize) {
6304
+ if (box.isHorizontal()) {
6305
+ var scaleMargin = {
6306
+ left: Math.max(totalLeftBoxesWidth, maxHorizontalLeftPadding),
6307
+ right: Math.max(totalRightBoxesWidth, maxHorizontalRightPadding),
6308
+ top: 0,
6309
+ bottom: 0
6310
+ };
6311
+
6312
+ // Don't use min size here because of label rotation. When the labels are rotated, their rotation highly depends
6313
+ // on the margin. Sometimes they need to increase in size slightly
6314
+ box.update(box.fullWidth ? chartWidth : maxChartAreaWidth, chartHeight / 2, scaleMargin);
6315
+ } else {
6316
+ box.update(minBoxSize.minSize.width, maxChartAreaHeight);
6317
+ }
6318
+ }
6319
+ }
6320
+
6321
+ // Update, and calculate the left and right margins for the horizontal boxes
6322
+ helpers.each(leftBoxes.concat(rightBoxes), fitBox);
6323
+
6324
+ helpers.each(leftBoxes, function(box) {
6325
+ totalLeftBoxesWidth += box.width;
6326
+ });
6327
+
6328
+ helpers.each(rightBoxes, function(box) {
6329
+ totalRightBoxesWidth += box.width;
6330
+ });
6331
+
6332
+ // Set the Left and Right margins for the horizontal boxes
6333
+ helpers.each(topBoxes.concat(bottomBoxes), fitBox);
6334
+
6335
+ // Figure out how much margin is on the top and bottom of the vertical boxes
6336
+ helpers.each(topBoxes, function(box) {
6337
+ totalTopBoxesHeight += box.height;
6338
+ });
6339
+
6340
+ helpers.each(bottomBoxes, function(box) {
6341
+ totalBottomBoxesHeight += box.height;
6342
+ });
6343
+
6344
+ function finalFitVerticalBox(box) {
6345
+ var minBoxSize = helpers.findNextWhere(minBoxSizes, function(minSize) {
6346
+ return minSize.box === box;
6347
+ });
6348
+
6349
+ var scaleMargin = {
6350
+ left: 0,
6351
+ right: 0,
6352
+ top: totalTopBoxesHeight,
6353
+ bottom: totalBottomBoxesHeight
6354
+ };
6355
+
6356
+ if (minBoxSize) {
6357
+ box.update(minBoxSize.minSize.width, maxChartAreaHeight, scaleMargin);
6358
+ }
6359
+ }
6360
+
6361
+ // Let the left layout know the final margin
6362
+ helpers.each(leftBoxes.concat(rightBoxes), finalFitVerticalBox);
6363
+
6364
+ // Recalculate because the size of each layout might have changed slightly due to the margins (label rotation for instance)
6365
+ totalLeftBoxesWidth = leftPadding;
6366
+ totalRightBoxesWidth = rightPadding;
6367
+ totalTopBoxesHeight = topPadding;
6368
+ totalBottomBoxesHeight = bottomPadding;
6369
+
6370
+ helpers.each(leftBoxes, function(box) {
6371
+ totalLeftBoxesWidth += box.width;
6372
+ });
6373
+
6374
+ helpers.each(rightBoxes, function(box) {
6375
+ totalRightBoxesWidth += box.width;
6376
+ });
6377
+
6378
+ helpers.each(topBoxes, function(box) {
6379
+ totalTopBoxesHeight += box.height;
6380
+ });
6381
+ helpers.each(bottomBoxes, function(box) {
6382
+ totalBottomBoxesHeight += box.height;
6383
+ });
6384
+
6385
+ // We may be adding some padding to account for rotated x axis labels
6386
+ var leftPaddingAddition = Math.max(maxHorizontalLeftPadding - totalLeftBoxesWidth, 0);
6387
+ totalLeftBoxesWidth += leftPaddingAddition;
6388
+ totalRightBoxesWidth += Math.max(maxHorizontalRightPadding - totalRightBoxesWidth, 0);
6389
+
6390
+ var topPaddingAddition = Math.max(maxVerticalTopPadding - totalTopBoxesHeight, 0);
6391
+ totalTopBoxesHeight += topPaddingAddition;
6392
+ totalBottomBoxesHeight += Math.max(maxVerticalBottomPadding - totalBottomBoxesHeight, 0);
6393
+
6394
+ // Figure out if our chart area changed. This would occur if the dataset layout label rotation
6395
+ // changed due to the application of the margins in step 6. Since we can only get bigger, this is safe to do
6396
+ // without calling `fit` again
6397
+ var newMaxChartAreaHeight = height - totalTopBoxesHeight - totalBottomBoxesHeight;
6398
+ var newMaxChartAreaWidth = width - totalLeftBoxesWidth - totalRightBoxesWidth;
6399
+
6400
+ if (newMaxChartAreaWidth !== maxChartAreaWidth || newMaxChartAreaHeight !== maxChartAreaHeight) {
6401
+ helpers.each(leftBoxes, function(box) {
6402
+ box.height = newMaxChartAreaHeight;
6403
+ });
6404
+
6405
+ helpers.each(rightBoxes, function(box) {
6406
+ box.height = newMaxChartAreaHeight;
6407
+ });
6408
+
6409
+ helpers.each(topBoxes, function(box) {
6410
+ if (!box.fullWidth) {
6411
+ box.width = newMaxChartAreaWidth;
6412
+ }
6413
+ });
6414
+
6415
+ helpers.each(bottomBoxes, function(box) {
6416
+ if (!box.fullWidth) {
6417
+ box.width = newMaxChartAreaWidth;
6418
+ }
6419
+ });
6420
+
6421
+ maxChartAreaHeight = newMaxChartAreaHeight;
6422
+ maxChartAreaWidth = newMaxChartAreaWidth;
6423
+ }
6424
+
6425
+ // Step 7 - Position the boxes
6426
+ var left = leftPadding + leftPaddingAddition;
6427
+ var top = topPadding + topPaddingAddition;
6428
+
6429
+ function placeBox(box) {
6430
+ if (box.isHorizontal()) {
6431
+ box.left = box.fullWidth ? leftPadding : totalLeftBoxesWidth;
6432
+ box.right = box.fullWidth ? width - rightPadding : totalLeftBoxesWidth + maxChartAreaWidth;
6433
+ box.top = top;
6434
+ box.bottom = top + box.height;
6435
+
6436
+ // Move to next point
6437
+ top = box.bottom;
6438
+
6439
+ } else {
6440
+
6441
+ box.left = left;
6442
+ box.right = left + box.width;
6443
+ box.top = totalTopBoxesHeight;
6444
+ box.bottom = totalTopBoxesHeight + maxChartAreaHeight;
6445
+
6446
+ // Move to next point
6447
+ left = box.right;
6448
+ }
6449
+ }
6450
+
6451
+ helpers.each(leftBoxes.concat(topBoxes), placeBox);
6452
+
6453
+ // Account for chart width and height
6454
+ left += maxChartAreaWidth;
6455
+ top += maxChartAreaHeight;
6456
+
6457
+ helpers.each(rightBoxes, placeBox);
6458
+ helpers.each(bottomBoxes, placeBox);
6459
+
6460
+ // Step 8
6461
+ chart.chartArea = {
6462
+ left: totalLeftBoxesWidth,
6463
+ top: totalTopBoxesHeight,
6464
+ right: totalLeftBoxesWidth + maxChartAreaWidth,
6465
+ bottom: totalTopBoxesHeight + maxChartAreaHeight
6466
+ };
6467
+
6468
+ // Step 9
6469
+ helpers.each(chartAreaBoxes, function(box) {
6470
+ box.left = chart.chartArea.left;
6471
+ box.top = chart.chartArea.top;
6472
+ box.right = chart.chartArea.right;
6473
+ box.bottom = chart.chartArea.bottom;
6474
+
6475
+ box.update(maxChartAreaWidth, maxChartAreaHeight);
6476
+ });
6477
+ }
6478
+ };
6479
+ };
6480
+
6481
+ },{"45":45}],31:[function(require,module,exports){
6482
+ 'use strict';
6483
+
6484
+ var defaults = require(25);
6485
+ var Element = require(26);
6486
+ var helpers = require(45);
6487
+
6488
+ defaults._set('global', {
6489
+ plugins: {}
6490
+ });
6491
+
6492
+ module.exports = function(Chart) {
6493
+
6494
+ /**
6495
+ * The plugin service singleton
6496
+ * @namespace Chart.plugins
6497
+ * @since 2.1.0
6498
+ */
6499
+ Chart.plugins = {
6500
+ /**
6501
+ * Globally registered plugins.
6502
+ * @private
6503
+ */
6504
+ _plugins: [],
6505
+
6506
+ /**
6507
+ * This identifier is used to invalidate the descriptors cache attached to each chart
6508
+ * when a global plugin is registered or unregistered. In this case, the cache ID is
6509
+ * incremented and descriptors are regenerated during following API calls.
6510
+ * @private
6511
+ */
6512
+ _cacheId: 0,
6513
+
6514
+ /**
6515
+ * Registers the given plugin(s) if not already registered.
6516
+ * @param {Array|Object} plugins plugin instance(s).
6517
+ */
6518
+ register: function(plugins) {
6519
+ var p = this._plugins;
6520
+ ([]).concat(plugins).forEach(function(plugin) {
6521
+ if (p.indexOf(plugin) === -1) {
6522
+ p.push(plugin);
6523
+ }
6524
+ });
6525
+
6526
+ this._cacheId++;
6527
+ },
6528
+
6529
+ /**
6530
+ * Unregisters the given plugin(s) only if registered.
6531
+ * @param {Array|Object} plugins plugin instance(s).
6532
+ */
6533
+ unregister: function(plugins) {
6534
+ var p = this._plugins;
6535
+ ([]).concat(plugins).forEach(function(plugin) {
6536
+ var idx = p.indexOf(plugin);
6537
+ if (idx !== -1) {
6538
+ p.splice(idx, 1);
6539
+ }
6540
+ });
6541
+
6542
+ this._cacheId++;
6543
+ },
6544
+
6545
+ /**
6546
+ * Remove all registered plugins.
6547
+ * @since 2.1.5
6548
+ */
6549
+ clear: function() {
6550
+ this._plugins = [];
6551
+ this._cacheId++;
6552
+ },
6553
+
6554
+ /**
6555
+ * Returns the number of registered plugins?
6556
+ * @returns {Number}
6557
+ * @since 2.1.5
6558
+ */
6559
+ count: function() {
6560
+ return this._plugins.length;
6561
+ },
6562
+
6563
+ /**
6564
+ * Returns all registered plugin instances.
6565
+ * @returns {Array} array of plugin objects.
6566
+ * @since 2.1.5
6567
+ */
6568
+ getAll: function() {
6569
+ return this._plugins;
6570
+ },
6571
+
6572
+ /**
6573
+ * Calls enabled plugins for `chart` on the specified hook and with the given args.
6574
+ * This method immediately returns as soon as a plugin explicitly returns false. The
6575
+ * returned value can be used, for instance, to interrupt the current action.
6576
+ * @param {Object} chart - The chart instance for which plugins should be called.
6577
+ * @param {String} hook - The name of the plugin method to call (e.g. 'beforeUpdate').
6578
+ * @param {Array} [args] - Extra arguments to apply to the hook call.
6579
+ * @returns {Boolean} false if any of the plugins return false, else returns true.
6580
+ */
6581
+ notify: function(chart, hook, args) {
6582
+ var descriptors = this.descriptors(chart);
6583
+ var ilen = descriptors.length;
6584
+ var i, descriptor, plugin, params, method;
6585
+
6586
+ for (i = 0; i < ilen; ++i) {
6587
+ descriptor = descriptors[i];
6588
+ plugin = descriptor.plugin;
6589
+ method = plugin[hook];
6590
+ if (typeof method === 'function') {
6591
+ params = [chart].concat(args || []);
6592
+ params.push(descriptor.options);
6593
+ if (method.apply(plugin, params) === false) {
6594
+ return false;
6595
+ }
6596
+ }
6597
+ }
6598
+
6599
+ return true;
6600
+ },
6601
+
6602
+ /**
6603
+ * Returns descriptors of enabled plugins for the given chart.
6604
+ * @returns {Array} [{ plugin, options }]
6605
+ * @private
6606
+ */
6607
+ descriptors: function(chart) {
6608
+ var cache = chart._plugins || (chart._plugins = {});
6609
+ if (cache.id === this._cacheId) {
6610
+ return cache.descriptors;
6611
+ }
6612
+
6613
+ var plugins = [];
6614
+ var descriptors = [];
6615
+ var config = (chart && chart.config) || {};
6616
+ var options = (config.options && config.options.plugins) || {};
6617
+
6618
+ this._plugins.concat(config.plugins || []).forEach(function(plugin) {
6619
+ var idx = plugins.indexOf(plugin);
6620
+ if (idx !== -1) {
6621
+ return;
6622
+ }
6623
+
6624
+ var id = plugin.id;
6625
+ var opts = options[id];
6626
+ if (opts === false) {
6627
+ return;
6628
+ }
6629
+
6630
+ if (opts === true) {
6631
+ opts = helpers.clone(defaults.global.plugins[id]);
6632
+ }
6633
+
6634
+ plugins.push(plugin);
6635
+ descriptors.push({
6636
+ plugin: plugin,
6637
+ options: opts || {}
6638
+ });
6639
+ });
6640
+
6641
+ cache.descriptors = descriptors;
6642
+ cache.id = this._cacheId;
6643
+ return descriptors;
6644
+ }
6645
+ };
6646
+
6647
+ /**
6648
+ * Plugin extension hooks.
6649
+ * @interface IPlugin
6650
+ * @since 2.1.0
6651
+ */
6652
+ /**
6653
+ * @method IPlugin#beforeInit
6654
+ * @desc Called before initializing `chart`.
6655
+ * @param {Chart.Controller} chart - The chart instance.
6656
+ * @param {Object} options - The plugin options.
6657
+ */
6658
+ /**
6659
+ * @method IPlugin#afterInit
6660
+ * @desc Called after `chart` has been initialized and before the first update.
6661
+ * @param {Chart.Controller} chart - The chart instance.
6662
+ * @param {Object} options - The plugin options.
6663
+ */
6664
+ /**
6665
+ * @method IPlugin#beforeUpdate
6666
+ * @desc Called before updating `chart`. If any plugin returns `false`, the update
6667
+ * is cancelled (and thus subsequent render(s)) until another `update` is triggered.
6668
+ * @param {Chart.Controller} chart - The chart instance.
6669
+ * @param {Object} options - The plugin options.
6670
+ * @returns {Boolean} `false` to cancel the chart update.
6671
+ */
6672
+ /**
6673
+ * @method IPlugin#afterUpdate
6674
+ * @desc Called after `chart` has been updated and before rendering. Note that this
6675
+ * hook will not be called if the chart update has been previously cancelled.
6676
+ * @param {Chart.Controller} chart - The chart instance.
6677
+ * @param {Object} options - The plugin options.
6678
+ */
6679
+ /**
6680
+ * @method IPlugin#beforeDatasetsUpdate
6681
+ * @desc Called before updating the `chart` datasets. If any plugin returns `false`,
6682
+ * the datasets update is cancelled until another `update` is triggered.
6683
+ * @param {Chart.Controller} chart - The chart instance.
6684
+ * @param {Object} options - The plugin options.
6685
+ * @returns {Boolean} false to cancel the datasets update.
6686
+ * @since version 2.1.5
6687
+ */
6688
+ /**
6689
+ * @method IPlugin#afterDatasetsUpdate
6690
+ * @desc Called after the `chart` datasets have been updated. Note that this hook
6691
+ * will not be called if the datasets update has been previously cancelled.
6692
+ * @param {Chart.Controller} chart - The chart instance.
6693
+ * @param {Object} options - The plugin options.
6694
+ * @since version 2.1.5
6695
+ */
6696
+ /**
6697
+ * @method IPlugin#beforeDatasetUpdate
6698
+ * @desc Called before updating the `chart` dataset at the given `args.index`. If any plugin
6699
+ * returns `false`, the datasets update is cancelled until another `update` is triggered.
6700
+ * @param {Chart} chart - The chart instance.
6701
+ * @param {Object} args - The call arguments.
6702
+ * @param {Number} args.index - The dataset index.
6703
+ * @param {Object} args.meta - The dataset metadata.
6704
+ * @param {Object} options - The plugin options.
6705
+ * @returns {Boolean} `false` to cancel the chart datasets drawing.
6706
+ */
6707
+ /**
6708
+ * @method IPlugin#afterDatasetUpdate
6709
+ * @desc Called after the `chart` datasets at the given `args.index` has been updated. Note
6710
+ * that this hook will not be called if the datasets update has been previously cancelled.
6711
+ * @param {Chart} chart - The chart instance.
6712
+ * @param {Object} args - The call arguments.
6713
+ * @param {Number} args.index - The dataset index.
6714
+ * @param {Object} args.meta - The dataset metadata.
6715
+ * @param {Object} options - The plugin options.
6716
+ */
6717
+ /**
6718
+ * @method IPlugin#beforeLayout
6719
+ * @desc Called before laying out `chart`. If any plugin returns `false`,
6720
+ * the layout update is cancelled until another `update` is triggered.
6721
+ * @param {Chart.Controller} chart - The chart instance.
6722
+ * @param {Object} options - The plugin options.
6723
+ * @returns {Boolean} `false` to cancel the chart layout.
6724
+ */
6725
+ /**
6726
+ * @method IPlugin#afterLayout
6727
+ * @desc Called after the `chart` has been layed out. Note that this hook will not
6728
+ * be called if the layout update has been previously cancelled.
6729
+ * @param {Chart.Controller} chart - The chart instance.
6730
+ * @param {Object} options - The plugin options.
6731
+ */
6732
+ /**
6733
+ * @method IPlugin#beforeRender
6734
+ * @desc Called before rendering `chart`. If any plugin returns `false`,
6735
+ * the rendering is cancelled until another `render` is triggered.
6736
+ * @param {Chart.Controller} chart - The chart instance.
6737
+ * @param {Object} options - The plugin options.
6738
+ * @returns {Boolean} `false` to cancel the chart rendering.
6739
+ */
6740
+ /**
6741
+ * @method IPlugin#afterRender
6742
+ * @desc Called after the `chart` has been fully rendered (and animation completed). Note
6743
+ * that this hook will not be called if the rendering has been previously cancelled.
6744
+ * @param {Chart.Controller} chart - The chart instance.
6745
+ * @param {Object} options - The plugin options.
6746
+ */
6747
+ /**
6748
+ * @method IPlugin#beforeDraw
6749
+ * @desc Called before drawing `chart` at every animation frame specified by the given
6750
+ * easing value. If any plugin returns `false`, the frame drawing is cancelled until
6751
+ * another `render` is triggered.
6752
+ * @param {Chart.Controller} chart - The chart instance.
6753
+ * @param {Number} easingValue - The current animation value, between 0.0 and 1.0.
6754
+ * @param {Object} options - The plugin options.
6755
+ * @returns {Boolean} `false` to cancel the chart drawing.
6756
+ */
6757
+ /**
6758
+ * @method IPlugin#afterDraw
6759
+ * @desc Called after the `chart` has been drawn for the specific easing value. Note
6760
+ * that this hook will not be called if the drawing has been previously cancelled.
6761
+ * @param {Chart.Controller} chart - The chart instance.
6762
+ * @param {Number} easingValue - The current animation value, between 0.0 and 1.0.
6763
+ * @param {Object} options - The plugin options.
6764
+ */
6765
+ /**
6766
+ * @method IPlugin#beforeDatasetsDraw
6767
+ * @desc Called before drawing the `chart` datasets. If any plugin returns `false`,
6768
+ * the datasets drawing is cancelled until another `render` is triggered.
6769
+ * @param {Chart.Controller} chart - The chart instance.
6770
+ * @param {Number} easingValue - The current animation value, between 0.0 and 1.0.
6771
+ * @param {Object} options - The plugin options.
6772
+ * @returns {Boolean} `false` to cancel the chart datasets drawing.
6773
+ */
6774
+ /**
6775
+ * @method IPlugin#afterDatasetsDraw
6776
+ * @desc Called after the `chart` datasets have been drawn. Note that this hook
6777
+ * will not be called if the datasets drawing has been previously cancelled.
6778
+ * @param {Chart.Controller} chart - The chart instance.
6779
+ * @param {Number} easingValue - The current animation value, between 0.0 and 1.0.
6780
+ * @param {Object} options - The plugin options.
6781
+ */
6782
+ /**
6783
+ * @method IPlugin#beforeDatasetDraw
6784
+ * @desc Called before drawing the `chart` dataset at the given `args.index` (datasets
6785
+ * are drawn in the reverse order). If any plugin returns `false`, the datasets drawing
6786
+ * is cancelled until another `render` is triggered.
6787
+ * @param {Chart} chart - The chart instance.
6788
+ * @param {Object} args - The call arguments.
6789
+ * @param {Number} args.index - The dataset index.
6790
+ * @param {Object} args.meta - The dataset metadata.
6791
+ * @param {Number} args.easingValue - The current animation value, between 0.0 and 1.0.
6792
+ * @param {Object} options - The plugin options.
6793
+ * @returns {Boolean} `false` to cancel the chart datasets drawing.
6794
+ */
6795
+ /**
6796
+ * @method IPlugin#afterDatasetDraw
6797
+ * @desc Called after the `chart` datasets at the given `args.index` have been drawn
6798
+ * (datasets are drawn in the reverse order). Note that this hook will not be called
6799
+ * if the datasets drawing has been previously cancelled.
6800
+ * @param {Chart} chart - The chart instance.
6801
+ * @param {Object} args - The call arguments.
6802
+ * @param {Number} args.index - The dataset index.
6803
+ * @param {Object} args.meta - The dataset metadata.
6804
+ * @param {Number} args.easingValue - The current animation value, between 0.0 and 1.0.
6805
+ * @param {Object} options - The plugin options.
6806
+ */
6807
+ /**
6808
+ * @method IPlugin#beforeTooltipDraw
6809
+ * @desc Called before drawing the `tooltip`. If any plugin returns `false`,
6810
+ * the tooltip drawing is cancelled until another `render` is triggered.
6811
+ * @param {Chart} chart - The chart instance.
6812
+ * @param {Object} args - The call arguments.
6813
+ * @param {Object} args.tooltip - The tooltip.
6814
+ * @param {Number} args.easingValue - The current animation value, between 0.0 and 1.0.
6815
+ * @param {Object} options - The plugin options.
6816
+ * @returns {Boolean} `false` to cancel the chart tooltip drawing.
6817
+ */
6818
+ /**
6819
+ * @method IPlugin#afterTooltipDraw
6820
+ * @desc Called after drawing the `tooltip`. Note that this hook will not
6821
+ * be called if the tooltip drawing has been previously cancelled.
6822
+ * @param {Chart} chart - The chart instance.
6823
+ * @param {Object} args - The call arguments.
6824
+ * @param {Object} args.tooltip - The tooltip.
6825
+ * @param {Number} args.easingValue - The current animation value, between 0.0 and 1.0.
6826
+ * @param {Object} options - The plugin options.
6827
+ */
6828
+ /**
6829
+ * @method IPlugin#beforeEvent
6830
+ * @desc Called before processing the specified `event`. If any plugin returns `false`,
6831
+ * the event will be discarded.
6832
+ * @param {Chart.Controller} chart - The chart instance.
6833
+ * @param {IEvent} event - The event object.
6834
+ * @param {Object} options - The plugin options.
6835
+ */
6836
+ /**
6837
+ * @method IPlugin#afterEvent
6838
+ * @desc Called after the `event` has been consumed. Note that this hook
6839
+ * will not be called if the `event` has been previously discarded.
6840
+ * @param {Chart.Controller} chart - The chart instance.
6841
+ * @param {IEvent} event - The event object.
6842
+ * @param {Object} options - The plugin options.
6843
+ */
6844
+ /**
6845
+ * @method IPlugin#resize
6846
+ * @desc Called after the chart as been resized.
6847
+ * @param {Chart.Controller} chart - The chart instance.
6848
+ * @param {Number} size - The new canvas display size (eq. canvas.style width & height).
6849
+ * @param {Object} options - The plugin options.
6850
+ */
6851
+ /**
6852
+ * @method IPlugin#destroy
6853
+ * @desc Called after the chart as been destroyed.
6854
+ * @param {Chart.Controller} chart - The chart instance.
6855
+ * @param {Object} options - The plugin options.
6856
+ */
6857
+
6858
+ /**
6859
+ * Provided for backward compatibility, use Chart.plugins instead
6860
+ * @namespace Chart.pluginService
6861
+ * @deprecated since version 2.1.5
6862
+ * @todo remove at version 3
6863
+ * @private
6864
+ */
6865
+ Chart.pluginService = Chart.plugins;
6866
+
6867
+ /**
6868
+ * Provided for backward compatibility, inheriting from Chart.PlugingBase has no
6869
+ * effect, instead simply create/register plugins via plain JavaScript objects.
6870
+ * @interface Chart.PluginBase
6871
+ * @deprecated since version 2.5.0
6872
+ * @todo remove at version 3
6873
+ * @private
6874
+ */
6875
+ Chart.PluginBase = Element.extend({});
6876
+ };
6877
+
6878
+ },{"25":25,"26":26,"45":45}],32:[function(require,module,exports){
6879
+ 'use strict';
6880
+
6881
+ var defaults = require(25);
6882
+ var Element = require(26);
6883
+ var helpers = require(45);
6884
+ var Ticks = require(34);
6885
+
6886
+ defaults._set('scale', {
6887
+ display: true,
6888
+ position: 'left',
6889
+ offset: false,
6890
+
6891
+ // grid line settings
6892
+ gridLines: {
6893
+ display: true,
6894
+ color: 'rgba(0, 0, 0, 0.1)',
6895
+ lineWidth: 1,
6896
+ drawBorder: true,
6897
+ drawOnChartArea: true,
6898
+ drawTicks: true,
6899
+ tickMarkLength: 10,
6900
+ zeroLineWidth: 1,
6901
+ zeroLineColor: 'rgba(0,0,0,0.25)',
6902
+ zeroLineBorderDash: [],
6903
+ zeroLineBorderDashOffset: 0.0,
6904
+ offsetGridLines: false,
6905
+ borderDash: [],
6906
+ borderDashOffset: 0.0
6907
+ },
6908
+
6909
+ // scale label
6910
+ scaleLabel: {
6911
+ // display property
6912
+ display: false,
6913
+
6914
+ // actual label
6915
+ labelString: '',
6916
+
6917
+ // line height
6918
+ lineHeight: 1.2,
6919
+
6920
+ // top/bottom padding
6921
+ padding: {
6922
+ top: 4,
6923
+ bottom: 4
6924
+ }
6925
+ },
6926
+
6927
+ // label settings
6928
+ ticks: {
6929
+ beginAtZero: false,
6930
+ minRotation: 0,
6931
+ maxRotation: 50,
6932
+ mirror: false,
6933
+ padding: 0,
6934
+ reverse: false,
6935
+ display: true,
6936
+ autoSkip: true,
6937
+ autoSkipPadding: 0,
6938
+ labelOffset: 0,
6939
+ // We pass through arrays to be rendered as multiline labels, we convert Others to strings here.
6940
+ callback: Ticks.formatters.values,
6941
+ minor: {},
6942
+ major: {}
6943
+ }
6944
+ });
6945
+
6946
+ function labelsFromTicks(ticks) {
6947
+ var labels = [];
6948
+ var i, ilen;
6949
+
6950
+ for (i = 0, ilen = ticks.length; i < ilen; ++i) {
6951
+ labels.push(ticks[i].label);
6952
+ }
6953
+
6954
+ return labels;
6955
+ }
6956
+
6957
+ function getLineValue(scale, index, offsetGridLines) {
6958
+ var lineValue = scale.getPixelForTick(index);
6959
+
6960
+ if (offsetGridLines) {
6961
+ if (index === 0) {
6962
+ lineValue -= (scale.getPixelForTick(1) - lineValue) / 2;
6963
+ } else {
6964
+ lineValue -= (lineValue - scale.getPixelForTick(index - 1)) / 2;
6965
+ }
6966
+ }
6967
+ return lineValue;
6968
+ }
6969
+
6970
+ module.exports = function(Chart) {
6971
+
6972
+ function computeTextSize(context, tick, font) {
6973
+ return helpers.isArray(tick) ?
6974
+ helpers.longestText(context, font, tick) :
6975
+ context.measureText(tick).width;
6976
+ }
6977
+
6978
+ function parseFontOptions(options) {
6979
+ var valueOrDefault = helpers.valueOrDefault;
6980
+ var globalDefaults = defaults.global;
6981
+ var size = valueOrDefault(options.fontSize, globalDefaults.defaultFontSize);
6982
+ var style = valueOrDefault(options.fontStyle, globalDefaults.defaultFontStyle);
6983
+ var family = valueOrDefault(options.fontFamily, globalDefaults.defaultFontFamily);
6984
+
6985
+ return {
6986
+ size: size,
6987
+ style: style,
6988
+ family: family,
6989
+ font: helpers.fontString(size, style, family)
6990
+ };
6991
+ }
6992
+
6993
+ function parseLineHeight(options) {
6994
+ return helpers.options.toLineHeight(
6995
+ helpers.valueOrDefault(options.lineHeight, 1.2),
6996
+ helpers.valueOrDefault(options.fontSize, defaults.global.defaultFontSize));
6997
+ }
6998
+
6999
+ Chart.Scale = Element.extend({
7000
+ /**
7001
+ * Get the padding needed for the scale
7002
+ * @method getPadding
7003
+ * @private
7004
+ * @returns {Padding} the necessary padding
7005
+ */
7006
+ getPadding: function() {
7007
+ var me = this;
7008
+ return {
7009
+ left: me.paddingLeft || 0,
7010
+ top: me.paddingTop || 0,
7011
+ right: me.paddingRight || 0,
7012
+ bottom: me.paddingBottom || 0
7013
+ };
7014
+ },
7015
+
7016
+ /**
7017
+ * Returns the scale tick objects ({label, major})
7018
+ * @since 2.7
7019
+ */
7020
+ getTicks: function() {
7021
+ return this._ticks;
7022
+ },
7023
+
7024
+ // These methods are ordered by lifecyle. Utilities then follow.
7025
+ // Any function defined here is inherited by all scale types.
7026
+ // Any function can be extended by the scale type
7027
+
7028
+ mergeTicksOptions: function() {
7029
+ var ticks = this.options.ticks;
7030
+ if (ticks.minor === false) {
7031
+ ticks.minor = {
7032
+ display: false
7033
+ };
7034
+ }
7035
+ if (ticks.major === false) {
7036
+ ticks.major = {
7037
+ display: false
7038
+ };
7039
+ }
7040
+ for (var key in ticks) {
7041
+ if (key !== 'major' && key !== 'minor') {
7042
+ if (typeof ticks.minor[key] === 'undefined') {
7043
+ ticks.minor[key] = ticks[key];
7044
+ }
7045
+ if (typeof ticks.major[key] === 'undefined') {
7046
+ ticks.major[key] = ticks[key];
7047
+ }
7048
+ }
7049
+ }
7050
+ },
7051
+ beforeUpdate: function() {
7052
+ helpers.callback(this.options.beforeUpdate, [this]);
7053
+ },
7054
+ update: function(maxWidth, maxHeight, margins) {
7055
+ var me = this;
7056
+ var i, ilen, labels, label, ticks, tick;
7057
+
7058
+ // Update Lifecycle - Probably don't want to ever extend or overwrite this function ;)
7059
+ me.beforeUpdate();
7060
+
7061
+ // Absorb the master measurements
7062
+ me.maxWidth = maxWidth;
7063
+ me.maxHeight = maxHeight;
7064
+ me.margins = helpers.extend({
7065
+ left: 0,
7066
+ right: 0,
7067
+ top: 0,
7068
+ bottom: 0
7069
+ }, margins);
7070
+ me.longestTextCache = me.longestTextCache || {};
7071
+
7072
+ // Dimensions
7073
+ me.beforeSetDimensions();
7074
+ me.setDimensions();
7075
+ me.afterSetDimensions();
7076
+
7077
+ // Data min/max
7078
+ me.beforeDataLimits();
7079
+ me.determineDataLimits();
7080
+ me.afterDataLimits();
7081
+
7082
+ // Ticks - `this.ticks` is now DEPRECATED!
7083
+ // Internal ticks are now stored as objects in the PRIVATE `this._ticks` member
7084
+ // and must not be accessed directly from outside this class. `this.ticks` being
7085
+ // around for long time and not marked as private, we can't change its structure
7086
+ // without unexpected breaking changes. If you need to access the scale ticks,
7087
+ // use scale.getTicks() instead.
7088
+
7089
+ me.beforeBuildTicks();
7090
+
7091
+ // New implementations should return an array of objects but for BACKWARD COMPAT,
7092
+ // we still support no return (`this.ticks` internally set by calling this method).
7093
+ ticks = me.buildTicks() || [];
7094
+
7095
+ me.afterBuildTicks();
7096
+
7097
+ me.beforeTickToLabelConversion();
7098
+
7099
+ // New implementations should return the formatted tick labels but for BACKWARD
7100
+ // COMPAT, we still support no return (`this.ticks` internally changed by calling
7101
+ // this method and supposed to contain only string values).
7102
+ labels = me.convertTicksToLabels(ticks) || me.ticks;
7103
+
7104
+ me.afterTickToLabelConversion();
7105
+
7106
+ me.ticks = labels; // BACKWARD COMPATIBILITY
7107
+
7108
+ // IMPORTANT: from this point, we consider that `this.ticks` will NEVER change!
7109
+
7110
+ // BACKWARD COMPAT: synchronize `_ticks` with labels (so potentially `this.ticks`)
7111
+ for (i = 0, ilen = labels.length; i < ilen; ++i) {
7112
+ label = labels[i];
7113
+ tick = ticks[i];
7114
+ if (!tick) {
7115
+ ticks.push(tick = {
7116
+ label: label,
7117
+ major: false
7118
+ });
7119
+ } else {
7120
+ tick.label = label;
7121
+ }
7122
+ }
7123
+
7124
+ me._ticks = ticks;
7125
+
7126
+ // Tick Rotation
7127
+ me.beforeCalculateTickRotation();
7128
+ me.calculateTickRotation();
7129
+ me.afterCalculateTickRotation();
7130
+ // Fit
7131
+ me.beforeFit();
7132
+ me.fit();
7133
+ me.afterFit();
7134
+ //
7135
+ me.afterUpdate();
7136
+
7137
+ return me.minSize;
7138
+
7139
+ },
7140
+ afterUpdate: function() {
7141
+ helpers.callback(this.options.afterUpdate, [this]);
7142
+ },
7143
+
7144
+ //
7145
+
7146
+ beforeSetDimensions: function() {
7147
+ helpers.callback(this.options.beforeSetDimensions, [this]);
7148
+ },
7149
+ setDimensions: function() {
7150
+ var me = this;
7151
+ // Set the unconstrained dimension before label rotation
7152
+ if (me.isHorizontal()) {
7153
+ // Reset position before calculating rotation
7154
+ me.width = me.maxWidth;
7155
+ me.left = 0;
7156
+ me.right = me.width;
7157
+ } else {
7158
+ me.height = me.maxHeight;
7159
+
7160
+ // Reset position before calculating rotation
7161
+ me.top = 0;
7162
+ me.bottom = me.height;
7163
+ }
7164
+
7165
+ // Reset padding
7166
+ me.paddingLeft = 0;
7167
+ me.paddingTop = 0;
7168
+ me.paddingRight = 0;
7169
+ me.paddingBottom = 0;
7170
+ },
7171
+ afterSetDimensions: function() {
7172
+ helpers.callback(this.options.afterSetDimensions, [this]);
7173
+ },
7174
+
7175
+ // Data limits
7176
+ beforeDataLimits: function() {
7177
+ helpers.callback(this.options.beforeDataLimits, [this]);
7178
+ },
7179
+ determineDataLimits: helpers.noop,
7180
+ afterDataLimits: function() {
7181
+ helpers.callback(this.options.afterDataLimits, [this]);
7182
+ },
7183
+
7184
+ //
7185
+ beforeBuildTicks: function() {
7186
+ helpers.callback(this.options.beforeBuildTicks, [this]);
7187
+ },
7188
+ buildTicks: helpers.noop,
7189
+ afterBuildTicks: function() {
7190
+ helpers.callback(this.options.afterBuildTicks, [this]);
7191
+ },
7192
+
7193
+ beforeTickToLabelConversion: function() {
7194
+ helpers.callback(this.options.beforeTickToLabelConversion, [this]);
7195
+ },
7196
+ convertTicksToLabels: function() {
7197
+ var me = this;
7198
+ // Convert ticks to strings
7199
+ var tickOpts = me.options.ticks;
7200
+ me.ticks = me.ticks.map(tickOpts.userCallback || tickOpts.callback, this);
7201
+ },
7202
+ afterTickToLabelConversion: function() {
7203
+ helpers.callback(this.options.afterTickToLabelConversion, [this]);
7204
+ },
7205
+
7206
+ //
7207
+
7208
+ beforeCalculateTickRotation: function() {
7209
+ helpers.callback(this.options.beforeCalculateTickRotation, [this]);
7210
+ },
7211
+ calculateTickRotation: function() {
7212
+ var me = this;
7213
+ var context = me.ctx;
7214
+ var tickOpts = me.options.ticks;
7215
+ var labels = labelsFromTicks(me._ticks);
7216
+
7217
+ // Get the width of each grid by calculating the difference
7218
+ // between x offsets between 0 and 1.
7219
+ var tickFont = parseFontOptions(tickOpts);
7220
+ context.font = tickFont.font;
7221
+
7222
+ var labelRotation = tickOpts.minRotation || 0;
7223
+
7224
+ if (labels.length && me.options.display && me.isHorizontal()) {
7225
+ var originalLabelWidth = helpers.longestText(context, tickFont.font, labels, me.longestTextCache);
7226
+ var labelWidth = originalLabelWidth;
7227
+ var cosRotation, sinRotation;
7228
+
7229
+ // Allow 3 pixels x2 padding either side for label readability
7230
+ var tickWidth = me.getPixelForTick(1) - me.getPixelForTick(0) - 6;
7231
+
7232
+ // Max label rotation can be set or default to 90 - also act as a loop counter
7233
+ while (labelWidth > tickWidth && labelRotation < tickOpts.maxRotation) {
7234
+ var angleRadians = helpers.toRadians(labelRotation);
7235
+ cosRotation = Math.cos(angleRadians);
7236
+ sinRotation = Math.sin(angleRadians);
7237
+
7238
+ if (sinRotation * originalLabelWidth > me.maxHeight) {
7239
+ // go back one step
7240
+ labelRotation--;
7241
+ break;
7242
+ }
7243
+
7244
+ labelRotation++;
7245
+ labelWidth = cosRotation * originalLabelWidth;
7246
+ }
7247
+ }
7248
+
7249
+ me.labelRotation = labelRotation;
7250
+ },
7251
+ afterCalculateTickRotation: function() {
7252
+ helpers.callback(this.options.afterCalculateTickRotation, [this]);
7253
+ },
7254
+
7255
+ //
7256
+
7257
+ beforeFit: function() {
7258
+ helpers.callback(this.options.beforeFit, [this]);
7259
+ },
7260
+ fit: function() {
7261
+ var me = this;
7262
+ // Reset
7263
+ var minSize = me.minSize = {
7264
+ width: 0,
7265
+ height: 0
7266
+ };
7267
+
7268
+ var labels = labelsFromTicks(me._ticks);
7269
+
7270
+ var opts = me.options;
7271
+ var tickOpts = opts.ticks;
7272
+ var scaleLabelOpts = opts.scaleLabel;
7273
+ var gridLineOpts = opts.gridLines;
7274
+ var display = opts.display;
7275
+ var isHorizontal = me.isHorizontal();
7276
+
7277
+ var tickFont = parseFontOptions(tickOpts);
7278
+ var tickMarkLength = opts.gridLines.tickMarkLength;
7279
+
7280
+ // Width
7281
+ if (isHorizontal) {
7282
+ // subtract the margins to line up with the chartArea if we are a full width scale
7283
+ minSize.width = me.isFullWidth() ? me.maxWidth - me.margins.left - me.margins.right : me.maxWidth;
7284
+ } else {
7285
+ minSize.width = display && gridLineOpts.drawTicks ? tickMarkLength : 0;
7286
+ }
7287
+
7288
+ // height
7289
+ if (isHorizontal) {
7290
+ minSize.height = display && gridLineOpts.drawTicks ? tickMarkLength : 0;
7291
+ } else {
7292
+ minSize.height = me.maxHeight; // fill all the height
7293
+ }
7294
+
7295
+ // Are we showing a title for the scale?
7296
+ if (scaleLabelOpts.display && display) {
7297
+ var scaleLabelLineHeight = parseLineHeight(scaleLabelOpts);
7298
+ var scaleLabelPadding = helpers.options.toPadding(scaleLabelOpts.padding);
7299
+ var deltaHeight = scaleLabelLineHeight + scaleLabelPadding.height;
7300
+
7301
+ if (isHorizontal) {
7302
+ minSize.height += deltaHeight;
7303
+ } else {
7304
+ minSize.width += deltaHeight;
7305
+ }
7306
+ }
7307
+
7308
+ // Don't bother fitting the ticks if we are not showing them
7309
+ if (tickOpts.display && display) {
7310
+ var largestTextWidth = helpers.longestText(me.ctx, tickFont.font, labels, me.longestTextCache);
7311
+ var tallestLabelHeightInLines = helpers.numberOfLabelLines(labels);
7312
+ var lineSpace = tickFont.size * 0.5;
7313
+ var tickPadding = me.options.ticks.padding;
7314
+
7315
+ if (isHorizontal) {
7316
+ // A horizontal axis is more constrained by the height.
7317
+ me.longestLabelWidth = largestTextWidth;
7318
+
7319
+ var angleRadians = helpers.toRadians(me.labelRotation);
7320
+ var cosRotation = Math.cos(angleRadians);
7321
+ var sinRotation = Math.sin(angleRadians);
7322
+
7323
+ // TODO - improve this calculation
7324
+ var labelHeight = (sinRotation * largestTextWidth)
7325
+ + (tickFont.size * tallestLabelHeightInLines)
7326
+ + (lineSpace * (tallestLabelHeightInLines - 1))
7327
+ + lineSpace; // padding
7328
+
7329
+ minSize.height = Math.min(me.maxHeight, minSize.height + labelHeight + tickPadding);
7330
+
7331
+ me.ctx.font = tickFont.font;
7332
+ var firstLabelWidth = computeTextSize(me.ctx, labels[0], tickFont.font);
7333
+ var lastLabelWidth = computeTextSize(me.ctx, labels[labels.length - 1], tickFont.font);
7334
+
7335
+ // Ensure that our ticks are always inside the canvas. When rotated, ticks are right aligned
7336
+ // which means that the right padding is dominated by the font height
7337
+ if (me.labelRotation !== 0) {
7338
+ me.paddingLeft = opts.position === 'bottom' ? (cosRotation * firstLabelWidth) + 3 : (cosRotation * lineSpace) + 3; // add 3 px to move away from canvas edges
7339
+ me.paddingRight = opts.position === 'bottom' ? (cosRotation * lineSpace) + 3 : (cosRotation * lastLabelWidth) + 3;
7340
+ } else {
7341
+ me.paddingLeft = firstLabelWidth / 2 + 3; // add 3 px to move away from canvas edges
7342
+ me.paddingRight = lastLabelWidth / 2 + 3;
7343
+ }
7344
+ } else {
7345
+ // A vertical axis is more constrained by the width. Labels are the
7346
+ // dominant factor here, so get that length first and account for padding
7347
+ if (tickOpts.mirror) {
7348
+ largestTextWidth = 0;
7349
+ } else {
7350
+ // use lineSpace for consistency with horizontal axis
7351
+ // tickPadding is not implemented for horizontal
7352
+ largestTextWidth += tickPadding + lineSpace;
7353
+ }
7354
+
7355
+ minSize.width = Math.min(me.maxWidth, minSize.width + largestTextWidth);
7356
+
7357
+ me.paddingTop = tickFont.size / 2;
7358
+ me.paddingBottom = tickFont.size / 2;
7359
+ }
7360
+ }
7361
+
7362
+ me.handleMargins();
7363
+
7364
+ me.width = minSize.width;
7365
+ me.height = minSize.height;
7366
+ },
7367
+
7368
+ /**
7369
+ * Handle margins and padding interactions
7370
+ * @private
7371
+ */
7372
+ handleMargins: function() {
7373
+ var me = this;
7374
+ if (me.margins) {
7375
+ me.paddingLeft = Math.max(me.paddingLeft - me.margins.left, 0);
7376
+ me.paddingTop = Math.max(me.paddingTop - me.margins.top, 0);
7377
+ me.paddingRight = Math.max(me.paddingRight - me.margins.right, 0);
7378
+ me.paddingBottom = Math.max(me.paddingBottom - me.margins.bottom, 0);
7379
+ }
7380
+ },
7381
+
7382
+ afterFit: function() {
7383
+ helpers.callback(this.options.afterFit, [this]);
7384
+ },
7385
+
7386
+ // Shared Methods
7387
+ isHorizontal: function() {
7388
+ return this.options.position === 'top' || this.options.position === 'bottom';
7389
+ },
7390
+ isFullWidth: function() {
7391
+ return (this.options.fullWidth);
7392
+ },
7393
+
7394
+ // 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
7395
+ getRightValue: function(rawValue) {
7396
+ // Null and undefined values first
7397
+ if (helpers.isNullOrUndef(rawValue)) {
7398
+ return NaN;
7399
+ }
7400
+ // isNaN(object) returns true, so make sure NaN is checking for a number; Discard Infinite values
7401
+ if (typeof rawValue === 'number' && !isFinite(rawValue)) {
7402
+ return NaN;
7403
+ }
7404
+ // If it is in fact an object, dive in one more level
7405
+ if (rawValue) {
7406
+ if (this.isHorizontal()) {
7407
+ if (rawValue.x !== undefined) {
7408
+ return this.getRightValue(rawValue.x);
7409
+ }
7410
+ } else if (rawValue.y !== undefined) {
7411
+ return this.getRightValue(rawValue.y);
7412
+ }
7413
+ }
7414
+
7415
+ // Value is good, return it
7416
+ return rawValue;
7417
+ },
7418
+
7419
+ /**
7420
+ * Used to get the value to display in the tooltip for the data at the given index
7421
+ * @param index
7422
+ * @param datasetIndex
7423
+ */
7424
+ getLabelForIndex: helpers.noop,
7425
+
7426
+ /**
7427
+ * Returns the location of the given data point. Value can either be an index or a numerical value
7428
+ * The coordinate (0, 0) is at the upper-left corner of the canvas
7429
+ * @param value
7430
+ * @param index
7431
+ * @param datasetIndex
7432
+ */
7433
+ getPixelForValue: helpers.noop,
7434
+
7435
+ /**
7436
+ * Used to get the data value from a given pixel. This is the inverse of getPixelForValue
7437
+ * The coordinate (0, 0) is at the upper-left corner of the canvas
7438
+ * @param pixel
7439
+ */
7440
+ getValueForPixel: helpers.noop,
7441
+
7442
+ /**
7443
+ * Returns the location of the tick at the given index
7444
+ * The coordinate (0, 0) is at the upper-left corner of the canvas
7445
+ */
7446
+ getPixelForTick: function(index) {
7447
+ var me = this;
7448
+ var offset = me.options.offset;
7449
+ if (me.isHorizontal()) {
7450
+ var innerWidth = me.width - (me.paddingLeft + me.paddingRight);
7451
+ var tickWidth = innerWidth / Math.max((me._ticks.length - (offset ? 0 : 1)), 1);
7452
+ var pixel = (tickWidth * index) + me.paddingLeft;
7453
+
7454
+ if (offset) {
7455
+ pixel += tickWidth / 2;
7456
+ }
7457
+
7458
+ var finalVal = me.left + Math.round(pixel);
7459
+ finalVal += me.isFullWidth() ? me.margins.left : 0;
7460
+ return finalVal;
7461
+ }
7462
+ var innerHeight = me.height - (me.paddingTop + me.paddingBottom);
7463
+ return me.top + (index * (innerHeight / (me._ticks.length - 1)));
7464
+ },
7465
+
7466
+ /**
7467
+ * Utility for getting the pixel location of a percentage of scale
7468
+ * The coordinate (0, 0) is at the upper-left corner of the canvas
7469
+ */
7470
+ getPixelForDecimal: function(decimal) {
7471
+ var me = this;
7472
+ if (me.isHorizontal()) {
7473
+ var innerWidth = me.width - (me.paddingLeft + me.paddingRight);
7474
+ var valueOffset = (innerWidth * decimal) + me.paddingLeft;
7475
+
7476
+ var finalVal = me.left + Math.round(valueOffset);
7477
+ finalVal += me.isFullWidth() ? me.margins.left : 0;
7478
+ return finalVal;
7479
+ }
7480
+ return me.top + (decimal * me.height);
7481
+ },
7482
+
7483
+ /**
7484
+ * Returns the pixel for the minimum chart value
7485
+ * The coordinate (0, 0) is at the upper-left corner of the canvas
7486
+ */
7487
+ getBasePixel: function() {
7488
+ return this.getPixelForValue(this.getBaseValue());
7489
+ },
7490
+
7491
+ getBaseValue: function() {
7492
+ var me = this;
7493
+ var min = me.min;
7494
+ var max = me.max;
7495
+
7496
+ return me.beginAtZero ? 0 :
7497
+ min < 0 && max < 0 ? max :
7498
+ min > 0 && max > 0 ? min :
7499
+ 0;
7500
+ },
7501
+
7502
+ /**
7503
+ * Returns a subset of ticks to be plotted to avoid overlapping labels.
7504
+ * @private
7505
+ */
7506
+ _autoSkip: function(ticks) {
7507
+ var skipRatio;
7508
+ var me = this;
7509
+ var isHorizontal = me.isHorizontal();
7510
+ var optionTicks = me.options.ticks.minor;
7511
+ var tickCount = ticks.length;
7512
+ var labelRotationRadians = helpers.toRadians(me.labelRotation);
7513
+ var cosRotation = Math.cos(labelRotationRadians);
7514
+ var longestRotatedLabel = me.longestLabelWidth * cosRotation;
7515
+ var result = [];
7516
+ var i, tick, shouldSkip;
7517
+
7518
+ // figure out the maximum number of gridlines to show
7519
+ var maxTicks;
7520
+ if (optionTicks.maxTicksLimit) {
7521
+ maxTicks = optionTicks.maxTicksLimit;
7522
+ }
7523
+
7524
+ if (isHorizontal) {
7525
+ skipRatio = false;
7526
+
7527
+ if ((longestRotatedLabel + optionTicks.autoSkipPadding) * tickCount > (me.width - (me.paddingLeft + me.paddingRight))) {
7528
+ skipRatio = 1 + Math.floor(((longestRotatedLabel + optionTicks.autoSkipPadding) * tickCount) / (me.width - (me.paddingLeft + me.paddingRight)));
7529
+ }
7530
+
7531
+ // if they defined a max number of optionTicks,
7532
+ // increase skipRatio until that number is met
7533
+ if (maxTicks && tickCount > maxTicks) {
7534
+ skipRatio = Math.max(skipRatio, Math.floor(tickCount / maxTicks));
7535
+ }
7536
+ }
7537
+
7538
+ for (i = 0; i < tickCount; i++) {
7539
+ tick = ticks[i];
7540
+
7541
+ // Since we always show the last tick,we need may need to hide the last shown one before
7542
+ shouldSkip = (skipRatio > 1 && i % skipRatio > 0) || (i % skipRatio === 0 && i + skipRatio >= tickCount);
7543
+ if (shouldSkip && i !== tickCount - 1) {
7544
+ // leave tick in place but make sure it's not displayed (#4635)
7545
+ delete tick.label;
7546
+ }
7547
+ result.push(tick);
7548
+ }
7549
+ return result;
7550
+ },
7551
+
7552
+ // Actually draw the scale on the canvas
7553
+ // @param {rectangle} chartArea : the area of the chart to draw full grid lines on
7554
+ draw: function(chartArea) {
7555
+ var me = this;
7556
+ var options = me.options;
7557
+ if (!options.display) {
7558
+ return;
7559
+ }
7560
+
7561
+ var context = me.ctx;
7562
+ var globalDefaults = defaults.global;
7563
+ var optionTicks = options.ticks.minor;
7564
+ var optionMajorTicks = options.ticks.major || optionTicks;
7565
+ var gridLines = options.gridLines;
7566
+ var scaleLabel = options.scaleLabel;
7567
+
7568
+ var isRotated = me.labelRotation !== 0;
7569
+ var isHorizontal = me.isHorizontal();
7570
+
7571
+ var ticks = optionTicks.autoSkip ? me._autoSkip(me.getTicks()) : me.getTicks();
7572
+ var tickFontColor = helpers.valueOrDefault(optionTicks.fontColor, globalDefaults.defaultFontColor);
7573
+ var tickFont = parseFontOptions(optionTicks);
7574
+ var majorTickFontColor = helpers.valueOrDefault(optionMajorTicks.fontColor, globalDefaults.defaultFontColor);
7575
+ var majorTickFont = parseFontOptions(optionMajorTicks);
7576
+
7577
+ var tl = gridLines.drawTicks ? gridLines.tickMarkLength : 0;
7578
+
7579
+ var scaleLabelFontColor = helpers.valueOrDefault(scaleLabel.fontColor, globalDefaults.defaultFontColor);
7580
+ var scaleLabelFont = parseFontOptions(scaleLabel);
7581
+ var scaleLabelPadding = helpers.options.toPadding(scaleLabel.padding);
7582
+ var labelRotationRadians = helpers.toRadians(me.labelRotation);
7583
+
7584
+ var itemsToDraw = [];
7585
+
7586
+ var xTickStart = options.position === 'right' ? me.left : me.right - tl;
7587
+ var xTickEnd = options.position === 'right' ? me.left + tl : me.right;
7588
+ var yTickStart = options.position === 'bottom' ? me.top : me.bottom - tl;
7589
+ var yTickEnd = options.position === 'bottom' ? me.top + tl : me.bottom;
7590
+
7591
+ helpers.each(ticks, function(tick, index) {
7592
+ // autoskipper skipped this tick (#4635)
7593
+ if (helpers.isNullOrUndef(tick.label)) {
7594
+ return;
7595
+ }
7596
+
7597
+ var label = tick.label;
7598
+ var lineWidth, lineColor, borderDash, borderDashOffset;
7599
+ if (index === me.zeroLineIndex && options.offset === gridLines.offsetGridLines) {
7600
+ // Draw the first index specially
7601
+ lineWidth = gridLines.zeroLineWidth;
7602
+ lineColor = gridLines.zeroLineColor;
7603
+ borderDash = gridLines.zeroLineBorderDash;
7604
+ borderDashOffset = gridLines.zeroLineBorderDashOffset;
7605
+ } else {
7606
+ lineWidth = helpers.valueAtIndexOrDefault(gridLines.lineWidth, index);
7607
+ lineColor = helpers.valueAtIndexOrDefault(gridLines.color, index);
7608
+ borderDash = helpers.valueOrDefault(gridLines.borderDash, globalDefaults.borderDash);
7609
+ borderDashOffset = helpers.valueOrDefault(gridLines.borderDashOffset, globalDefaults.borderDashOffset);
7610
+ }
7611
+
7612
+ // Common properties
7613
+ var tx1, ty1, tx2, ty2, x1, y1, x2, y2, labelX, labelY;
7614
+ var textAlign = 'middle';
7615
+ var textBaseline = 'middle';
7616
+ var tickPadding = optionTicks.padding;
7617
+
7618
+ if (isHorizontal) {
7619
+ var labelYOffset = tl + tickPadding;
7620
+
7621
+ if (options.position === 'bottom') {
7622
+ // bottom
7623
+ textBaseline = !isRotated ? 'top' : 'middle';
7624
+ textAlign = !isRotated ? 'center' : 'right';
7625
+ labelY = me.top + labelYOffset;
7626
+ } else {
7627
+ // top
7628
+ textBaseline = !isRotated ? 'bottom' : 'middle';
7629
+ textAlign = !isRotated ? 'center' : 'left';
7630
+ labelY = me.bottom - labelYOffset;
7631
+ }
7632
+
7633
+ var xLineValue = getLineValue(me, index, gridLines.offsetGridLines && ticks.length > 1);
7634
+ if (xLineValue < me.left) {
7635
+ lineColor = 'rgba(0,0,0,0)';
7636
+ }
7637
+ xLineValue += helpers.aliasPixel(lineWidth);
7638
+
7639
+ labelX = me.getPixelForTick(index) + optionTicks.labelOffset; // x values for optionTicks (need to consider offsetLabel option)
7640
+
7641
+ tx1 = tx2 = x1 = x2 = xLineValue;
7642
+ ty1 = yTickStart;
7643
+ ty2 = yTickEnd;
7644
+ y1 = chartArea.top;
7645
+ y2 = chartArea.bottom;
7646
+ } else {
7647
+ var isLeft = options.position === 'left';
7648
+ var labelXOffset;
7649
+
7650
+ if (optionTicks.mirror) {
7651
+ textAlign = isLeft ? 'left' : 'right';
7652
+ labelXOffset = tickPadding;
7653
+ } else {
7654
+ textAlign = isLeft ? 'right' : 'left';
7655
+ labelXOffset = tl + tickPadding;
7656
+ }
7657
+
7658
+ labelX = isLeft ? me.right - labelXOffset : me.left + labelXOffset;
7659
+
7660
+ var yLineValue = getLineValue(me, index, gridLines.offsetGridLines && ticks.length > 1);
7661
+ if (yLineValue < me.top) {
7662
+ lineColor = 'rgba(0,0,0,0)';
7663
+ }
7664
+ yLineValue += helpers.aliasPixel(lineWidth);
7665
+
7666
+ labelY = me.getPixelForTick(index) + optionTicks.labelOffset;
7667
+
7668
+ tx1 = xTickStart;
7669
+ tx2 = xTickEnd;
7670
+ x1 = chartArea.left;
7671
+ x2 = chartArea.right;
7672
+ ty1 = ty2 = y1 = y2 = yLineValue;
7673
+ }
7674
+
7675
+ itemsToDraw.push({
7676
+ tx1: tx1,
7677
+ ty1: ty1,
7678
+ tx2: tx2,
7679
+ ty2: ty2,
7680
+ x1: x1,
7681
+ y1: y1,
7682
+ x2: x2,
7683
+ y2: y2,
7684
+ labelX: labelX,
7685
+ labelY: labelY,
7686
+ glWidth: lineWidth,
7687
+ glColor: lineColor,
7688
+ glBorderDash: borderDash,
7689
+ glBorderDashOffset: borderDashOffset,
7690
+ rotation: -1 * labelRotationRadians,
7691
+ label: label,
7692
+ major: tick.major,
7693
+ textBaseline: textBaseline,
7694
+ textAlign: textAlign
7695
+ });
7696
+ });
7697
+
7698
+ // Draw all of the tick labels, tick marks, and grid lines at the correct places
7699
+ helpers.each(itemsToDraw, function(itemToDraw) {
7700
+ if (gridLines.display) {
7701
+ context.save();
7702
+ context.lineWidth = itemToDraw.glWidth;
7703
+ context.strokeStyle = itemToDraw.glColor;
7704
+ if (context.setLineDash) {
7705
+ context.setLineDash(itemToDraw.glBorderDash);
7706
+ context.lineDashOffset = itemToDraw.glBorderDashOffset;
7707
+ }
7708
+
7709
+ context.beginPath();
7710
+
7711
+ if (gridLines.drawTicks) {
7712
+ context.moveTo(itemToDraw.tx1, itemToDraw.ty1);
7713
+ context.lineTo(itemToDraw.tx2, itemToDraw.ty2);
7714
+ }
7715
+
7716
+ if (gridLines.drawOnChartArea) {
7717
+ context.moveTo(itemToDraw.x1, itemToDraw.y1);
7718
+ context.lineTo(itemToDraw.x2, itemToDraw.y2);
7719
+ }
7720
+
7721
+ context.stroke();
7722
+ context.restore();
7723
+ }
7724
+
7725
+ if (optionTicks.display) {
7726
+ // Make sure we draw text in the correct color and font
7727
+ context.save();
7728
+ context.translate(itemToDraw.labelX, itemToDraw.labelY);
7729
+ context.rotate(itemToDraw.rotation);
7730
+ context.font = itemToDraw.major ? majorTickFont.font : tickFont.font;
7731
+ context.fillStyle = itemToDraw.major ? majorTickFontColor : tickFontColor;
7732
+ context.textBaseline = itemToDraw.textBaseline;
7733
+ context.textAlign = itemToDraw.textAlign;
7734
+
7735
+ var label = itemToDraw.label;
7736
+ if (helpers.isArray(label)) {
7737
+ for (var i = 0, y = 0; i < label.length; ++i) {
7738
+ // We just make sure the multiline element is a string here..
7739
+ context.fillText('' + label[i], 0, y);
7740
+ // apply same lineSpacing as calculated @ L#320
7741
+ y += (tickFont.size * 1.5);
7742
+ }
7743
+ } else {
7744
+ context.fillText(label, 0, 0);
7745
+ }
7746
+ context.restore();
7747
+ }
7748
+ });
7749
+
7750
+ if (scaleLabel.display) {
7751
+ // Draw the scale label
7752
+ var scaleLabelX;
7753
+ var scaleLabelY;
7754
+ var rotation = 0;
7755
+ var halfLineHeight = parseLineHeight(scaleLabel) / 2;
7756
+
7757
+ if (isHorizontal) {
7758
+ scaleLabelX = me.left + ((me.right - me.left) / 2); // midpoint of the width
7759
+ scaleLabelY = options.position === 'bottom'
7760
+ ? me.bottom - halfLineHeight - scaleLabelPadding.bottom
7761
+ : me.top + halfLineHeight + scaleLabelPadding.top;
7762
+ } else {
7763
+ var isLeft = options.position === 'left';
7764
+ scaleLabelX = isLeft
7765
+ ? me.left + halfLineHeight + scaleLabelPadding.top
7766
+ : me.right - halfLineHeight - scaleLabelPadding.top;
7767
+ scaleLabelY = me.top + ((me.bottom - me.top) / 2);
7768
+ rotation = isLeft ? -0.5 * Math.PI : 0.5 * Math.PI;
7769
+ }
7770
+
7771
+ context.save();
7772
+ context.translate(scaleLabelX, scaleLabelY);
7773
+ context.rotate(rotation);
7774
+ context.textAlign = 'center';
7775
+ context.textBaseline = 'middle';
7776
+ context.fillStyle = scaleLabelFontColor; // render in correct colour
7777
+ context.font = scaleLabelFont.font;
7778
+ context.fillText(scaleLabel.labelString, 0, 0);
7779
+ context.restore();
7780
+ }
7781
+
7782
+ if (gridLines.drawBorder) {
7783
+ // Draw the line at the edge of the axis
7784
+ context.lineWidth = helpers.valueAtIndexOrDefault(gridLines.lineWidth, 0);
7785
+ context.strokeStyle = helpers.valueAtIndexOrDefault(gridLines.color, 0);
7786
+ var x1 = me.left;
7787
+ var x2 = me.right;
7788
+ var y1 = me.top;
7789
+ var y2 = me.bottom;
7790
+
7791
+ var aliasPixel = helpers.aliasPixel(context.lineWidth);
7792
+ if (isHorizontal) {
7793
+ y1 = y2 = options.position === 'top' ? me.bottom : me.top;
7794
+ y1 += aliasPixel;
7795
+ y2 += aliasPixel;
7796
+ } else {
7797
+ x1 = x2 = options.position === 'left' ? me.right : me.left;
7798
+ x1 += aliasPixel;
7799
+ x2 += aliasPixel;
7800
+ }
7801
+
7802
+ context.beginPath();
7803
+ context.moveTo(x1, y1);
7804
+ context.lineTo(x2, y2);
7805
+ context.stroke();
7806
+ }
7807
+ }
7808
+ });
7809
+ };
7810
+
7811
+ },{"25":25,"26":26,"34":34,"45":45}],33:[function(require,module,exports){
7812
+ 'use strict';
7813
+
7814
+ var defaults = require(25);
7815
+ var helpers = require(45);
7816
+
7817
+ module.exports = function(Chart) {
7818
+
7819
+ Chart.scaleService = {
7820
+ // Scale registration object. Extensions can register new scale types (such as log or DB scales) and then
7821
+ // use the new chart options to grab the correct scale
7822
+ constructors: {},
7823
+ // Use a registration function so that we can move to an ES6 map when we no longer need to support
7824
+ // old browsers
7825
+
7826
+ // Scale config defaults
7827
+ defaults: {},
7828
+ registerScaleType: function(type, scaleConstructor, scaleDefaults) {
7829
+ this.constructors[type] = scaleConstructor;
7830
+ this.defaults[type] = helpers.clone(scaleDefaults);
7831
+ },
7832
+ getScaleConstructor: function(type) {
7833
+ return this.constructors.hasOwnProperty(type) ? this.constructors[type] : undefined;
7834
+ },
7835
+ getScaleDefaults: function(type) {
7836
+ // Return the scale defaults merged with the global settings so that we always use the latest ones
7837
+ return this.defaults.hasOwnProperty(type) ? helpers.merge({}, [defaults.scale, this.defaults[type]]) : {};
7838
+ },
7839
+ updateScaleDefaults: function(type, additions) {
7840
+ var me = this;
7841
+ if (me.defaults.hasOwnProperty(type)) {
7842
+ me.defaults[type] = helpers.extend(me.defaults[type], additions);
7843
+ }
7844
+ },
7845
+ addScalesToLayout: function(chart) {
7846
+ // Adds each scale to the chart.boxes array to be sized accordingly
7847
+ helpers.each(chart.scales, function(scale) {
7848
+ // Set ILayoutItem parameters for backwards compatibility
7849
+ scale.fullWidth = scale.options.fullWidth;
7850
+ scale.position = scale.options.position;
7851
+ scale.weight = scale.options.weight;
7852
+ Chart.layoutService.addBox(chart, scale);
7853
+ });
7854
+ }
7855
+ };
7856
+ };
7857
+
7858
+ },{"25":25,"45":45}],34:[function(require,module,exports){
7859
+ 'use strict';
7860
+
7861
+ var helpers = require(45);
7862
+
7863
+ /**
7864
+ * Namespace to hold static tick generation functions
7865
+ * @namespace Chart.Ticks
7866
+ */
7867
+ module.exports = {
7868
+ /**
7869
+ * Namespace to hold generators for different types of ticks
7870
+ * @namespace Chart.Ticks.generators
7871
+ */
7872
+ generators: {
7873
+ /**
7874
+ * Interface for the options provided to the numeric tick generator
7875
+ * @interface INumericTickGenerationOptions
7876
+ */
7877
+ /**
7878
+ * The maximum number of ticks to display
7879
+ * @name INumericTickGenerationOptions#maxTicks
7880
+ * @type Number
7881
+ */
7882
+ /**
7883
+ * The distance between each tick.
7884
+ * @name INumericTickGenerationOptions#stepSize
7885
+ * @type Number
7886
+ * @optional
7887
+ */
7888
+ /**
7889
+ * Forced minimum for the ticks. If not specified, the minimum of the data range is used to calculate the tick minimum
7890
+ * @name INumericTickGenerationOptions#min
7891
+ * @type Number
7892
+ * @optional
7893
+ */
7894
+ /**
7895
+ * The maximum value of the ticks. If not specified, the maximum of the data range is used to calculate the tick maximum
7896
+ * @name INumericTickGenerationOptions#max
7897
+ * @type Number
7898
+ * @optional
7899
+ */
7900
+
7901
+ /**
7902
+ * Generate a set of linear ticks
7903
+ * @method Chart.Ticks.generators.linear
7904
+ * @param generationOptions {INumericTickGenerationOptions} the options used to generate the ticks
7905
+ * @param dataRange {IRange} the range of the data
7906
+ * @returns {Array<Number>} array of tick values
7907
+ */
7908
+ linear: function(generationOptions, dataRange) {
7909
+ var ticks = [];
7910
+ // To get a "nice" value for the tick spacing, we will use the appropriately named
7911
+ // "nice number" algorithm. See http://stackoverflow.com/questions/8506881/nice-label-algorithm-for-charts-with-minimum-ticks
7912
+ // for details.
7913
+
7914
+ var spacing;
7915
+ if (generationOptions.stepSize && generationOptions.stepSize > 0) {
7916
+ spacing = generationOptions.stepSize;
7917
+ } else {
7918
+ var niceRange = helpers.niceNum(dataRange.max - dataRange.min, false);
7919
+ spacing = helpers.niceNum(niceRange / (generationOptions.maxTicks - 1), true);
7920
+ }
7921
+ var niceMin = Math.floor(dataRange.min / spacing) * spacing;
7922
+ var niceMax = Math.ceil(dataRange.max / spacing) * spacing;
7923
+
7924
+ // If min, max and stepSize is set and they make an evenly spaced scale use it.
7925
+ if (generationOptions.min && generationOptions.max && generationOptions.stepSize) {
7926
+ // If very close to our whole number, use it.
7927
+ if (helpers.almostWhole((generationOptions.max - generationOptions.min) / generationOptions.stepSize, spacing / 1000)) {
7928
+ niceMin = generationOptions.min;
7929
+ niceMax = generationOptions.max;
7930
+ }
7931
+ }
7932
+
7933
+ var numSpaces = (niceMax - niceMin) / spacing;
7934
+ // If very close to our rounded value, use it.
7935
+ if (helpers.almostEquals(numSpaces, Math.round(numSpaces), spacing / 1000)) {
7936
+ numSpaces = Math.round(numSpaces);
7937
+ } else {
7938
+ numSpaces = Math.ceil(numSpaces);
7939
+ }
7940
+
7941
+ // Put the values into the ticks array
7942
+ ticks.push(generationOptions.min !== undefined ? generationOptions.min : niceMin);
7943
+ for (var j = 1; j < numSpaces; ++j) {
7944
+ ticks.push(niceMin + (j * spacing));
7945
+ }
7946
+ ticks.push(generationOptions.max !== undefined ? generationOptions.max : niceMax);
7947
+
7948
+ return ticks;
7949
+ },
7950
+
7951
+ /**
7952
+ * Generate a set of logarithmic ticks
7953
+ * @method Chart.Ticks.generators.logarithmic
7954
+ * @param generationOptions {INumericTickGenerationOptions} the options used to generate the ticks
7955
+ * @param dataRange {IRange} the range of the data
7956
+ * @returns {Array<Number>} array of tick values
7957
+ */
7958
+ logarithmic: function(generationOptions, dataRange) {
7959
+ var ticks = [];
7960
+ var valueOrDefault = helpers.valueOrDefault;
7961
+
7962
+ // Figure out what the max number of ticks we can support it is based on the size of
7963
+ // the axis area. For now, we say that the minimum tick spacing in pixels must be 50
7964
+ // We also limit the maximum number of ticks to 11 which gives a nice 10 squares on
7965
+ // the graph
7966
+ var tickVal = valueOrDefault(generationOptions.min, Math.pow(10, Math.floor(helpers.log10(dataRange.min))));
7967
+
7968
+ var endExp = Math.floor(helpers.log10(dataRange.max));
7969
+ var endSignificand = Math.ceil(dataRange.max / Math.pow(10, endExp));
7970
+ var exp, significand;
7971
+
7972
+ if (tickVal === 0) {
7973
+ exp = Math.floor(helpers.log10(dataRange.minNotZero));
7974
+ significand = Math.floor(dataRange.minNotZero / Math.pow(10, exp));
7975
+
7976
+ ticks.push(tickVal);
7977
+ tickVal = significand * Math.pow(10, exp);
7978
+ } else {
7979
+ exp = Math.floor(helpers.log10(tickVal));
7980
+ significand = Math.floor(tickVal / Math.pow(10, exp));
7981
+ }
7982
+
7983
+ do {
7984
+ ticks.push(tickVal);
7985
+
7986
+ ++significand;
7987
+ if (significand === 10) {
7988
+ significand = 1;
7989
+ ++exp;
7990
+ }
7991
+
7992
+ tickVal = significand * Math.pow(10, exp);
7993
+ } while (exp < endExp || (exp === endExp && significand < endSignificand));
7994
+
7995
+ var lastTick = valueOrDefault(generationOptions.max, tickVal);
7996
+ ticks.push(lastTick);
7997
+
7998
+ return ticks;
7999
+ }
8000
+ },
8001
+
8002
+ /**
8003
+ * Namespace to hold formatters for different types of ticks
8004
+ * @namespace Chart.Ticks.formatters
8005
+ */
8006
+ formatters: {
8007
+ /**
8008
+ * Formatter for value labels
8009
+ * @method Chart.Ticks.formatters.values
8010
+ * @param value the value to display
8011
+ * @return {String|Array} the label to display
8012
+ */
8013
+ values: function(value) {
8014
+ return helpers.isArray(value) ? value : '' + value;
8015
+ },
8016
+
8017
+ /**
8018
+ * Formatter for linear numeric ticks
8019
+ * @method Chart.Ticks.formatters.linear
8020
+ * @param tickValue {Number} the value to be formatted
8021
+ * @param index {Number} the position of the tickValue parameter in the ticks array
8022
+ * @param ticks {Array<Number>} the list of ticks being converted
8023
+ * @return {String} string representation of the tickValue parameter
8024
+ */
8025
+ linear: function(tickValue, index, ticks) {
8026
+ // If we have lots of ticks, don't use the ones
8027
+ var delta = ticks.length > 3 ? ticks[2] - ticks[1] : ticks[1] - ticks[0];
8028
+
8029
+ // If we have a number like 2.5 as the delta, figure out how many decimal places we need
8030
+ if (Math.abs(delta) > 1) {
8031
+ if (tickValue !== Math.floor(tickValue)) {
8032
+ // not an integer
8033
+ delta = tickValue - Math.floor(tickValue);
8034
+ }
8035
+ }
8036
+
8037
+ var logDelta = helpers.log10(Math.abs(delta));
8038
+ var tickString = '';
8039
+
8040
+ if (tickValue !== 0) {
8041
+ var numDecimal = -1 * Math.floor(logDelta);
8042
+ numDecimal = Math.max(Math.min(numDecimal, 20), 0); // toFixed has a max of 20 decimal places
8043
+ tickString = tickValue.toFixed(numDecimal);
8044
+ } else {
8045
+ tickString = '0'; // never show decimal places for 0
8046
+ }
8047
+
8048
+ return tickString;
8049
+ },
8050
+
8051
+ logarithmic: function(tickValue, index, ticks) {
8052
+ var remain = tickValue / (Math.pow(10, Math.floor(helpers.log10(tickValue))));
8053
+
8054
+ if (tickValue === 0) {
8055
+ return '0';
8056
+ } else if (remain === 1 || remain === 2 || remain === 5 || index === 0 || index === ticks.length - 1) {
8057
+ return tickValue.toExponential();
8058
+ }
8059
+ return '';
8060
+ }
8061
+ }
8062
+ };
8063
+
8064
+ },{"45":45}],35:[function(require,module,exports){
8065
+ 'use strict';
8066
+
8067
+ var defaults = require(25);
8068
+ var Element = require(26);
8069
+ var helpers = require(45);
8070
+
8071
+ defaults._set('global', {
8072
+ tooltips: {
8073
+ enabled: true,
8074
+ custom: null,
8075
+ mode: 'nearest',
8076
+ position: 'average',
8077
+ intersect: true,
8078
+ backgroundColor: 'rgba(0,0,0,0.8)',
8079
+ titleFontStyle: 'bold',
8080
+ titleSpacing: 2,
8081
+ titleMarginBottom: 6,
8082
+ titleFontColor: '#fff',
8083
+ titleAlign: 'left',
8084
+ bodySpacing: 2,
8085
+ bodyFontColor: '#fff',
8086
+ bodyAlign: 'left',
8087
+ footerFontStyle: 'bold',
8088
+ footerSpacing: 2,
8089
+ footerMarginTop: 6,
8090
+ footerFontColor: '#fff',
8091
+ footerAlign: 'left',
8092
+ yPadding: 6,
8093
+ xPadding: 6,
8094
+ caretPadding: 2,
8095
+ caretSize: 5,
8096
+ cornerRadius: 6,
8097
+ multiKeyBackground: '#fff',
8098
+ displayColors: true,
8099
+ borderColor: 'rgba(0,0,0,0)',
8100
+ borderWidth: 0,
8101
+ callbacks: {
8102
+ // Args are: (tooltipItems, data)
8103
+ beforeTitle: helpers.noop,
8104
+ title: function(tooltipItems, data) {
8105
+ // Pick first xLabel for now
8106
+ var title = '';
8107
+ var labels = data.labels;
8108
+ var labelCount = labels ? labels.length : 0;
8109
+
8110
+ if (tooltipItems.length > 0) {
8111
+ var item = tooltipItems[0];
8112
+
8113
+ if (item.xLabel) {
8114
+ title = item.xLabel;
8115
+ } else if (labelCount > 0 && item.index < labelCount) {
8116
+ title = labels[item.index];
8117
+ }
8118
+ }
8119
+
8120
+ return title;
8121
+ },
8122
+ afterTitle: helpers.noop,
8123
+
8124
+ // Args are: (tooltipItems, data)
8125
+ beforeBody: helpers.noop,
8126
+
8127
+ // Args are: (tooltipItem, data)
8128
+ beforeLabel: helpers.noop,
8129
+ label: function(tooltipItem, data) {
8130
+ var label = data.datasets[tooltipItem.datasetIndex].label || '';
8131
+
8132
+ if (label) {
8133
+ label += ': ';
8134
+ }
8135
+ label += tooltipItem.yLabel;
8136
+ return label;
8137
+ },
8138
+ labelColor: function(tooltipItem, chart) {
8139
+ var meta = chart.getDatasetMeta(tooltipItem.datasetIndex);
8140
+ var activeElement = meta.data[tooltipItem.index];
8141
+ var view = activeElement._view;
8142
+ return {
8143
+ borderColor: view.borderColor,
8144
+ backgroundColor: view.backgroundColor
8145
+ };
8146
+ },
8147
+ labelTextColor: function() {
8148
+ return this._options.bodyFontColor;
8149
+ },
8150
+ afterLabel: helpers.noop,
8151
+
8152
+ // Args are: (tooltipItems, data)
8153
+ afterBody: helpers.noop,
8154
+
8155
+ // Args are: (tooltipItems, data)
8156
+ beforeFooter: helpers.noop,
8157
+ footer: helpers.noop,
8158
+ afterFooter: helpers.noop
8159
+ }
8160
+ }
8161
+ });
8162
+
8163
+ module.exports = function(Chart) {
8164
+
8165
+ /**
8166
+ * Helper method to merge the opacity into a color
8167
+ */
8168
+ function mergeOpacity(colorString, opacity) {
8169
+ var color = helpers.color(colorString);
8170
+ return color.alpha(opacity * color.alpha()).rgbaString();
8171
+ }
8172
+
8173
+ // Helper to push or concat based on if the 2nd parameter is an array or not
8174
+ function pushOrConcat(base, toPush) {
8175
+ if (toPush) {
8176
+ if (helpers.isArray(toPush)) {
8177
+ // base = base.concat(toPush);
8178
+ Array.prototype.push.apply(base, toPush);
8179
+ } else {
8180
+ base.push(toPush);
8181
+ }
8182
+ }
8183
+
8184
+ return base;
8185
+ }
8186
+
8187
+ // Private helper to create a tooltip item model
8188
+ // @param element : the chart element (point, arc, bar) to create the tooltip item for
8189
+ // @return : new tooltip item
8190
+ function createTooltipItem(element) {
8191
+ var xScale = element._xScale;
8192
+ var yScale = element._yScale || element._scale; // handle radar || polarArea charts
8193
+ var index = element._index;
8194
+ var datasetIndex = element._datasetIndex;
8195
+
8196
+ return {
8197
+ xLabel: xScale ? xScale.getLabelForIndex(index, datasetIndex) : '',
8198
+ yLabel: yScale ? yScale.getLabelForIndex(index, datasetIndex) : '',
8199
+ index: index,
8200
+ datasetIndex: datasetIndex,
8201
+ x: element._model.x,
8202
+ y: element._model.y
8203
+ };
8204
+ }
8205
+
8206
+ /**
8207
+ * Helper to get the reset model for the tooltip
8208
+ * @param tooltipOpts {Object} the tooltip options
8209
+ */
8210
+ function getBaseModel(tooltipOpts) {
8211
+ var globalDefaults = defaults.global;
8212
+ var valueOrDefault = helpers.valueOrDefault;
8213
+
8214
+ return {
8215
+ // Positioning
8216
+ xPadding: tooltipOpts.xPadding,
8217
+ yPadding: tooltipOpts.yPadding,
8218
+ xAlign: tooltipOpts.xAlign,
8219
+ yAlign: tooltipOpts.yAlign,
8220
+
8221
+ // Body
8222
+ bodyFontColor: tooltipOpts.bodyFontColor,
8223
+ _bodyFontFamily: valueOrDefault(tooltipOpts.bodyFontFamily, globalDefaults.defaultFontFamily),
8224
+ _bodyFontStyle: valueOrDefault(tooltipOpts.bodyFontStyle, globalDefaults.defaultFontStyle),
8225
+ _bodyAlign: tooltipOpts.bodyAlign,
8226
+ bodyFontSize: valueOrDefault(tooltipOpts.bodyFontSize, globalDefaults.defaultFontSize),
8227
+ bodySpacing: tooltipOpts.bodySpacing,
8228
+
8229
+ // Title
8230
+ titleFontColor: tooltipOpts.titleFontColor,
8231
+ _titleFontFamily: valueOrDefault(tooltipOpts.titleFontFamily, globalDefaults.defaultFontFamily),
8232
+ _titleFontStyle: valueOrDefault(tooltipOpts.titleFontStyle, globalDefaults.defaultFontStyle),
8233
+ titleFontSize: valueOrDefault(tooltipOpts.titleFontSize, globalDefaults.defaultFontSize),
8234
+ _titleAlign: tooltipOpts.titleAlign,
8235
+ titleSpacing: tooltipOpts.titleSpacing,
8236
+ titleMarginBottom: tooltipOpts.titleMarginBottom,
8237
+
8238
+ // Footer
8239
+ footerFontColor: tooltipOpts.footerFontColor,
8240
+ _footerFontFamily: valueOrDefault(tooltipOpts.footerFontFamily, globalDefaults.defaultFontFamily),
8241
+ _footerFontStyle: valueOrDefault(tooltipOpts.footerFontStyle, globalDefaults.defaultFontStyle),
8242
+ footerFontSize: valueOrDefault(tooltipOpts.footerFontSize, globalDefaults.defaultFontSize),
8243
+ _footerAlign: tooltipOpts.footerAlign,
8244
+ footerSpacing: tooltipOpts.footerSpacing,
8245
+ footerMarginTop: tooltipOpts.footerMarginTop,
8246
+
8247
+ // Appearance
8248
+ caretSize: tooltipOpts.caretSize,
8249
+ cornerRadius: tooltipOpts.cornerRadius,
8250
+ backgroundColor: tooltipOpts.backgroundColor,
8251
+ opacity: 0,
8252
+ legendColorBackground: tooltipOpts.multiKeyBackground,
8253
+ displayColors: tooltipOpts.displayColors,
8254
+ borderColor: tooltipOpts.borderColor,
8255
+ borderWidth: tooltipOpts.borderWidth
8256
+ };
8257
+ }
8258
+
8259
+ /**
8260
+ * Get the size of the tooltip
8261
+ */
8262
+ function getTooltipSize(tooltip, model) {
8263
+ var ctx = tooltip._chart.ctx;
8264
+
8265
+ var height = model.yPadding * 2; // Tooltip Padding
8266
+ var width = 0;
8267
+
8268
+ // Count of all lines in the body
8269
+ var body = model.body;
8270
+ var combinedBodyLength = body.reduce(function(count, bodyItem) {
8271
+ return count + bodyItem.before.length + bodyItem.lines.length + bodyItem.after.length;
8272
+ }, 0);
8273
+ combinedBodyLength += model.beforeBody.length + model.afterBody.length;
8274
+
8275
+ var titleLineCount = model.title.length;
8276
+ var footerLineCount = model.footer.length;
8277
+ var titleFontSize = model.titleFontSize;
8278
+ var bodyFontSize = model.bodyFontSize;
8279
+ var footerFontSize = model.footerFontSize;
8280
+
8281
+ height += titleLineCount * titleFontSize; // Title Lines
8282
+ height += titleLineCount ? (titleLineCount - 1) * model.titleSpacing : 0; // Title Line Spacing
8283
+ height += titleLineCount ? model.titleMarginBottom : 0; // Title's bottom Margin
8284
+ height += combinedBodyLength * bodyFontSize; // Body Lines
8285
+ height += combinedBodyLength ? (combinedBodyLength - 1) * model.bodySpacing : 0; // Body Line Spacing
8286
+ height += footerLineCount ? model.footerMarginTop : 0; // Footer Margin
8287
+ height += footerLineCount * (footerFontSize); // Footer Lines
8288
+ height += footerLineCount ? (footerLineCount - 1) * model.footerSpacing : 0; // Footer Line Spacing
8289
+
8290
+ // Title width
8291
+ var widthPadding = 0;
8292
+ var maxLineWidth = function(line) {
8293
+ width = Math.max(width, ctx.measureText(line).width + widthPadding);
8294
+ };
8295
+
8296
+ ctx.font = helpers.fontString(titleFontSize, model._titleFontStyle, model._titleFontFamily);
8297
+ helpers.each(model.title, maxLineWidth);
8298
+
8299
+ // Body width
8300
+ ctx.font = helpers.fontString(bodyFontSize, model._bodyFontStyle, model._bodyFontFamily);
8301
+ helpers.each(model.beforeBody.concat(model.afterBody), maxLineWidth);
8302
+
8303
+ // Body lines may include some extra width due to the color box
8304
+ widthPadding = model.displayColors ? (bodyFontSize + 2) : 0;
8305
+ helpers.each(body, function(bodyItem) {
8306
+ helpers.each(bodyItem.before, maxLineWidth);
8307
+ helpers.each(bodyItem.lines, maxLineWidth);
8308
+ helpers.each(bodyItem.after, maxLineWidth);
8309
+ });
8310
+
8311
+ // Reset back to 0
8312
+ widthPadding = 0;
8313
+
8314
+ // Footer width
8315
+ ctx.font = helpers.fontString(footerFontSize, model._footerFontStyle, model._footerFontFamily);
8316
+ helpers.each(model.footer, maxLineWidth);
8317
+
8318
+ // Add padding
8319
+ width += 2 * model.xPadding;
8320
+
8321
+ return {
8322
+ width: width,
8323
+ height: height
8324
+ };
8325
+ }
8326
+
8327
+ /**
8328
+ * Helper to get the alignment of a tooltip given the size
8329
+ */
8330
+ function determineAlignment(tooltip, size) {
8331
+ var model = tooltip._model;
8332
+ var chart = tooltip._chart;
8333
+ var chartArea = tooltip._chart.chartArea;
8334
+ var xAlign = 'center';
8335
+ var yAlign = 'center';
8336
+
8337
+ if (model.y < size.height) {
8338
+ yAlign = 'top';
8339
+ } else if (model.y > (chart.height - size.height)) {
8340
+ yAlign = 'bottom';
8341
+ }
8342
+
8343
+ var lf, rf; // functions to determine left, right alignment
8344
+ var olf, orf; // functions to determine if left/right alignment causes tooltip to go outside chart
8345
+ var yf; // function to get the y alignment if the tooltip goes outside of the left or right edges
8346
+ var midX = (chartArea.left + chartArea.right) / 2;
8347
+ var midY = (chartArea.top + chartArea.bottom) / 2;
8348
+
8349
+ if (yAlign === 'center') {
8350
+ lf = function(x) {
8351
+ return x <= midX;
8352
+ };
8353
+ rf = function(x) {
8354
+ return x > midX;
8355
+ };
8356
+ } else {
8357
+ lf = function(x) {
8358
+ return x <= (size.width / 2);
8359
+ };
8360
+ rf = function(x) {
8361
+ return x >= (chart.width - (size.width / 2));
8362
+ };
8363
+ }
8364
+
8365
+ olf = function(x) {
8366
+ return x + size.width > chart.width;
8367
+ };
8368
+ orf = function(x) {
8369
+ return x - size.width < 0;
8370
+ };
8371
+ yf = function(y) {
8372
+ return y <= midY ? 'top' : 'bottom';
8373
+ };
8374
+
8375
+ if (lf(model.x)) {
8376
+ xAlign = 'left';
8377
+
8378
+ // Is tooltip too wide and goes over the right side of the chart.?
8379
+ if (olf(model.x)) {
8380
+ xAlign = 'center';
8381
+ yAlign = yf(model.y);
8382
+ }
8383
+ } else if (rf(model.x)) {
8384
+ xAlign = 'right';
8385
+
8386
+ // Is tooltip too wide and goes outside left edge of canvas?
8387
+ if (orf(model.x)) {
8388
+ xAlign = 'center';
8389
+ yAlign = yf(model.y);
8390
+ }
8391
+ }
8392
+
8393
+ var opts = tooltip._options;
8394
+ return {
8395
+ xAlign: opts.xAlign ? opts.xAlign : xAlign,
8396
+ yAlign: opts.yAlign ? opts.yAlign : yAlign
8397
+ };
8398
+ }
8399
+
8400
+ /**
8401
+ * @Helper to get the location a tooltip needs to be placed at given the initial position (via the vm) and the size and alignment
8402
+ */
8403
+ function getBackgroundPoint(vm, size, alignment) {
8404
+ // Background Position
8405
+ var x = vm.x;
8406
+ var y = vm.y;
8407
+
8408
+ var caretSize = vm.caretSize;
8409
+ var caretPadding = vm.caretPadding;
8410
+ var cornerRadius = vm.cornerRadius;
8411
+ var xAlign = alignment.xAlign;
8412
+ var yAlign = alignment.yAlign;
8413
+ var paddingAndSize = caretSize + caretPadding;
8414
+ var radiusAndPadding = cornerRadius + caretPadding;
8415
+
8416
+ if (xAlign === 'right') {
8417
+ x -= size.width;
8418
+ } else if (xAlign === 'center') {
8419
+ x -= (size.width / 2);
8420
+ }
8421
+
8422
+ if (yAlign === 'top') {
8423
+ y += paddingAndSize;
8424
+ } else if (yAlign === 'bottom') {
8425
+ y -= size.height + paddingAndSize;
8426
+ } else {
8427
+ y -= (size.height / 2);
8428
+ }
8429
+
8430
+ if (yAlign === 'center') {
8431
+ if (xAlign === 'left') {
8432
+ x += paddingAndSize;
8433
+ } else if (xAlign === 'right') {
8434
+ x -= paddingAndSize;
8435
+ }
8436
+ } else if (xAlign === 'left') {
8437
+ x -= radiusAndPadding;
8438
+ } else if (xAlign === 'right') {
8439
+ x += radiusAndPadding;
8440
+ }
8441
+
8442
+ return {
8443
+ x: x,
8444
+ y: y
8445
+ };
8446
+ }
8447
+
8448
+ Chart.Tooltip = Element.extend({
8449
+ initialize: function() {
8450
+ this._model = getBaseModel(this._options);
8451
+ this._lastActive = [];
8452
+ },
8453
+
8454
+ // Get the title
8455
+ // Args are: (tooltipItem, data)
8456
+ getTitle: function() {
8457
+ var me = this;
8458
+ var opts = me._options;
8459
+ var callbacks = opts.callbacks;
8460
+
8461
+ var beforeTitle = callbacks.beforeTitle.apply(me, arguments);
8462
+ var title = callbacks.title.apply(me, arguments);
8463
+ var afterTitle = callbacks.afterTitle.apply(me, arguments);
8464
+
8465
+ var lines = [];
8466
+ lines = pushOrConcat(lines, beforeTitle);
8467
+ lines = pushOrConcat(lines, title);
8468
+ lines = pushOrConcat(lines, afterTitle);
8469
+
8470
+ return lines;
8471
+ },
8472
+
8473
+ // Args are: (tooltipItem, data)
8474
+ getBeforeBody: function() {
8475
+ var lines = this._options.callbacks.beforeBody.apply(this, arguments);
8476
+ return helpers.isArray(lines) ? lines : lines !== undefined ? [lines] : [];
8477
+ },
8478
+
8479
+ // Args are: (tooltipItem, data)
8480
+ getBody: function(tooltipItems, data) {
8481
+ var me = this;
8482
+ var callbacks = me._options.callbacks;
8483
+ var bodyItems = [];
8484
+
8485
+ helpers.each(tooltipItems, function(tooltipItem) {
8486
+ var bodyItem = {
8487
+ before: [],
8488
+ lines: [],
8489
+ after: []
8490
+ };
8491
+ pushOrConcat(bodyItem.before, callbacks.beforeLabel.call(me, tooltipItem, data));
8492
+ pushOrConcat(bodyItem.lines, callbacks.label.call(me, tooltipItem, data));
8493
+ pushOrConcat(bodyItem.after, callbacks.afterLabel.call(me, tooltipItem, data));
8494
+
8495
+ bodyItems.push(bodyItem);
8496
+ });
8497
+
8498
+ return bodyItems;
8499
+ },
8500
+
8501
+ // Args are: (tooltipItem, data)
8502
+ getAfterBody: function() {
8503
+ var lines = this._options.callbacks.afterBody.apply(this, arguments);
8504
+ return helpers.isArray(lines) ? lines : lines !== undefined ? [lines] : [];
8505
+ },
8506
+
8507
+ // Get the footer and beforeFooter and afterFooter lines
8508
+ // Args are: (tooltipItem, data)
8509
+ getFooter: function() {
8510
+ var me = this;
8511
+ var callbacks = me._options.callbacks;
8512
+
8513
+ var beforeFooter = callbacks.beforeFooter.apply(me, arguments);
8514
+ var footer = callbacks.footer.apply(me, arguments);
8515
+ var afterFooter = callbacks.afterFooter.apply(me, arguments);
8516
+
8517
+ var lines = [];
8518
+ lines = pushOrConcat(lines, beforeFooter);
8519
+ lines = pushOrConcat(lines, footer);
8520
+ lines = pushOrConcat(lines, afterFooter);
8521
+
8522
+ return lines;
8523
+ },
8524
+
8525
+ update: function(changed) {
8526
+ var me = this;
8527
+ var opts = me._options;
8528
+
8529
+ // Need to regenerate the model because its faster than using extend and it is necessary due to the optimization in Chart.Element.transition
8530
+ // 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
8531
+ // which breaks any animations.
8532
+ var existingModel = me._model;
8533
+ var model = me._model = getBaseModel(opts);
8534
+ var active = me._active;
8535
+
8536
+ var data = me._data;
8537
+
8538
+ // In the case where active.length === 0 we need to keep these at existing values for good animations
8539
+ var alignment = {
8540
+ xAlign: existingModel.xAlign,
8541
+ yAlign: existingModel.yAlign
8542
+ };
8543
+ var backgroundPoint = {
8544
+ x: existingModel.x,
8545
+ y: existingModel.y
8546
+ };
8547
+ var tooltipSize = {
8548
+ width: existingModel.width,
8549
+ height: existingModel.height
8550
+ };
8551
+ var tooltipPosition = {
8552
+ x: existingModel.caretX,
8553
+ y: existingModel.caretY
8554
+ };
8555
+
8556
+ var i, len;
8557
+
8558
+ if (active.length) {
8559
+ model.opacity = 1;
8560
+
8561
+ var labelColors = [];
8562
+ var labelTextColors = [];
8563
+ tooltipPosition = Chart.Tooltip.positioners[opts.position].call(me, active, me._eventPosition);
8564
+
8565
+ var tooltipItems = [];
8566
+ for (i = 0, len = active.length; i < len; ++i) {
8567
+ tooltipItems.push(createTooltipItem(active[i]));
8568
+ }
8569
+
8570
+ // If the user provided a filter function, use it to modify the tooltip items
8571
+ if (opts.filter) {
8572
+ tooltipItems = tooltipItems.filter(function(a) {
8573
+ return opts.filter(a, data);
8574
+ });
8575
+ }
8576
+
8577
+ // If the user provided a sorting function, use it to modify the tooltip items
8578
+ if (opts.itemSort) {
8579
+ tooltipItems = tooltipItems.sort(function(a, b) {
8580
+ return opts.itemSort(a, b, data);
8581
+ });
8582
+ }
8583
+
8584
+ // Determine colors for boxes
8585
+ helpers.each(tooltipItems, function(tooltipItem) {
8586
+ labelColors.push(opts.callbacks.labelColor.call(me, tooltipItem, me._chart));
8587
+ labelTextColors.push(opts.callbacks.labelTextColor.call(me, tooltipItem, me._chart));
8588
+ });
8589
+
8590
+
8591
+ // Build the Text Lines
8592
+ model.title = me.getTitle(tooltipItems, data);
8593
+ model.beforeBody = me.getBeforeBody(tooltipItems, data);
8594
+ model.body = me.getBody(tooltipItems, data);
8595
+ model.afterBody = me.getAfterBody(tooltipItems, data);
8596
+ model.footer = me.getFooter(tooltipItems, data);
8597
+
8598
+ // Initial positioning and colors
8599
+ model.x = Math.round(tooltipPosition.x);
8600
+ model.y = Math.round(tooltipPosition.y);
8601
+ model.caretPadding = opts.caretPadding;
8602
+ model.labelColors = labelColors;
8603
+ model.labelTextColors = labelTextColors;
8604
+
8605
+ // data points
8606
+ model.dataPoints = tooltipItems;
8607
+
8608
+ // We need to determine alignment of the tooltip
8609
+ tooltipSize = getTooltipSize(this, model);
8610
+ alignment = determineAlignment(this, tooltipSize);
8611
+ // Final Size and Position
8612
+ backgroundPoint = getBackgroundPoint(model, tooltipSize, alignment);
8613
+ } else {
8614
+ model.opacity = 0;
8615
+ }
8616
+
8617
+ model.xAlign = alignment.xAlign;
8618
+ model.yAlign = alignment.yAlign;
8619
+ model.x = backgroundPoint.x;
8620
+ model.y = backgroundPoint.y;
8621
+ model.width = tooltipSize.width;
8622
+ model.height = tooltipSize.height;
8623
+
8624
+ // Point where the caret on the tooltip points to
8625
+ model.caretX = tooltipPosition.x;
8626
+ model.caretY = tooltipPosition.y;
8627
+
8628
+ me._model = model;
8629
+
8630
+ if (changed && opts.custom) {
8631
+ opts.custom.call(me, model);
8632
+ }
8633
+
8634
+ return me;
8635
+ },
8636
+ drawCaret: function(tooltipPoint, size) {
8637
+ var ctx = this._chart.ctx;
8638
+ var vm = this._view;
8639
+ var caretPosition = this.getCaretPosition(tooltipPoint, size, vm);
8640
+
8641
+ ctx.lineTo(caretPosition.x1, caretPosition.y1);
8642
+ ctx.lineTo(caretPosition.x2, caretPosition.y2);
8643
+ ctx.lineTo(caretPosition.x3, caretPosition.y3);
8644
+ },
8645
+ getCaretPosition: function(tooltipPoint, size, vm) {
8646
+ var x1, x2, x3, y1, y2, y3;
8647
+ var caretSize = vm.caretSize;
8648
+ var cornerRadius = vm.cornerRadius;
8649
+ var xAlign = vm.xAlign;
8650
+ var yAlign = vm.yAlign;
8651
+ var ptX = tooltipPoint.x;
8652
+ var ptY = tooltipPoint.y;
8653
+ var width = size.width;
8654
+ var height = size.height;
8655
+
8656
+ if (yAlign === 'center') {
8657
+ y2 = ptY + (height / 2);
8658
+
8659
+ if (xAlign === 'left') {
8660
+ x1 = ptX;
8661
+ x2 = x1 - caretSize;
8662
+ x3 = x1;
8663
+
8664
+ y1 = y2 + caretSize;
8665
+ y3 = y2 - caretSize;
8666
+ } else {
8667
+ x1 = ptX + width;
8668
+ x2 = x1 + caretSize;
8669
+ x3 = x1;
8670
+
8671
+ y1 = y2 - caretSize;
8672
+ y3 = y2 + caretSize;
8673
+ }
8674
+ } else {
8675
+ if (xAlign === 'left') {
8676
+ x2 = ptX + cornerRadius + (caretSize);
8677
+ x1 = x2 - caretSize;
8678
+ x3 = x2 + caretSize;
8679
+ } else if (xAlign === 'right') {
8680
+ x2 = ptX + width - cornerRadius - caretSize;
8681
+ x1 = x2 - caretSize;
8682
+ x3 = x2 + caretSize;
8683
+ } else {
8684
+ x2 = ptX + (width / 2);
8685
+ x1 = x2 - caretSize;
8686
+ x3 = x2 + caretSize;
8687
+ }
8688
+ if (yAlign === 'top') {
8689
+ y1 = ptY;
8690
+ y2 = y1 - caretSize;
8691
+ y3 = y1;
8692
+ } else {
8693
+ y1 = ptY + height;
8694
+ y2 = y1 + caretSize;
8695
+ y3 = y1;
8696
+ // invert drawing order
8697
+ var tmp = x3;
8698
+ x3 = x1;
8699
+ x1 = tmp;
8700
+ }
8701
+ }
8702
+ return {x1: x1, x2: x2, x3: x3, y1: y1, y2: y2, y3: y3};
8703
+ },
8704
+ drawTitle: function(pt, vm, ctx, opacity) {
8705
+ var title = vm.title;
8706
+
8707
+ if (title.length) {
8708
+ ctx.textAlign = vm._titleAlign;
8709
+ ctx.textBaseline = 'top';
8710
+
8711
+ var titleFontSize = vm.titleFontSize;
8712
+ var titleSpacing = vm.titleSpacing;
8713
+
8714
+ ctx.fillStyle = mergeOpacity(vm.titleFontColor, opacity);
8715
+ ctx.font = helpers.fontString(titleFontSize, vm._titleFontStyle, vm._titleFontFamily);
8716
+
8717
+ var i, len;
8718
+ for (i = 0, len = title.length; i < len; ++i) {
8719
+ ctx.fillText(title[i], pt.x, pt.y);
8720
+ pt.y += titleFontSize + titleSpacing; // Line Height and spacing
8721
+
8722
+ if (i + 1 === title.length) {
8723
+ pt.y += vm.titleMarginBottom - titleSpacing; // If Last, add margin, remove spacing
8724
+ }
8725
+ }
8726
+ }
8727
+ },
8728
+ drawBody: function(pt, vm, ctx, opacity) {
8729
+ var bodyFontSize = vm.bodyFontSize;
8730
+ var bodySpacing = vm.bodySpacing;
8731
+ var body = vm.body;
8732
+
8733
+ ctx.textAlign = vm._bodyAlign;
8734
+ ctx.textBaseline = 'top';
8735
+ ctx.font = helpers.fontString(bodyFontSize, vm._bodyFontStyle, vm._bodyFontFamily);
8736
+
8737
+ // Before Body
8738
+ var xLinePadding = 0;
8739
+ var fillLineOfText = function(line) {
8740
+ ctx.fillText(line, pt.x + xLinePadding, pt.y);
8741
+ pt.y += bodyFontSize + bodySpacing;
8742
+ };
8743
+
8744
+ // Before body lines
8745
+ ctx.fillStyle = mergeOpacity(vm.bodyFontColor, opacity);
8746
+ helpers.each(vm.beforeBody, fillLineOfText);
8747
+
8748
+ var drawColorBoxes = vm.displayColors;
8749
+ xLinePadding = drawColorBoxes ? (bodyFontSize + 2) : 0;
8750
+
8751
+ // Draw body lines now
8752
+ helpers.each(body, function(bodyItem, i) {
8753
+ var textColor = mergeOpacity(vm.labelTextColors[i], opacity);
8754
+ ctx.fillStyle = textColor;
8755
+ helpers.each(bodyItem.before, fillLineOfText);
8756
+
8757
+ helpers.each(bodyItem.lines, function(line) {
8758
+ // Draw Legend-like boxes if needed
8759
+ if (drawColorBoxes) {
8760
+ // Fill a white rect so that colours merge nicely if the opacity is < 1
8761
+ ctx.fillStyle = mergeOpacity(vm.legendColorBackground, opacity);
8762
+ ctx.fillRect(pt.x, pt.y, bodyFontSize, bodyFontSize);
8763
+
8764
+ // Border
8765
+ ctx.lineWidth = 1;
8766
+ ctx.strokeStyle = mergeOpacity(vm.labelColors[i].borderColor, opacity);
8767
+ ctx.strokeRect(pt.x, pt.y, bodyFontSize, bodyFontSize);
8768
+
8769
+ // Inner square
8770
+ ctx.fillStyle = mergeOpacity(vm.labelColors[i].backgroundColor, opacity);
8771
+ ctx.fillRect(pt.x + 1, pt.y + 1, bodyFontSize - 2, bodyFontSize - 2);
8772
+ ctx.fillStyle = textColor;
8773
+ }
8774
+
8775
+ fillLineOfText(line);
8776
+ });
8777
+
8778
+ helpers.each(bodyItem.after, fillLineOfText);
8779
+ });
8780
+
8781
+ // Reset back to 0 for after body
8782
+ xLinePadding = 0;
8783
+
8784
+ // After body lines
8785
+ helpers.each(vm.afterBody, fillLineOfText);
8786
+ pt.y -= bodySpacing; // Remove last body spacing
8787
+ },
8788
+ drawFooter: function(pt, vm, ctx, opacity) {
8789
+ var footer = vm.footer;
8790
+
8791
+ if (footer.length) {
8792
+ pt.y += vm.footerMarginTop;
8793
+
8794
+ ctx.textAlign = vm._footerAlign;
8795
+ ctx.textBaseline = 'top';
8796
+
8797
+ ctx.fillStyle = mergeOpacity(vm.footerFontColor, opacity);
8798
+ ctx.font = helpers.fontString(vm.footerFontSize, vm._footerFontStyle, vm._footerFontFamily);
8799
+
8800
+ helpers.each(footer, function(line) {
8801
+ ctx.fillText(line, pt.x, pt.y);
8802
+ pt.y += vm.footerFontSize + vm.footerSpacing;
8803
+ });
8804
+ }
8805
+ },
8806
+ drawBackground: function(pt, vm, ctx, tooltipSize, opacity) {
8807
+ ctx.fillStyle = mergeOpacity(vm.backgroundColor, opacity);
8808
+ ctx.strokeStyle = mergeOpacity(vm.borderColor, opacity);
8809
+ ctx.lineWidth = vm.borderWidth;
8810
+ var xAlign = vm.xAlign;
8811
+ var yAlign = vm.yAlign;
8812
+ var x = pt.x;
8813
+ var y = pt.y;
8814
+ var width = tooltipSize.width;
8815
+ var height = tooltipSize.height;
8816
+ var radius = vm.cornerRadius;
8817
+
8818
+ ctx.beginPath();
8819
+ ctx.moveTo(x + radius, y);
8820
+ if (yAlign === 'top') {
8821
+ this.drawCaret(pt, tooltipSize);
8822
+ }
8823
+ ctx.lineTo(x + width - radius, y);
8824
+ ctx.quadraticCurveTo(x + width, y, x + width, y + radius);
8825
+ if (yAlign === 'center' && xAlign === 'right') {
8826
+ this.drawCaret(pt, tooltipSize);
8827
+ }
8828
+ ctx.lineTo(x + width, y + height - radius);
8829
+ ctx.quadraticCurveTo(x + width, y + height, x + width - radius, y + height);
8830
+ if (yAlign === 'bottom') {
8831
+ this.drawCaret(pt, tooltipSize);
8832
+ }
8833
+ ctx.lineTo(x + radius, y + height);
8834
+ ctx.quadraticCurveTo(x, y + height, x, y + height - radius);
8835
+ if (yAlign === 'center' && xAlign === 'left') {
8836
+ this.drawCaret(pt, tooltipSize);
8837
+ }
8838
+ ctx.lineTo(x, y + radius);
8839
+ ctx.quadraticCurveTo(x, y, x + radius, y);
8840
+ ctx.closePath();
8841
+
8842
+ ctx.fill();
8843
+
8844
+ if (vm.borderWidth > 0) {
8845
+ ctx.stroke();
8846
+ }
8847
+ },
8848
+ draw: function() {
8849
+ var ctx = this._chart.ctx;
8850
+ var vm = this._view;
8851
+
8852
+ if (vm.opacity === 0) {
8853
+ return;
8854
+ }
8855
+
8856
+ var tooltipSize = {
8857
+ width: vm.width,
8858
+ height: vm.height
8859
+ };
8860
+ var pt = {
8861
+ x: vm.x,
8862
+ y: vm.y
8863
+ };
8864
+
8865
+ // IE11/Edge does not like very small opacities, so snap to 0
8866
+ var opacity = Math.abs(vm.opacity < 1e-3) ? 0 : vm.opacity;
8867
+
8868
+ // Truthy/falsey value for empty tooltip
8869
+ var hasTooltipContent = vm.title.length || vm.beforeBody.length || vm.body.length || vm.afterBody.length || vm.footer.length;
8870
+
8871
+ if (this._options.enabled && hasTooltipContent) {
8872
+ // Draw Background
8873
+ this.drawBackground(pt, vm, ctx, tooltipSize, opacity);
8874
+
8875
+ // Draw Title, Body, and Footer
8876
+ pt.x += vm.xPadding;
8877
+ pt.y += vm.yPadding;
8878
+
8879
+ // Titles
8880
+ this.drawTitle(pt, vm, ctx, opacity);
8881
+
8882
+ // Body
8883
+ this.drawBody(pt, vm, ctx, opacity);
8884
+
8885
+ // Footer
8886
+ this.drawFooter(pt, vm, ctx, opacity);
8887
+ }
8888
+ },
8889
+
8890
+ /**
8891
+ * Handle an event
8892
+ * @private
8893
+ * @param {IEvent} event - The event to handle
8894
+ * @returns {Boolean} true if the tooltip changed
8895
+ */
8896
+ handleEvent: function(e) {
8897
+ var me = this;
8898
+ var options = me._options;
8899
+ var changed = false;
8900
+
8901
+ me._lastActive = me._lastActive || [];
8902
+
8903
+ // Find Active Elements for tooltips
8904
+ if (e.type === 'mouseout') {
8905
+ me._active = [];
8906
+ } else {
8907
+ me._active = me._chart.getElementsAtEventForMode(e, options.mode, options);
8908
+ }
8909
+
8910
+ // Remember Last Actives
8911
+ changed = !helpers.arrayEquals(me._active, me._lastActive);
8912
+
8913
+ // If tooltip didn't change, do not handle the target event
8914
+ if (!changed) {
8915
+ return false;
8916
+ }
8917
+
8918
+ me._lastActive = me._active;
8919
+
8920
+ if (options.enabled || options.custom) {
8921
+ me._eventPosition = {
8922
+ x: e.x,
8923
+ y: e.y
8924
+ };
8925
+
8926
+ var model = me._model;
8927
+ me.update(true);
8928
+ me.pivot();
8929
+
8930
+ // See if our tooltip position changed
8931
+ changed |= (model.x !== me._model.x) || (model.y !== me._model.y);
8932
+ }
8933
+
8934
+ return changed;
8935
+ }
8936
+ });
8937
+
8938
+ /**
8939
+ * @namespace Chart.Tooltip.positioners
8940
+ */
8941
+ Chart.Tooltip.positioners = {
8942
+ /**
8943
+ * Average mode places the tooltip at the average position of the elements shown
8944
+ * @function Chart.Tooltip.positioners.average
8945
+ * @param elements {ChartElement[]} the elements being displayed in the tooltip
8946
+ * @returns {Point} tooltip position
8947
+ */
8948
+ average: function(elements) {
8949
+ if (!elements.length) {
8950
+ return false;
8951
+ }
8952
+
8953
+ var i, len;
8954
+ var x = 0;
8955
+ var y = 0;
8956
+ var count = 0;
8957
+
8958
+ for (i = 0, len = elements.length; i < len; ++i) {
8959
+ var el = elements[i];
8960
+ if (el && el.hasValue()) {
8961
+ var pos = el.tooltipPosition();
8962
+ x += pos.x;
8963
+ y += pos.y;
8964
+ ++count;
8965
+ }
8966
+ }
8967
+
8968
+ return {
8969
+ x: Math.round(x / count),
8970
+ y: Math.round(y / count)
8971
+ };
8972
+ },
8973
+
8974
+ /**
8975
+ * Gets the tooltip position nearest of the item nearest to the event position
8976
+ * @function Chart.Tooltip.positioners.nearest
8977
+ * @param elements {Chart.Element[]} the tooltip elements
8978
+ * @param eventPosition {Point} the position of the event in canvas coordinates
8979
+ * @returns {Point} the tooltip position
8980
+ */
8981
+ nearest: function(elements, eventPosition) {
8982
+ var x = eventPosition.x;
8983
+ var y = eventPosition.y;
8984
+ var minDistance = Number.POSITIVE_INFINITY;
8985
+ var i, len, nearestElement;
8986
+
8987
+ for (i = 0, len = elements.length; i < len; ++i) {
8988
+ var el = elements[i];
8989
+ if (el && el.hasValue()) {
8990
+ var center = el.getCenterPoint();
8991
+ var d = helpers.distanceBetweenPoints(eventPosition, center);
8992
+
8993
+ if (d < minDistance) {
8994
+ minDistance = d;
8995
+ nearestElement = el;
8996
+ }
8997
+ }
8998
+ }
8999
+
9000
+ if (nearestElement) {
9001
+ var tp = nearestElement.tooltipPosition();
9002
+ x = tp.x;
9003
+ y = tp.y;
9004
+ }
9005
+
9006
+ return {
9007
+ x: x,
9008
+ y: y
9009
+ };
9010
+ }
9011
+ };
9012
+ };
9013
+
9014
+ },{"25":25,"26":26,"45":45}],36:[function(require,module,exports){
9015
+ 'use strict';
9016
+
9017
+ var defaults = require(25);
9018
+ var Element = require(26);
9019
+ var helpers = require(45);
9020
+
9021
+ defaults._set('global', {
9022
+ elements: {
9023
+ arc: {
9024
+ backgroundColor: defaults.global.defaultColor,
9025
+ borderColor: '#fff',
9026
+ borderWidth: 2
9027
+ }
9028
+ }
9029
+ });
9030
+
9031
+ module.exports = Element.extend({
9032
+ inLabelRange: function(mouseX) {
9033
+ var vm = this._view;
9034
+
9035
+ if (vm) {
9036
+ return (Math.pow(mouseX - vm.x, 2) < Math.pow(vm.radius + vm.hoverRadius, 2));
9037
+ }
9038
+ return false;
9039
+ },
9040
+
9041
+ inRange: function(chartX, chartY) {
9042
+ var vm = this._view;
9043
+
9044
+ if (vm) {
9045
+ var pointRelativePosition = helpers.getAngleFromPoint(vm, {x: chartX, y: chartY});
9046
+ var angle = pointRelativePosition.angle;
9047
+ var distance = pointRelativePosition.distance;
9048
+
9049
+ // Sanitise angle range
9050
+ var startAngle = vm.startAngle;
9051
+ var endAngle = vm.endAngle;
9052
+ while (endAngle < startAngle) {
9053
+ endAngle += 2.0 * Math.PI;
9054
+ }
9055
+ while (angle > endAngle) {
9056
+ angle -= 2.0 * Math.PI;
9057
+ }
9058
+ while (angle < startAngle) {
9059
+ angle += 2.0 * Math.PI;
9060
+ }
9061
+
9062
+ // Check if within the range of the open/close angle
9063
+ var betweenAngles = (angle >= startAngle && angle <= endAngle);
9064
+ var withinRadius = (distance >= vm.innerRadius && distance <= vm.outerRadius);
9065
+
9066
+ return (betweenAngles && withinRadius);
9067
+ }
9068
+ return false;
9069
+ },
9070
+
9071
+ getCenterPoint: function() {
9072
+ var vm = this._view;
9073
+ var halfAngle = (vm.startAngle + vm.endAngle) / 2;
9074
+ var halfRadius = (vm.innerRadius + vm.outerRadius) / 2;
9075
+ return {
9076
+ x: vm.x + Math.cos(halfAngle) * halfRadius,
9077
+ y: vm.y + Math.sin(halfAngle) * halfRadius
9078
+ };
9079
+ },
9080
+
9081
+ getArea: function() {
9082
+ var vm = this._view;
9083
+ return Math.PI * ((vm.endAngle - vm.startAngle) / (2 * Math.PI)) * (Math.pow(vm.outerRadius, 2) - Math.pow(vm.innerRadius, 2));
9084
+ },
9085
+
9086
+ tooltipPosition: function() {
9087
+ var vm = this._view;
9088
+ var centreAngle = vm.startAngle + ((vm.endAngle - vm.startAngle) / 2);
9089
+ var rangeFromCentre = (vm.outerRadius - vm.innerRadius) / 2 + vm.innerRadius;
9090
+
9091
+ return {
9092
+ x: vm.x + (Math.cos(centreAngle) * rangeFromCentre),
9093
+ y: vm.y + (Math.sin(centreAngle) * rangeFromCentre)
9094
+ };
9095
+ },
9096
+
9097
+ draw: function() {
9098
+ var ctx = this._chart.ctx;
9099
+ var vm = this._view;
9100
+ var sA = vm.startAngle;
9101
+ var eA = vm.endAngle;
9102
+
9103
+ ctx.beginPath();
9104
+
9105
+ ctx.arc(vm.x, vm.y, vm.outerRadius, sA, eA);
9106
+ ctx.arc(vm.x, vm.y, vm.innerRadius, eA, sA, true);
9107
+
9108
+ ctx.closePath();
9109
+ ctx.strokeStyle = vm.borderColor;
9110
+ ctx.lineWidth = vm.borderWidth;
9111
+
9112
+ ctx.fillStyle = vm.backgroundColor;
9113
+
9114
+ ctx.fill();
9115
+ ctx.lineJoin = 'bevel';
9116
+
9117
+ if (vm.borderWidth) {
9118
+ ctx.stroke();
9119
+ }
9120
+ }
9121
+ });
9122
+
9123
+ },{"25":25,"26":26,"45":45}],37:[function(require,module,exports){
9124
+ 'use strict';
9125
+
9126
+ var defaults = require(25);
9127
+ var Element = require(26);
9128
+ var helpers = require(45);
9129
+
9130
+ var globalDefaults = defaults.global;
9131
+
9132
+ defaults._set('global', {
9133
+ elements: {
9134
+ line: {
9135
+ tension: 0.4,
9136
+ backgroundColor: globalDefaults.defaultColor,
9137
+ borderWidth: 3,
9138
+ borderColor: globalDefaults.defaultColor,
9139
+ borderCapStyle: 'butt',
9140
+ borderDash: [],
9141
+ borderDashOffset: 0.0,
9142
+ borderJoinStyle: 'miter',
9143
+ capBezierPoints: true,
9144
+ fill: true, // do we fill in the area between the line and its base axis
9145
+ }
9146
+ }
9147
+ });
9148
+
9149
+ module.exports = Element.extend({
9150
+ draw: function() {
9151
+ var me = this;
9152
+ var vm = me._view;
9153
+ var ctx = me._chart.ctx;
9154
+ var spanGaps = vm.spanGaps;
9155
+ var points = me._children.slice(); // clone array
9156
+ var globalOptionLineElements = globalDefaults.elements.line;
9157
+ var lastDrawnIndex = -1;
9158
+ var index, current, previous, currentVM;
9159
+
9160
+ // If we are looping, adding the first point again
9161
+ if (me._loop && points.length) {
9162
+ points.push(points[0]);
9163
+ }
9164
+
9165
+ ctx.save();
9166
+
9167
+ // Stroke Line Options
9168
+ ctx.lineCap = vm.borderCapStyle || globalOptionLineElements.borderCapStyle;
9169
+
9170
+ // IE 9 and 10 do not support line dash
9171
+ if (ctx.setLineDash) {
9172
+ ctx.setLineDash(vm.borderDash || globalOptionLineElements.borderDash);
9173
+ }
9174
+
9175
+ ctx.lineDashOffset = vm.borderDashOffset || globalOptionLineElements.borderDashOffset;
9176
+ ctx.lineJoin = vm.borderJoinStyle || globalOptionLineElements.borderJoinStyle;
9177
+ ctx.lineWidth = vm.borderWidth || globalOptionLineElements.borderWidth;
9178
+ ctx.strokeStyle = vm.borderColor || globalDefaults.defaultColor;
9179
+
9180
+ // Stroke Line
9181
+ ctx.beginPath();
9182
+ lastDrawnIndex = -1;
9183
+
9184
+ for (index = 0; index < points.length; ++index) {
9185
+ current = points[index];
9186
+ previous = helpers.previousItem(points, index);
9187
+ currentVM = current._view;
9188
+
9189
+ // First point moves to it's starting position no matter what
9190
+ if (index === 0) {
9191
+ if (!currentVM.skip) {
9192
+ ctx.moveTo(currentVM.x, currentVM.y);
9193
+ lastDrawnIndex = index;
9194
+ }
9195
+ } else {
9196
+ previous = lastDrawnIndex === -1 ? previous : points[lastDrawnIndex];
9197
+
9198
+ if (!currentVM.skip) {
9199
+ if ((lastDrawnIndex !== (index - 1) && !spanGaps) || lastDrawnIndex === -1) {
9200
+ // There was a gap and this is the first point after the gap
9201
+ ctx.moveTo(currentVM.x, currentVM.y);
9202
+ } else {
9203
+ // Line to next point
9204
+ helpers.canvas.lineTo(ctx, previous._view, current._view);
9205
+ }
9206
+ lastDrawnIndex = index;
9207
+ }
9208
+ }
9209
+ }
9210
+
9211
+ ctx.stroke();
9212
+ ctx.restore();
9213
+ }
9214
+ });
9215
+
9216
+ },{"25":25,"26":26,"45":45}],38:[function(require,module,exports){
9217
+ 'use strict';
9218
+
9219
+ var defaults = require(25);
9220
+ var Element = require(26);
9221
+ var helpers = require(45);
9222
+
9223
+ var defaultColor = defaults.global.defaultColor;
9224
+
9225
+ defaults._set('global', {
9226
+ elements: {
9227
+ point: {
9228
+ radius: 3,
9229
+ pointStyle: 'circle',
9230
+ backgroundColor: defaultColor,
9231
+ borderColor: defaultColor,
9232
+ borderWidth: 1,
9233
+ // Hover
9234
+ hitRadius: 1,
9235
+ hoverRadius: 4,
9236
+ hoverBorderWidth: 1
9237
+ }
9238
+ }
9239
+ });
9240
+
9241
+ function xRange(mouseX) {
9242
+ var vm = this._view;
9243
+ return vm ? (Math.pow(mouseX - vm.x, 2) < Math.pow(vm.radius + vm.hitRadius, 2)) : false;
9244
+ }
9245
+
9246
+ function yRange(mouseY) {
9247
+ var vm = this._view;
9248
+ return vm ? (Math.pow(mouseY - vm.y, 2) < Math.pow(vm.radius + vm.hitRadius, 2)) : false;
9249
+ }
9250
+
9251
+ module.exports = Element.extend({
9252
+ inRange: function(mouseX, mouseY) {
9253
+ var vm = this._view;
9254
+ return vm ? ((Math.pow(mouseX - vm.x, 2) + Math.pow(mouseY - vm.y, 2)) < Math.pow(vm.hitRadius + vm.radius, 2)) : false;
9255
+ },
9256
+
9257
+ inLabelRange: xRange,
9258
+ inXRange: xRange,
9259
+ inYRange: yRange,
9260
+
9261
+ getCenterPoint: function() {
9262
+ var vm = this._view;
9263
+ return {
9264
+ x: vm.x,
9265
+ y: vm.y
9266
+ };
9267
+ },
9268
+
9269
+ getArea: function() {
9270
+ return Math.PI * Math.pow(this._view.radius, 2);
9271
+ },
9272
+
9273
+ tooltipPosition: function() {
9274
+ var vm = this._view;
9275
+ return {
9276
+ x: vm.x,
9277
+ y: vm.y,
9278
+ padding: vm.radius + vm.borderWidth
9279
+ };
9280
+ },
9281
+
9282
+ draw: function(chartArea) {
9283
+ var vm = this._view;
9284
+ var model = this._model;
9285
+ var ctx = this._chart.ctx;
9286
+ var pointStyle = vm.pointStyle;
9287
+ var radius = vm.radius;
9288
+ var x = vm.x;
9289
+ var y = vm.y;
9290
+ var color = helpers.color;
9291
+ var errMargin = 1.01; // 1.01 is margin for Accumulated error. (Especially Edge, IE.)
9292
+ var ratio = 0;
9293
+
9294
+ if (vm.skip) {
9295
+ return;
9296
+ }
9297
+
9298
+ ctx.strokeStyle = vm.borderColor || defaultColor;
9299
+ ctx.lineWidth = helpers.valueOrDefault(vm.borderWidth, defaults.global.elements.point.borderWidth);
9300
+ ctx.fillStyle = vm.backgroundColor || defaultColor;
9301
+
9302
+ // Cliping for Points.
9303
+ // going out from inner charArea?
9304
+ if ((chartArea !== undefined) && ((model.x < chartArea.left) || (chartArea.right * errMargin < model.x) || (model.y < chartArea.top) || (chartArea.bottom * errMargin < model.y))) {
9305
+ // Point fade out
9306
+ if (model.x < chartArea.left) {
9307
+ ratio = (x - model.x) / (chartArea.left - model.x);
9308
+ } else if (chartArea.right * errMargin < model.x) {
9309
+ ratio = (model.x - x) / (model.x - chartArea.right);
9310
+ } else if (model.y < chartArea.top) {
9311
+ ratio = (y - model.y) / (chartArea.top - model.y);
9312
+ } else if (chartArea.bottom * errMargin < model.y) {
9313
+ ratio = (model.y - y) / (model.y - chartArea.bottom);
9314
+ }
9315
+ ratio = Math.round(ratio * 100) / 100;
9316
+ ctx.strokeStyle = color(ctx.strokeStyle).alpha(ratio).rgbString();
9317
+ ctx.fillStyle = color(ctx.fillStyle).alpha(ratio).rgbString();
9318
+ }
9319
+
9320
+ helpers.canvas.drawPoint(ctx, pointStyle, radius, x, y);
9321
+ }
9322
+ });
9323
+
9324
+ },{"25":25,"26":26,"45":45}],39:[function(require,module,exports){
9325
+ 'use strict';
9326
+
9327
+ var defaults = require(25);
9328
+ var Element = require(26);
9329
+
9330
+ defaults._set('global', {
9331
+ elements: {
9332
+ rectangle: {
9333
+ backgroundColor: defaults.global.defaultColor,
9334
+ borderColor: defaults.global.defaultColor,
9335
+ borderSkipped: 'bottom',
9336
+ borderWidth: 0
9337
+ }
9338
+ }
9339
+ });
9340
+
9341
+ function isVertical(bar) {
9342
+ return bar._view.width !== undefined;
9343
+ }
9344
+
9345
+ /**
9346
+ * Helper function to get the bounds of the bar regardless of the orientation
9347
+ * @param bar {Chart.Element.Rectangle} the bar
9348
+ * @return {Bounds} bounds of the bar
9349
+ * @private
9350
+ */
9351
+ function getBarBounds(bar) {
9352
+ var vm = bar._view;
9353
+ var x1, x2, y1, y2;
9354
+
9355
+ if (isVertical(bar)) {
9356
+ // vertical
9357
+ var halfWidth = vm.width / 2;
9358
+ x1 = vm.x - halfWidth;
9359
+ x2 = vm.x + halfWidth;
9360
+ y1 = Math.min(vm.y, vm.base);
9361
+ y2 = Math.max(vm.y, vm.base);
9362
+ } else {
9363
+ // horizontal bar
9364
+ var halfHeight = vm.height / 2;
9365
+ x1 = Math.min(vm.x, vm.base);
9366
+ x2 = Math.max(vm.x, vm.base);
9367
+ y1 = vm.y - halfHeight;
9368
+ y2 = vm.y + halfHeight;
9369
+ }
9370
+
9371
+ return {
9372
+ left: x1,
9373
+ top: y1,
9374
+ right: x2,
9375
+ bottom: y2
9376
+ };
9377
+ }
9378
+
9379
+ module.exports = Element.extend({
9380
+ draw: function() {
9381
+ var ctx = this._chart.ctx;
9382
+ var vm = this._view;
9383
+ var left, right, top, bottom, signX, signY, borderSkipped;
9384
+ var borderWidth = vm.borderWidth;
9385
+
9386
+ if (!vm.horizontal) {
9387
+ // bar
9388
+ left = vm.x - vm.width / 2;
9389
+ right = vm.x + vm.width / 2;
9390
+ top = vm.y;
9391
+ bottom = vm.base;
9392
+ signX = 1;
9393
+ signY = bottom > top ? 1 : -1;
9394
+ borderSkipped = vm.borderSkipped || 'bottom';
9395
+ } else {
9396
+ // horizontal bar
9397
+ left = vm.base;
9398
+ right = vm.x;
9399
+ top = vm.y - vm.height / 2;
9400
+ bottom = vm.y + vm.height / 2;
9401
+ signX = right > left ? 1 : -1;
9402
+ signY = 1;
9403
+ borderSkipped = vm.borderSkipped || 'left';
9404
+ }
9405
+
9406
+ // Canvas doesn't allow us to stroke inside the width so we can
9407
+ // adjust the sizes to fit if we're setting a stroke on the line
9408
+ if (borderWidth) {
9409
+ // borderWidth shold be less than bar width and bar height.
9410
+ var barSize = Math.min(Math.abs(left - right), Math.abs(top - bottom));
9411
+ borderWidth = borderWidth > barSize ? barSize : borderWidth;
9412
+ var halfStroke = borderWidth / 2;
9413
+ // Adjust borderWidth when bar top position is near vm.base(zero).
9414
+ var borderLeft = left + (borderSkipped !== 'left' ? halfStroke * signX : 0);
9415
+ var borderRight = right + (borderSkipped !== 'right' ? -halfStroke * signX : 0);
9416
+ var borderTop = top + (borderSkipped !== 'top' ? halfStroke * signY : 0);
9417
+ var borderBottom = bottom + (borderSkipped !== 'bottom' ? -halfStroke * signY : 0);
9418
+ // not become a vertical line?
9419
+ if (borderLeft !== borderRight) {
9420
+ top = borderTop;
9421
+ bottom = borderBottom;
9422
+ }
9423
+ // not become a horizontal line?
9424
+ if (borderTop !== borderBottom) {
9425
+ left = borderLeft;
9426
+ right = borderRight;
9427
+ }
9428
+ }
9429
+
9430
+ ctx.beginPath();
9431
+ ctx.fillStyle = vm.backgroundColor;
9432
+ ctx.strokeStyle = vm.borderColor;
9433
+ ctx.lineWidth = borderWidth;
9434
+
9435
+ // Corner points, from bottom-left to bottom-right clockwise
9436
+ // | 1 2 |
9437
+ // | 0 3 |
9438
+ var corners = [
9439
+ [left, bottom],
9440
+ [left, top],
9441
+ [right, top],
9442
+ [right, bottom]
9443
+ ];
9444
+
9445
+ // Find first (starting) corner with fallback to 'bottom'
9446
+ var borders = ['bottom', 'left', 'top', 'right'];
9447
+ var startCorner = borders.indexOf(borderSkipped, 0);
9448
+ if (startCorner === -1) {
9449
+ startCorner = 0;
9450
+ }
9451
+
9452
+ function cornerAt(index) {
9453
+ return corners[(startCorner + index) % 4];
9454
+ }
9455
+
9456
+ // Draw rectangle from 'startCorner'
9457
+ var corner = cornerAt(0);
9458
+ ctx.moveTo(corner[0], corner[1]);
9459
+
9460
+ for (var i = 1; i < 4; i++) {
9461
+ corner = cornerAt(i);
9462
+ ctx.lineTo(corner[0], corner[1]);
9463
+ }
9464
+
9465
+ ctx.fill();
9466
+ if (borderWidth) {
9467
+ ctx.stroke();
9468
+ }
9469
+ },
9470
+
9471
+ height: function() {
9472
+ var vm = this._view;
9473
+ return vm.base - vm.y;
9474
+ },
9475
+
9476
+ inRange: function(mouseX, mouseY) {
9477
+ var inRange = false;
9478
+
9479
+ if (this._view) {
9480
+ var bounds = getBarBounds(this);
9481
+ inRange = mouseX >= bounds.left && mouseX <= bounds.right && mouseY >= bounds.top && mouseY <= bounds.bottom;
9482
+ }
9483
+
9484
+ return inRange;
9485
+ },
9486
+
9487
+ inLabelRange: function(mouseX, mouseY) {
9488
+ var me = this;
9489
+ if (!me._view) {
9490
+ return false;
9491
+ }
9492
+
9493
+ var inRange = false;
9494
+ var bounds = getBarBounds(me);
9495
+
9496
+ if (isVertical(me)) {
9497
+ inRange = mouseX >= bounds.left && mouseX <= bounds.right;
9498
+ } else {
9499
+ inRange = mouseY >= bounds.top && mouseY <= bounds.bottom;
9500
+ }
9501
+
9502
+ return inRange;
9503
+ },
9504
+
9505
+ inXRange: function(mouseX) {
9506
+ var bounds = getBarBounds(this);
9507
+ return mouseX >= bounds.left && mouseX <= bounds.right;
9508
+ },
9509
+
9510
+ inYRange: function(mouseY) {
9511
+ var bounds = getBarBounds(this);
9512
+ return mouseY >= bounds.top && mouseY <= bounds.bottom;
9513
+ },
9514
+
9515
+ getCenterPoint: function() {
9516
+ var vm = this._view;
9517
+ var x, y;
9518
+ if (isVertical(this)) {
9519
+ x = vm.x;
9520
+ y = (vm.y + vm.base) / 2;
9521
+ } else {
9522
+ x = (vm.x + vm.base) / 2;
9523
+ y = vm.y;
9524
+ }
9525
+
9526
+ return {x: x, y: y};
9527
+ },
9528
+
9529
+ getArea: function() {
9530
+ var vm = this._view;
9531
+ return vm.width * Math.abs(vm.y - vm.base);
9532
+ },
9533
+
9534
+ tooltipPosition: function() {
9535
+ var vm = this._view;
9536
+ return {
9537
+ x: vm.x,
9538
+ y: vm.y
9539
+ };
9540
+ }
9541
+ });
9542
+
9543
+ },{"25":25,"26":26}],40:[function(require,module,exports){
9544
+ 'use strict';
9545
+
9546
+ module.exports = {};
9547
+ module.exports.Arc = require(36);
9548
+ module.exports.Line = require(37);
9549
+ module.exports.Point = require(38);
9550
+ module.exports.Rectangle = require(39);
9551
+
9552
+ },{"36":36,"37":37,"38":38,"39":39}],41:[function(require,module,exports){
9553
+ 'use strict';
9554
+
9555
+ var helpers = require(42);
9556
+
9557
+ /**
9558
+ * @namespace Chart.helpers.canvas
9559
+ */
9560
+ var exports = module.exports = {
9561
+ /**
9562
+ * Clears the entire canvas associated to the given `chart`.
9563
+ * @param {Chart} chart - The chart for which to clear the canvas.
9564
+ */
9565
+ clear: function(chart) {
9566
+ chart.ctx.clearRect(0, 0, chart.width, chart.height);
9567
+ },
9568
+
9569
+ /**
9570
+ * Creates a "path" for a rectangle with rounded corners at position (x, y) with a
9571
+ * given size (width, height) and the same `radius` for all corners.
9572
+ * @param {CanvasRenderingContext2D} ctx - The canvas 2D Context.
9573
+ * @param {Number} x - The x axis of the coordinate for the rectangle starting point.
9574
+ * @param {Number} y - The y axis of the coordinate for the rectangle starting point.
9575
+ * @param {Number} width - The rectangle's width.
9576
+ * @param {Number} height - The rectangle's height.
9577
+ * @param {Number} radius - The rounded amount (in pixels) for the four corners.
9578
+ * @todo handle `radius` as top-left, top-right, bottom-right, bottom-left array/object?
9579
+ */
9580
+ roundedRect: function(ctx, x, y, width, height, radius) {
9581
+ if (radius) {
9582
+ var rx = Math.min(radius, width / 2);
9583
+ var ry = Math.min(radius, height / 2);
9584
+
9585
+ ctx.moveTo(x + rx, y);
9586
+ ctx.lineTo(x + width - rx, y);
9587
+ ctx.quadraticCurveTo(x + width, y, x + width, y + ry);
9588
+ ctx.lineTo(x + width, y + height - ry);
9589
+ ctx.quadraticCurveTo(x + width, y + height, x + width - rx, y + height);
9590
+ ctx.lineTo(x + rx, y + height);
9591
+ ctx.quadraticCurveTo(x, y + height, x, y + height - ry);
9592
+ ctx.lineTo(x, y + ry);
9593
+ ctx.quadraticCurveTo(x, y, x + rx, y);
9594
+ } else {
9595
+ ctx.rect(x, y, width, height);
9596
+ }
9597
+ },
9598
+
9599
+ drawPoint: function(ctx, style, radius, x, y) {
9600
+ var type, edgeLength, xOffset, yOffset, height, size;
9601
+
9602
+ if (style && typeof style === 'object') {
9603
+ type = style.toString();
9604
+ if (type === '[object HTMLImageElement]' || type === '[object HTMLCanvasElement]') {
9605
+ ctx.drawImage(style, x - style.width / 2, y - style.height / 2, style.width, style.height);
9606
+ return;
9607
+ }
9608
+ }
9609
+
9610
+ if (isNaN(radius) || radius <= 0) {
9611
+ return;
9612
+ }
9613
+
9614
+ switch (style) {
9615
+ // Default includes circle
9616
+ default:
9617
+ ctx.beginPath();
9618
+ ctx.arc(x, y, radius, 0, Math.PI * 2);
9619
+ ctx.closePath();
9620
+ ctx.fill();
9621
+ break;
9622
+ case 'triangle':
9623
+ ctx.beginPath();
9624
+ edgeLength = 3 * radius / Math.sqrt(3);
9625
+ height = edgeLength * Math.sqrt(3) / 2;
9626
+ ctx.moveTo(x - edgeLength / 2, y + height / 3);
9627
+ ctx.lineTo(x + edgeLength / 2, y + height / 3);
9628
+ ctx.lineTo(x, y - 2 * height / 3);
9629
+ ctx.closePath();
9630
+ ctx.fill();
9631
+ break;
9632
+ case 'rect':
9633
+ size = 1 / Math.SQRT2 * radius;
9634
+ ctx.beginPath();
9635
+ ctx.fillRect(x - size, y - size, 2 * size, 2 * size);
9636
+ ctx.strokeRect(x - size, y - size, 2 * size, 2 * size);
9637
+ break;
9638
+ case 'rectRounded':
9639
+ var offset = radius / Math.SQRT2;
9640
+ var leftX = x - offset;
9641
+ var topY = y - offset;
9642
+ var sideSize = Math.SQRT2 * radius;
9643
+ ctx.beginPath();
9644
+ this.roundedRect(ctx, leftX, topY, sideSize, sideSize, radius / 2);
9645
+ ctx.closePath();
9646
+ ctx.fill();
9647
+ break;
9648
+ case 'rectRot':
9649
+ size = 1 / Math.SQRT2 * radius;
9650
+ ctx.beginPath();
9651
+ ctx.moveTo(x - size, y);
9652
+ ctx.lineTo(x, y + size);
9653
+ ctx.lineTo(x + size, y);
9654
+ ctx.lineTo(x, y - size);
9655
+ ctx.closePath();
9656
+ ctx.fill();
9657
+ break;
9658
+ case 'cross':
9659
+ ctx.beginPath();
9660
+ ctx.moveTo(x, y + radius);
9661
+ ctx.lineTo(x, y - radius);
9662
+ ctx.moveTo(x - radius, y);
9663
+ ctx.lineTo(x + radius, y);
9664
+ ctx.closePath();
9665
+ break;
9666
+ case 'crossRot':
9667
+ ctx.beginPath();
9668
+ xOffset = Math.cos(Math.PI / 4) * radius;
9669
+ yOffset = Math.sin(Math.PI / 4) * radius;
9670
+ ctx.moveTo(x - xOffset, y - yOffset);
9671
+ ctx.lineTo(x + xOffset, y + yOffset);
9672
+ ctx.moveTo(x - xOffset, y + yOffset);
9673
+ ctx.lineTo(x + xOffset, y - yOffset);
9674
+ ctx.closePath();
9675
+ break;
9676
+ case 'star':
9677
+ ctx.beginPath();
9678
+ ctx.moveTo(x, y + radius);
9679
+ ctx.lineTo(x, y - radius);
9680
+ ctx.moveTo(x - radius, y);
9681
+ ctx.lineTo(x + radius, y);
9682
+ xOffset = Math.cos(Math.PI / 4) * radius;
9683
+ yOffset = Math.sin(Math.PI / 4) * radius;
9684
+ ctx.moveTo(x - xOffset, y - yOffset);
9685
+ ctx.lineTo(x + xOffset, y + yOffset);
9686
+ ctx.moveTo(x - xOffset, y + yOffset);
9687
+ ctx.lineTo(x + xOffset, y - yOffset);
9688
+ ctx.closePath();
9689
+ break;
9690
+ case 'line':
9691
+ ctx.beginPath();
9692
+ ctx.moveTo(x - radius, y);
9693
+ ctx.lineTo(x + radius, y);
9694
+ ctx.closePath();
9695
+ break;
9696
+ case 'dash':
9697
+ ctx.beginPath();
9698
+ ctx.moveTo(x, y);
9699
+ ctx.lineTo(x + radius, y);
9700
+ ctx.closePath();
9701
+ break;
9702
+ }
9703
+
9704
+ ctx.stroke();
9705
+ },
9706
+
9707
+ clipArea: function(ctx, area) {
9708
+ ctx.save();
9709
+ ctx.beginPath();
9710
+ ctx.rect(area.left, area.top, area.right - area.left, area.bottom - area.top);
9711
+ ctx.clip();
9712
+ },
9713
+
9714
+ unclipArea: function(ctx) {
9715
+ ctx.restore();
9716
+ },
9717
+
9718
+ lineTo: function(ctx, previous, target, flip) {
9719
+ if (target.steppedLine) {
9720
+ if ((target.steppedLine === 'after' && !flip) || (target.steppedLine !== 'after' && flip)) {
9721
+ ctx.lineTo(previous.x, target.y);
9722
+ } else {
9723
+ ctx.lineTo(target.x, previous.y);
9724
+ }
9725
+ ctx.lineTo(target.x, target.y);
9726
+ return;
9727
+ }
9728
+
9729
+ if (!target.tension) {
9730
+ ctx.lineTo(target.x, target.y);
9731
+ return;
9732
+ }
9733
+
9734
+ ctx.bezierCurveTo(
9735
+ flip ? previous.controlPointPreviousX : previous.controlPointNextX,
9736
+ flip ? previous.controlPointPreviousY : previous.controlPointNextY,
9737
+ flip ? target.controlPointNextX : target.controlPointPreviousX,
9738
+ flip ? target.controlPointNextY : target.controlPointPreviousY,
9739
+ target.x,
9740
+ target.y);
9741
+ }
9742
+ };
9743
+
9744
+ // DEPRECATIONS
9745
+
9746
+ /**
9747
+ * Provided for backward compatibility, use Chart.helpers.canvas.clear instead.
9748
+ * @namespace Chart.helpers.clear
9749
+ * @deprecated since version 2.7.0
9750
+ * @todo remove at version 3
9751
+ * @private
9752
+ */
9753
+ helpers.clear = exports.clear;
9754
+
9755
+ /**
9756
+ * Provided for backward compatibility, use Chart.helpers.canvas.roundedRect instead.
9757
+ * @namespace Chart.helpers.drawRoundedRectangle
9758
+ * @deprecated since version 2.7.0
9759
+ * @todo remove at version 3
9760
+ * @private
9761
+ */
9762
+ helpers.drawRoundedRectangle = function(ctx) {
9763
+ ctx.beginPath();
9764
+ exports.roundedRect.apply(exports, arguments);
9765
+ ctx.closePath();
9766
+ };
9767
+
9768
+ },{"42":42}],42:[function(require,module,exports){
9769
+ 'use strict';
9770
+
9771
+ /**
9772
+ * @namespace Chart.helpers
9773
+ */
9774
+ var helpers = {
9775
+ /**
9776
+ * An empty function that can be used, for example, for optional callback.
9777
+ */
9778
+ noop: function() {},
9779
+
9780
+ /**
9781
+ * Returns a unique id, sequentially generated from a global variable.
9782
+ * @returns {Number}
9783
+ * @function
9784
+ */
9785
+ uid: (function() {
9786
+ var id = 0;
9787
+ return function() {
9788
+ return id++;
9789
+ };
9790
+ }()),
9791
+
9792
+ /**
9793
+ * Returns true if `value` is neither null nor undefined, else returns false.
9794
+ * @param {*} value - The value to test.
9795
+ * @returns {Boolean}
9796
+ * @since 2.7.0
9797
+ */
9798
+ isNullOrUndef: function(value) {
9799
+ return value === null || typeof value === 'undefined';
9800
+ },
9801
+
9802
+ /**
9803
+ * Returns true if `value` is an array, else returns false.
9804
+ * @param {*} value - The value to test.
9805
+ * @returns {Boolean}
9806
+ * @function
9807
+ */
9808
+ isArray: Array.isArray ? Array.isArray : function(value) {
9809
+ return Object.prototype.toString.call(value) === '[object Array]';
9810
+ },
9811
+
9812
+ /**
9813
+ * Returns true if `value` is an object (excluding null), else returns false.
9814
+ * @param {*} value - The value to test.
9815
+ * @returns {Boolean}
9816
+ * @since 2.7.0
9817
+ */
9818
+ isObject: function(value) {
9819
+ return value !== null && Object.prototype.toString.call(value) === '[object Object]';
9820
+ },
9821
+
9822
+ /**
9823
+ * Returns `value` if defined, else returns `defaultValue`.
9824
+ * @param {*} value - The value to return if defined.
9825
+ * @param {*} defaultValue - The value to return if `value` is undefined.
9826
+ * @returns {*}
9827
+ */
9828
+ valueOrDefault: function(value, defaultValue) {
9829
+ return typeof value === 'undefined' ? defaultValue : value;
9830
+ },
9831
+
9832
+ /**
9833
+ * Returns value at the given `index` in array if defined, else returns `defaultValue`.
9834
+ * @param {Array} value - The array to lookup for value at `index`.
9835
+ * @param {Number} index - The index in `value` to lookup for value.
9836
+ * @param {*} defaultValue - The value to return if `value[index]` is undefined.
9837
+ * @returns {*}
9838
+ */
9839
+ valueAtIndexOrDefault: function(value, index, defaultValue) {
9840
+ return helpers.valueOrDefault(helpers.isArray(value) ? value[index] : value, defaultValue);
9841
+ },
9842
+
9843
+ /**
9844
+ * Calls `fn` with the given `args` in the scope defined by `thisArg` and returns the
9845
+ * value returned by `fn`. If `fn` is not a function, this method returns undefined.
9846
+ * @param {Function} fn - The function to call.
9847
+ * @param {Array|undefined|null} args - The arguments with which `fn` should be called.
9848
+ * @param {Object} [thisArg] - The value of `this` provided for the call to `fn`.
9849
+ * @returns {*}
9850
+ */
9851
+ callback: function(fn, args, thisArg) {
9852
+ if (fn && typeof fn.call === 'function') {
9853
+ return fn.apply(thisArg, args);
9854
+ }
9855
+ },
9856
+
9857
+ /**
9858
+ * Note(SB) for performance sake, this method should only be used when loopable type
9859
+ * is unknown or in none intensive code (not called often and small loopable). Else
9860
+ * it's preferable to use a regular for() loop and save extra function calls.
9861
+ * @param {Object|Array} loopable - The object or array to be iterated.
9862
+ * @param {Function} fn - The function to call for each item.
9863
+ * @param {Object} [thisArg] - The value of `this` provided for the call to `fn`.
9864
+ * @param {Boolean} [reverse] - If true, iterates backward on the loopable.
9865
+ */
9866
+ each: function(loopable, fn, thisArg, reverse) {
9867
+ var i, len, keys;
9868
+ if (helpers.isArray(loopable)) {
9869
+ len = loopable.length;
9870
+ if (reverse) {
9871
+ for (i = len - 1; i >= 0; i--) {
9872
+ fn.call(thisArg, loopable[i], i);
9873
+ }
9874
+ } else {
9875
+ for (i = 0; i < len; i++) {
9876
+ fn.call(thisArg, loopable[i], i);
9877
+ }
9878
+ }
9879
+ } else if (helpers.isObject(loopable)) {
9880
+ keys = Object.keys(loopable);
9881
+ len = keys.length;
9882
+ for (i = 0; i < len; i++) {
9883
+ fn.call(thisArg, loopable[keys[i]], keys[i]);
9884
+ }
9885
+ }
9886
+ },
9887
+
9888
+ /**
9889
+ * Returns true if the `a0` and `a1` arrays have the same content, else returns false.
9890
+ * @see http://stackoverflow.com/a/14853974
9891
+ * @param {Array} a0 - The array to compare
9892
+ * @param {Array} a1 - The array to compare
9893
+ * @returns {Boolean}
9894
+ */
9895
+ arrayEquals: function(a0, a1) {
9896
+ var i, ilen, v0, v1;
9897
+
9898
+ if (!a0 || !a1 || a0.length !== a1.length) {
9899
+ return false;
9900
+ }
9901
+
9902
+ for (i = 0, ilen = a0.length; i < ilen; ++i) {
9903
+ v0 = a0[i];
9904
+ v1 = a1[i];
9905
+
9906
+ if (v0 instanceof Array && v1 instanceof Array) {
9907
+ if (!helpers.arrayEquals(v0, v1)) {
9908
+ return false;
9909
+ }
9910
+ } else if (v0 !== v1) {
9911
+ // NOTE: two different object instances will never be equal: {x:20} != {x:20}
9912
+ return false;
9913
+ }
9914
+ }
9915
+
9916
+ return true;
9917
+ },
9918
+
9919
+ /**
9920
+ * Returns a deep copy of `source` without keeping references on objects and arrays.
9921
+ * @param {*} source - The value to clone.
9922
+ * @returns {*}
9923
+ */
9924
+ clone: function(source) {
9925
+ if (helpers.isArray(source)) {
9926
+ return source.map(helpers.clone);
9927
+ }
9928
+
9929
+ if (helpers.isObject(source)) {
9930
+ var target = {};
9931
+ var keys = Object.keys(source);
9932
+ var klen = keys.length;
9933
+ var k = 0;
9934
+
9935
+ for (; k < klen; ++k) {
9936
+ target[keys[k]] = helpers.clone(source[keys[k]]);
9937
+ }
9938
+
9939
+ return target;
9940
+ }
9941
+
9942
+ return source;
9943
+ },
9944
+
9945
+ /**
9946
+ * The default merger when Chart.helpers.merge is called without merger option.
9947
+ * Note(SB): this method is also used by configMerge and scaleMerge as fallback.
9948
+ * @private
9949
+ */
9950
+ _merger: function(key, target, source, options) {
9951
+ var tval = target[key];
9952
+ var sval = source[key];
9953
+
9954
+ if (helpers.isObject(tval) && helpers.isObject(sval)) {
9955
+ helpers.merge(tval, sval, options);
9956
+ } else {
9957
+ target[key] = helpers.clone(sval);
9958
+ }
9959
+ },
9960
+
9961
+ /**
9962
+ * Merges source[key] in target[key] only if target[key] is undefined.
9963
+ * @private
9964
+ */
9965
+ _mergerIf: function(key, target, source) {
9966
+ var tval = target[key];
9967
+ var sval = source[key];
9968
+
9969
+ if (helpers.isObject(tval) && helpers.isObject(sval)) {
9970
+ helpers.mergeIf(tval, sval);
9971
+ } else if (!target.hasOwnProperty(key)) {
9972
+ target[key] = helpers.clone(sval);
9973
+ }
9974
+ },
9975
+
9976
+ /**
9977
+ * Recursively deep copies `source` properties into `target` with the given `options`.
9978
+ * IMPORTANT: `target` is not cloned and will be updated with `source` properties.
9979
+ * @param {Object} target - The target object in which all sources are merged into.
9980
+ * @param {Object|Array(Object)} source - Object(s) to merge into `target`.
9981
+ * @param {Object} [options] - Merging options:
9982
+ * @param {Function} [options.merger] - The merge method (key, target, source, options)
9983
+ * @returns {Object} The `target` object.
9984
+ */
9985
+ merge: function(target, source, options) {
9986
+ var sources = helpers.isArray(source) ? source : [source];
9987
+ var ilen = sources.length;
9988
+ var merge, i, keys, klen, k;
9989
+
9990
+ if (!helpers.isObject(target)) {
9991
+ return target;
9992
+ }
9993
+
9994
+ options = options || {};
9995
+ merge = options.merger || helpers._merger;
9996
+
9997
+ for (i = 0; i < ilen; ++i) {
9998
+ source = sources[i];
9999
+ if (!helpers.isObject(source)) {
10000
+ continue;
10001
+ }
10002
+
10003
+ keys = Object.keys(source);
10004
+ for (k = 0, klen = keys.length; k < klen; ++k) {
10005
+ merge(keys[k], target, source, options);
10006
+ }
10007
+ }
10008
+
10009
+ return target;
10010
+ },
10011
+
10012
+ /**
10013
+ * Recursively deep copies `source` properties into `target` *only* if not defined in target.
10014
+ * IMPORTANT: `target` is not cloned and will be updated with `source` properties.
10015
+ * @param {Object} target - The target object in which all sources are merged into.
10016
+ * @param {Object|Array(Object)} source - Object(s) to merge into `target`.
10017
+ * @returns {Object} The `target` object.
10018
+ */
10019
+ mergeIf: function(target, source) {
10020
+ return helpers.merge(target, source, {merger: helpers._mergerIf});
10021
+ },
10022
+
10023
+ /**
10024
+ * Applies the contents of two or more objects together into the first object.
10025
+ * @param {Object} target - The target object in which all objects are merged into.
10026
+ * @param {Object} arg1 - Object containing additional properties to merge in target.
10027
+ * @param {Object} argN - Additional objects containing properties to merge in target.
10028
+ * @returns {Object} The `target` object.
10029
+ */
10030
+ extend: function(target) {
10031
+ var setFn = function(value, key) {
10032
+ target[key] = value;
10033
+ };
10034
+ for (var i = 1, ilen = arguments.length; i < ilen; ++i) {
10035
+ helpers.each(arguments[i], setFn);
10036
+ }
10037
+ return target;
10038
+ },
10039
+
10040
+ /**
10041
+ * Basic javascript inheritance based on the model created in Backbone.js
10042
+ */
10043
+ inherits: function(extensions) {
10044
+ var me = this;
10045
+ var ChartElement = (extensions && extensions.hasOwnProperty('constructor')) ? extensions.constructor : function() {
10046
+ return me.apply(this, arguments);
10047
+ };
10048
+
10049
+ var Surrogate = function() {
10050
+ this.constructor = ChartElement;
10051
+ };
10052
+
10053
+ Surrogate.prototype = me.prototype;
10054
+ ChartElement.prototype = new Surrogate();
10055
+ ChartElement.extend = helpers.inherits;
10056
+
10057
+ if (extensions) {
10058
+ helpers.extend(ChartElement.prototype, extensions);
10059
+ }
10060
+
10061
+ ChartElement.__super__ = me.prototype;
10062
+ return ChartElement;
10063
+ }
10064
+ };
10065
+
10066
+ module.exports = helpers;
10067
+
10068
+ // DEPRECATIONS
10069
+
10070
+ /**
10071
+ * Provided for backward compatibility, use Chart.helpers.callback instead.
10072
+ * @function Chart.helpers.callCallback
10073
+ * @deprecated since version 2.6.0
10074
+ * @todo remove at version 3
10075
+ * @private
10076
+ */
10077
+ helpers.callCallback = helpers.callback;
10078
+
10079
+ /**
10080
+ * Provided for backward compatibility, use Array.prototype.indexOf instead.
10081
+ * Array.prototype.indexOf compatibility: Chrome, Opera, Safari, FF1.5+, IE9+
10082
+ * @function Chart.helpers.indexOf
10083
+ * @deprecated since version 2.7.0
10084
+ * @todo remove at version 3
10085
+ * @private
10086
+ */
10087
+ helpers.indexOf = function(array, item, fromIndex) {
10088
+ return Array.prototype.indexOf.call(array, item, fromIndex);
10089
+ };
10090
+
10091
+ /**
10092
+ * Provided for backward compatibility, use Chart.helpers.valueOrDefault instead.
10093
+ * @function Chart.helpers.getValueOrDefault
10094
+ * @deprecated since version 2.7.0
10095
+ * @todo remove at version 3
10096
+ * @private
10097
+ */
10098
+ helpers.getValueOrDefault = helpers.valueOrDefault;
10099
+
10100
+ /**
10101
+ * Provided for backward compatibility, use Chart.helpers.valueAtIndexOrDefault instead.
10102
+ * @function Chart.helpers.getValueAtIndexOrDefault
10103
+ * @deprecated since version 2.7.0
10104
+ * @todo remove at version 3
10105
+ * @private
10106
+ */
10107
+ helpers.getValueAtIndexOrDefault = helpers.valueAtIndexOrDefault;
10108
+
10109
+ },{}],43:[function(require,module,exports){
10110
+ 'use strict';
10111
+
10112
+ var helpers = require(42);
10113
+
10114
+ /**
10115
+ * Easing functions adapted from Robert Penner's easing equations.
10116
+ * @namespace Chart.helpers.easingEffects
10117
+ * @see http://www.robertpenner.com/easing/
10118
+ */
10119
+ var effects = {
10120
+ linear: function(t) {
10121
+ return t;
10122
+ },
10123
+
10124
+ easeInQuad: function(t) {
10125
+ return t * t;
10126
+ },
10127
+
10128
+ easeOutQuad: function(t) {
10129
+ return -t * (t - 2);
10130
+ },
10131
+
10132
+ easeInOutQuad: function(t) {
10133
+ if ((t /= 0.5) < 1) {
10134
+ return 0.5 * t * t;
10135
+ }
10136
+ return -0.5 * ((--t) * (t - 2) - 1);
10137
+ },
10138
+
10139
+ easeInCubic: function(t) {
10140
+ return t * t * t;
10141
+ },
10142
+
10143
+ easeOutCubic: function(t) {
10144
+ return (t = t - 1) * t * t + 1;
10145
+ },
10146
+
10147
+ easeInOutCubic: function(t) {
10148
+ if ((t /= 0.5) < 1) {
10149
+ return 0.5 * t * t * t;
10150
+ }
10151
+ return 0.5 * ((t -= 2) * t * t + 2);
10152
+ },
10153
+
10154
+ easeInQuart: function(t) {
10155
+ return t * t * t * t;
10156
+ },
10157
+
10158
+ easeOutQuart: function(t) {
10159
+ return -((t = t - 1) * t * t * t - 1);
10160
+ },
10161
+
10162
+ easeInOutQuart: function(t) {
10163
+ if ((t /= 0.5) < 1) {
10164
+ return 0.5 * t * t * t * t;
10165
+ }
10166
+ return -0.5 * ((t -= 2) * t * t * t - 2);
10167
+ },
10168
+
10169
+ easeInQuint: function(t) {
10170
+ return t * t * t * t * t;
10171
+ },
10172
+
10173
+ easeOutQuint: function(t) {
10174
+ return (t = t - 1) * t * t * t * t + 1;
10175
+ },
10176
+
10177
+ easeInOutQuint: function(t) {
10178
+ if ((t /= 0.5) < 1) {
10179
+ return 0.5 * t * t * t * t * t;
10180
+ }
10181
+ return 0.5 * ((t -= 2) * t * t * t * t + 2);
10182
+ },
10183
+
10184
+ easeInSine: function(t) {
10185
+ return -Math.cos(t * (Math.PI / 2)) + 1;
10186
+ },
10187
+
10188
+ easeOutSine: function(t) {
10189
+ return Math.sin(t * (Math.PI / 2));
10190
+ },
10191
+
10192
+ easeInOutSine: function(t) {
10193
+ return -0.5 * (Math.cos(Math.PI * t) - 1);
10194
+ },
10195
+
10196
+ easeInExpo: function(t) {
10197
+ return (t === 0) ? 0 : Math.pow(2, 10 * (t - 1));
10198
+ },
10199
+
10200
+ easeOutExpo: function(t) {
10201
+ return (t === 1) ? 1 : -Math.pow(2, -10 * t) + 1;
10202
+ },
10203
+
10204
+ easeInOutExpo: function(t) {
10205
+ if (t === 0) {
10206
+ return 0;
10207
+ }
10208
+ if (t === 1) {
10209
+ return 1;
10210
+ }
10211
+ if ((t /= 0.5) < 1) {
10212
+ return 0.5 * Math.pow(2, 10 * (t - 1));
10213
+ }
10214
+ return 0.5 * (-Math.pow(2, -10 * --t) + 2);
10215
+ },
10216
+
10217
+ easeInCirc: function(t) {
10218
+ if (t >= 1) {
10219
+ return t;
10220
+ }
10221
+ return -(Math.sqrt(1 - t * t) - 1);
10222
+ },
10223
+
10224
+ easeOutCirc: function(t) {
10225
+ return Math.sqrt(1 - (t = t - 1) * t);
10226
+ },
10227
+
10228
+ easeInOutCirc: function(t) {
10229
+ if ((t /= 0.5) < 1) {
10230
+ return -0.5 * (Math.sqrt(1 - t * t) - 1);
10231
+ }
10232
+ return 0.5 * (Math.sqrt(1 - (t -= 2) * t) + 1);
10233
+ },
10234
+
10235
+ easeInElastic: function(t) {
10236
+ var s = 1.70158;
10237
+ var p = 0;
10238
+ var a = 1;
10239
+ if (t === 0) {
10240
+ return 0;
10241
+ }
10242
+ if (t === 1) {
10243
+ return 1;
10244
+ }
10245
+ if (!p) {
10246
+ p = 0.3;
10247
+ }
10248
+ if (a < 1) {
10249
+ a = 1;
10250
+ s = p / 4;
10251
+ } else {
10252
+ s = p / (2 * Math.PI) * Math.asin(1 / a);
10253
+ }
10254
+ return -(a * Math.pow(2, 10 * (t -= 1)) * Math.sin((t - s) * (2 * Math.PI) / p));
10255
+ },
10256
+
10257
+ easeOutElastic: function(t) {
10258
+ var s = 1.70158;
10259
+ var p = 0;
10260
+ var a = 1;
10261
+ if (t === 0) {
10262
+ return 0;
10263
+ }
10264
+ if (t === 1) {
10265
+ return 1;
10266
+ }
10267
+ if (!p) {
10268
+ p = 0.3;
10269
+ }
10270
+ if (a < 1) {
10271
+ a = 1;
10272
+ s = p / 4;
10273
+ } else {
10274
+ s = p / (2 * Math.PI) * Math.asin(1 / a);
10275
+ }
10276
+ return a * Math.pow(2, -10 * t) * Math.sin((t - s) * (2 * Math.PI) / p) + 1;
10277
+ },
10278
+
10279
+ easeInOutElastic: function(t) {
10280
+ var s = 1.70158;
10281
+ var p = 0;
10282
+ var a = 1;
10283
+ if (t === 0) {
10284
+ return 0;
10285
+ }
10286
+ if ((t /= 0.5) === 2) {
10287
+ return 1;
10288
+ }
10289
+ if (!p) {
10290
+ p = 0.45;
10291
+ }
10292
+ if (a < 1) {
10293
+ a = 1;
10294
+ s = p / 4;
10295
+ } else {
10296
+ s = p / (2 * Math.PI) * Math.asin(1 / a);
10297
+ }
10298
+ if (t < 1) {
10299
+ return -0.5 * (a * Math.pow(2, 10 * (t -= 1)) * Math.sin((t - s) * (2 * Math.PI) / p));
10300
+ }
10301
+ return a * Math.pow(2, -10 * (t -= 1)) * Math.sin((t - s) * (2 * Math.PI) / p) * 0.5 + 1;
10302
+ },
10303
+ easeInBack: function(t) {
10304
+ var s = 1.70158;
10305
+ return t * t * ((s + 1) * t - s);
10306
+ },
10307
+
10308
+ easeOutBack: function(t) {
10309
+ var s = 1.70158;
10310
+ return (t = t - 1) * t * ((s + 1) * t + s) + 1;
10311
+ },
10312
+
10313
+ easeInOutBack: function(t) {
10314
+ var s = 1.70158;
10315
+ if ((t /= 0.5) < 1) {
10316
+ return 0.5 * (t * t * (((s *= (1.525)) + 1) * t - s));
10317
+ }
10318
+ return 0.5 * ((t -= 2) * t * (((s *= (1.525)) + 1) * t + s) + 2);
10319
+ },
10320
+
10321
+ easeInBounce: function(t) {
10322
+ return 1 - effects.easeOutBounce(1 - t);
10323
+ },
10324
+
10325
+ easeOutBounce: function(t) {
10326
+ if (t < (1 / 2.75)) {
10327
+ return 7.5625 * t * t;
10328
+ }
10329
+ if (t < (2 / 2.75)) {
10330
+ return 7.5625 * (t -= (1.5 / 2.75)) * t + 0.75;
10331
+ }
10332
+ if (t < (2.5 / 2.75)) {
10333
+ return 7.5625 * (t -= (2.25 / 2.75)) * t + 0.9375;
10334
+ }
10335
+ return 7.5625 * (t -= (2.625 / 2.75)) * t + 0.984375;
10336
+ },
10337
+
10338
+ easeInOutBounce: function(t) {
10339
+ if (t < 0.5) {
10340
+ return effects.easeInBounce(t * 2) * 0.5;
10341
+ }
10342
+ return effects.easeOutBounce(t * 2 - 1) * 0.5 + 0.5;
10343
+ }
10344
+ };
10345
+
10346
+ module.exports = {
10347
+ effects: effects
10348
+ };
10349
+
10350
+ // DEPRECATIONS
10351
+
10352
+ /**
10353
+ * Provided for backward compatibility, use Chart.helpers.easing.effects instead.
10354
+ * @function Chart.helpers.easingEffects
10355
+ * @deprecated since version 2.7.0
10356
+ * @todo remove at version 3
10357
+ * @private
10358
+ */
10359
+ helpers.easingEffects = effects;
10360
+
10361
+ },{"42":42}],44:[function(require,module,exports){
10362
+ 'use strict';
10363
+
10364
+ var helpers = require(42);
10365
+
10366
+ /**
10367
+ * @alias Chart.helpers.options
10368
+ * @namespace
10369
+ */
10370
+ module.exports = {
10371
+ /**
10372
+ * Converts the given line height `value` in pixels for a specific font `size`.
10373
+ * @param {Number|String} value - The lineHeight to parse (eg. 1.6, '14px', '75%', '1.6em').
10374
+ * @param {Number} size - The font size (in pixels) used to resolve relative `value`.
10375
+ * @returns {Number} The effective line height in pixels (size * 1.2 if value is invalid).
10376
+ * @see https://developer.mozilla.org/en-US/docs/Web/CSS/line-height
10377
+ * @since 2.7.0
10378
+ */
10379
+ toLineHeight: function(value, size) {
10380
+ var matches = ('' + value).match(/^(normal|(\d+(?:\.\d+)?)(px|em|%)?)$/);
10381
+ if (!matches || matches[1] === 'normal') {
10382
+ return size * 1.2;
10383
+ }
10384
+
10385
+ value = +matches[2];
10386
+
10387
+ switch (matches[3]) {
10388
+ case 'px':
10389
+ return value;
10390
+ case '%':
10391
+ value /= 100;
10392
+ break;
10393
+ default:
10394
+ break;
10395
+ }
10396
+
10397
+ return size * value;
10398
+ },
10399
+
10400
+ /**
10401
+ * Converts the given value into a padding object with pre-computed width/height.
10402
+ * @param {Number|Object} value - If a number, set the value to all TRBL component,
10403
+ * else, if and object, use defined properties and sets undefined ones to 0.
10404
+ * @returns {Object} The padding values (top, right, bottom, left, width, height)
10405
+ * @since 2.7.0
10406
+ */
10407
+ toPadding: function(value) {
10408
+ var t, r, b, l;
10409
+
10410
+ if (helpers.isObject(value)) {
10411
+ t = +value.top || 0;
10412
+ r = +value.right || 0;
10413
+ b = +value.bottom || 0;
10414
+ l = +value.left || 0;
10415
+ } else {
10416
+ t = r = b = l = +value || 0;
10417
+ }
10418
+
10419
+ return {
10420
+ top: t,
10421
+ right: r,
10422
+ bottom: b,
10423
+ left: l,
10424
+ height: t + b,
10425
+ width: l + r
10426
+ };
10427
+ },
10428
+
10429
+ /**
10430
+ * Evaluates the given `inputs` sequentially and returns the first defined value.
10431
+ * @param {Array[]} inputs - An array of values, falling back to the last value.
10432
+ * @param {Object} [context] - If defined and the current value is a function, the value
10433
+ * is called with `context` as first argument and the result becomes the new input.
10434
+ * @param {Number} [index] - If defined and the current value is an array, the value
10435
+ * at `index` become the new input.
10436
+ * @since 2.7.0
10437
+ */
10438
+ resolve: function(inputs, context, index) {
10439
+ var i, ilen, value;
10440
+
10441
+ for (i = 0, ilen = inputs.length; i < ilen; ++i) {
10442
+ value = inputs[i];
10443
+ if (value === undefined) {
10444
+ continue;
10445
+ }
10446
+ if (context !== undefined && typeof value === 'function') {
10447
+ value = value(context);
10448
+ }
10449
+ if (index !== undefined && helpers.isArray(value)) {
10450
+ value = value[index];
10451
+ }
10452
+ if (value !== undefined) {
10453
+ return value;
10454
+ }
10455
+ }
10456
+ }
10457
+ };
10458
+
10459
+ },{"42":42}],45:[function(require,module,exports){
10460
+ 'use strict';
10461
+
10462
+ module.exports = require(42);
10463
+ module.exports.easing = require(43);
10464
+ module.exports.canvas = require(41);
10465
+ module.exports.options = require(44);
10466
+
10467
+ },{"41":41,"42":42,"43":43,"44":44}],46:[function(require,module,exports){
10468
+ /**
10469
+ * Platform fallback implementation (minimal).
10470
+ * @see https://github.com/chartjs/Chart.js/pull/4591#issuecomment-319575939
10471
+ */
10472
+
10473
+ module.exports = {
10474
+ acquireContext: function(item) {
10475
+ if (item && item.canvas) {
10476
+ // Support for any object associated to a canvas (including a context2d)
10477
+ item = item.canvas;
10478
+ }
10479
+
10480
+ return item && item.getContext('2d') || null;
10481
+ }
10482
+ };
10483
+
10484
+ },{}],47:[function(require,module,exports){
10485
+ /**
10486
+ * Chart.Platform implementation for targeting a web browser
10487
+ */
10488
+
10489
+ 'use strict';
10490
+
10491
+ var helpers = require(45);
10492
+
10493
+ var EXPANDO_KEY = '$chartjs';
10494
+ var CSS_PREFIX = 'chartjs-';
10495
+ var CSS_RENDER_MONITOR = CSS_PREFIX + 'render-monitor';
10496
+ var CSS_RENDER_ANIMATION = CSS_PREFIX + 'render-animation';
10497
+ var ANIMATION_START_EVENTS = ['animationstart', 'webkitAnimationStart'];
10498
+
10499
+ /**
10500
+ * DOM event types -> Chart.js event types.
10501
+ * Note: only events with different types are mapped.
10502
+ * @see https://developer.mozilla.org/en-US/docs/Web/Events
10503
+ */
10504
+ var EVENT_TYPES = {
10505
+ touchstart: 'mousedown',
10506
+ touchmove: 'mousemove',
10507
+ touchend: 'mouseup',
10508
+ pointerenter: 'mouseenter',
10509
+ pointerdown: 'mousedown',
10510
+ pointermove: 'mousemove',
10511
+ pointerup: 'mouseup',
10512
+ pointerleave: 'mouseout',
10513
+ pointerout: 'mouseout'
10514
+ };
10515
+
10516
+ /**
10517
+ * The "used" size is the final value of a dimension property after all calculations have
10518
+ * been performed. This method uses the computed style of `element` but returns undefined
10519
+ * if the computed style is not expressed in pixels. That can happen in some cases where
10520
+ * `element` has a size relative to its parent and this last one is not yet displayed,
10521
+ * for example because of `display: none` on a parent node.
10522
+ * @see https://developer.mozilla.org/en-US/docs/Web/CSS/used_value
10523
+ * @returns {Number} Size in pixels or undefined if unknown.
10524
+ */
10525
+ function readUsedSize(element, property) {
10526
+ var value = helpers.getStyle(element, property);
10527
+ var matches = value && value.match(/^(\d+)(\.\d+)?px$/);
10528
+ return matches ? Number(matches[1]) : undefined;
10529
+ }
10530
+
10531
+ /**
10532
+ * Initializes the canvas style and render size without modifying the canvas display size,
10533
+ * since responsiveness is handled by the controller.resize() method. The config is used
10534
+ * to determine the aspect ratio to apply in case no explicit height has been specified.
10535
+ */
10536
+ function initCanvas(canvas, config) {
10537
+ var style = canvas.style;
10538
+
10539
+ // NOTE(SB) canvas.getAttribute('width') !== canvas.width: in the first case it
10540
+ // returns null or '' if no explicit value has been set to the canvas attribute.
10541
+ var renderHeight = canvas.getAttribute('height');
10542
+ var renderWidth = canvas.getAttribute('width');
10543
+
10544
+ // Chart.js modifies some canvas values that we want to restore on destroy
10545
+ canvas[EXPANDO_KEY] = {
10546
+ initial: {
10547
+ height: renderHeight,
10548
+ width: renderWidth,
10549
+ style: {
10550
+ display: style.display,
10551
+ height: style.height,
10552
+ width: style.width
10553
+ }
10554
+ }
10555
+ };
10556
+
10557
+ // Force canvas to display as block to avoid extra space caused by inline
10558
+ // elements, which would interfere with the responsive resize process.
10559
+ // https://github.com/chartjs/Chart.js/issues/2538
10560
+ style.display = style.display || 'block';
10561
+
10562
+ if (renderWidth === null || renderWidth === '') {
10563
+ var displayWidth = readUsedSize(canvas, 'width');
10564
+ if (displayWidth !== undefined) {
10565
+ canvas.width = displayWidth;
10566
+ }
10567
+ }
10568
+
10569
+ if (renderHeight === null || renderHeight === '') {
10570
+ if (canvas.style.height === '') {
10571
+ // If no explicit render height and style height, let's apply the aspect ratio,
10572
+ // which one can be specified by the user but also by charts as default option
10573
+ // (i.e. options.aspectRatio). If not specified, use canvas aspect ratio of 2.
10574
+ canvas.height = canvas.width / (config.options.aspectRatio || 2);
10575
+ } else {
10576
+ var displayHeight = readUsedSize(canvas, 'height');
10577
+ if (displayWidth !== undefined) {
10578
+ canvas.height = displayHeight;
10579
+ }
10580
+ }
10581
+ }
10582
+
10583
+ return canvas;
10584
+ }
10585
+
10586
+ /**
10587
+ * Detects support for options object argument in addEventListener.
10588
+ * https://developer.mozilla.org/en-US/docs/Web/API/EventTarget/addEventListener#Safely_detecting_option_support
10589
+ * @private
10590
+ */
10591
+ var supportsEventListenerOptions = (function() {
10592
+ var supports = false;
10593
+ try {
10594
+ var options = Object.defineProperty({}, 'passive', {
10595
+ get: function() {
10596
+ supports = true;
10597
+ }
10598
+ });
10599
+ window.addEventListener('e', null, options);
10600
+ } catch (e) {
10601
+ // continue regardless of error
10602
+ }
10603
+ return supports;
10604
+ }());
10605
+
10606
+ // Default passive to true as expected by Chrome for 'touchstart' and 'touchend' events.
10607
+ // https://github.com/chartjs/Chart.js/issues/4287
10608
+ var eventListenerOptions = supportsEventListenerOptions ? {passive: true} : false;
10609
+
10610
+ function addEventListener(node, type, listener) {
10611
+ node.addEventListener(type, listener, eventListenerOptions);
10612
+ }
10613
+
10614
+ function removeEventListener(node, type, listener) {
10615
+ node.removeEventListener(type, listener, eventListenerOptions);
10616
+ }
10617
+
10618
+ function createEvent(type, chart, x, y, nativeEvent) {
10619
+ return {
10620
+ type: type,
10621
+ chart: chart,
10622
+ native: nativeEvent || null,
10623
+ x: x !== undefined ? x : null,
10624
+ y: y !== undefined ? y : null,
10625
+ };
10626
+ }
10627
+
10628
+ function fromNativeEvent(event, chart) {
10629
+ var type = EVENT_TYPES[event.type] || event.type;
10630
+ var pos = helpers.getRelativePosition(event, chart);
10631
+ return createEvent(type, chart, pos.x, pos.y, event);
10632
+ }
10633
+
10634
+ function throttled(fn, thisArg) {
10635
+ var ticking = false;
10636
+ var args = [];
10637
+
10638
+ return function() {
10639
+ args = Array.prototype.slice.call(arguments);
10640
+ thisArg = thisArg || this;
10641
+
10642
+ if (!ticking) {
10643
+ ticking = true;
10644
+ helpers.requestAnimFrame.call(window, function() {
10645
+ ticking = false;
10646
+ fn.apply(thisArg, args);
10647
+ });
10648
+ }
10649
+ };
10650
+ }
10651
+
10652
+ // Implementation based on https://github.com/marcj/css-element-queries
10653
+ function createResizer(handler) {
10654
+ var resizer = document.createElement('div');
10655
+ var cls = CSS_PREFIX + 'size-monitor';
10656
+ var maxSize = 1000000;
10657
+ var style =
10658
+ 'position:absolute;' +
10659
+ 'left:0;' +
10660
+ 'top:0;' +
10661
+ 'right:0;' +
10662
+ 'bottom:0;' +
10663
+ 'overflow:hidden;' +
10664
+ 'pointer-events:none;' +
10665
+ 'visibility:hidden;' +
10666
+ 'z-index:-1;';
10667
+
10668
+ resizer.style.cssText = style;
10669
+ resizer.className = cls;
10670
+ resizer.innerHTML =
10671
+ '<div class="' + cls + '-expand" style="' + style + '">' +
10672
+ '<div style="' +
10673
+ 'position:absolute;' +
10674
+ 'width:' + maxSize + 'px;' +
10675
+ 'height:' + maxSize + 'px;' +
10676
+ 'left:0;' +
10677
+ 'top:0">' +
10678
+ '</div>' +
10679
+ '</div>' +
10680
+ '<div class="' + cls + '-shrink" style="' + style + '">' +
10681
+ '<div style="' +
10682
+ 'position:absolute;' +
10683
+ 'width:200%;' +
10684
+ 'height:200%;' +
10685
+ 'left:0; ' +
10686
+ 'top:0">' +
10687
+ '</div>' +
10688
+ '</div>';
10689
+
10690
+ var expand = resizer.childNodes[0];
10691
+ var shrink = resizer.childNodes[1];
10692
+
10693
+ resizer._reset = function() {
10694
+ expand.scrollLeft = maxSize;
10695
+ expand.scrollTop = maxSize;
10696
+ shrink.scrollLeft = maxSize;
10697
+ shrink.scrollTop = maxSize;
10698
+ };
10699
+ var onScroll = function() {
10700
+ resizer._reset();
10701
+ handler();
10702
+ };
10703
+
10704
+ addEventListener(expand, 'scroll', onScroll.bind(expand, 'expand'));
10705
+ addEventListener(shrink, 'scroll', onScroll.bind(shrink, 'shrink'));
10706
+
10707
+ return resizer;
10708
+ }
10709
+
10710
+ // https://davidwalsh.name/detect-node-insertion
10711
+ function watchForRender(node, handler) {
10712
+ var expando = node[EXPANDO_KEY] || (node[EXPANDO_KEY] = {});
10713
+ var proxy = expando.renderProxy = function(e) {
10714
+ if (e.animationName === CSS_RENDER_ANIMATION) {
10715
+ handler();
10716
+ }
10717
+ };
10718
+
10719
+ helpers.each(ANIMATION_START_EVENTS, function(type) {
10720
+ addEventListener(node, type, proxy);
10721
+ });
10722
+
10723
+ // #4737: Chrome might skip the CSS animation when the CSS_RENDER_MONITOR class
10724
+ // is removed then added back immediately (same animation frame?). Accessing the
10725
+ // `offsetParent` property will force a reflow and re-evaluate the CSS animation.
10726
+ // https://gist.github.com/paulirish/5d52fb081b3570c81e3a#box-metrics
10727
+ // https://github.com/chartjs/Chart.js/issues/4737
10728
+ expando.reflow = !!node.offsetParent;
10729
+
10730
+ node.classList.add(CSS_RENDER_MONITOR);
10731
+ }
10732
+
10733
+ function unwatchForRender(node) {
10734
+ var expando = node[EXPANDO_KEY] || {};
10735
+ var proxy = expando.renderProxy;
10736
+
10737
+ if (proxy) {
10738
+ helpers.each(ANIMATION_START_EVENTS, function(type) {
10739
+ removeEventListener(node, type, proxy);
10740
+ });
10741
+
10742
+ delete expando.renderProxy;
10743
+ }
10744
+
10745
+ node.classList.remove(CSS_RENDER_MONITOR);
10746
+ }
10747
+
10748
+ function addResizeListener(node, listener, chart) {
10749
+ var expando = node[EXPANDO_KEY] || (node[EXPANDO_KEY] = {});
10750
+
10751
+ // Let's keep track of this added resizer and thus avoid DOM query when removing it.
10752
+ var resizer = expando.resizer = createResizer(throttled(function() {
10753
+ if (expando.resizer) {
10754
+ return listener(createEvent('resize', chart));
10755
+ }
10756
+ }));
10757
+
10758
+ // The resizer needs to be attached to the node parent, so we first need to be
10759
+ // sure that `node` is attached to the DOM before injecting the resizer element.
10760
+ watchForRender(node, function() {
10761
+ if (expando.resizer) {
10762
+ var container = node.parentNode;
10763
+ if (container && container !== resizer.parentNode) {
10764
+ container.insertBefore(resizer, container.firstChild);
10765
+ }
10766
+
10767
+ // The container size might have changed, let's reset the resizer state.
10768
+ resizer._reset();
10769
+ }
10770
+ });
10771
+ }
10772
+
10773
+ function removeResizeListener(node) {
10774
+ var expando = node[EXPANDO_KEY] || {};
10775
+ var resizer = expando.resizer;
10776
+
10777
+ delete expando.resizer;
10778
+ unwatchForRender(node);
10779
+
10780
+ if (resizer && resizer.parentNode) {
10781
+ resizer.parentNode.removeChild(resizer);
10782
+ }
10783
+ }
10784
+
10785
+ function injectCSS(platform, css) {
10786
+ // http://stackoverflow.com/q/3922139
10787
+ var style = platform._style || document.createElement('style');
10788
+ if (!platform._style) {
10789
+ platform._style = style;
10790
+ css = '/* Chart.js */\n' + css;
10791
+ style.setAttribute('type', 'text/css');
10792
+ document.getElementsByTagName('head')[0].appendChild(style);
10793
+ }
10794
+
10795
+ style.appendChild(document.createTextNode(css));
10796
+ }
10797
+
10798
+ module.exports = {
10799
+ /**
10800
+ * This property holds whether this platform is enabled for the current environment.
10801
+ * Currently used by platform.js to select the proper implementation.
10802
+ * @private
10803
+ */
10804
+ _enabled: typeof window !== 'undefined' && typeof document !== 'undefined',
10805
+
10806
+ initialize: function() {
10807
+ var keyframes = 'from{opacity:0.99}to{opacity:1}';
10808
+
10809
+ injectCSS(this,
10810
+ // DOM rendering detection
10811
+ // https://davidwalsh.name/detect-node-insertion
10812
+ '@-webkit-keyframes ' + CSS_RENDER_ANIMATION + '{' + keyframes + '}' +
10813
+ '@keyframes ' + CSS_RENDER_ANIMATION + '{' + keyframes + '}' +
10814
+ '.' + CSS_RENDER_MONITOR + '{' +
10815
+ '-webkit-animation:' + CSS_RENDER_ANIMATION + ' 0.001s;' +
10816
+ 'animation:' + CSS_RENDER_ANIMATION + ' 0.001s;' +
10817
+ '}'
10818
+ );
10819
+ },
10820
+
10821
+ acquireContext: function(item, config) {
10822
+ if (typeof item === 'string') {
10823
+ item = document.getElementById(item);
10824
+ } else if (item.length) {
10825
+ // Support for array based queries (such as jQuery)
10826
+ item = item[0];
10827
+ }
10828
+
10829
+ if (item && item.canvas) {
10830
+ // Support for any object associated to a canvas (including a context2d)
10831
+ item = item.canvas;
10832
+ }
10833
+
10834
+ // To prevent canvas fingerprinting, some add-ons undefine the getContext
10835
+ // method, for example: https://github.com/kkapsner/CanvasBlocker
10836
+ // https://github.com/chartjs/Chart.js/issues/2807
10837
+ var context = item && item.getContext && item.getContext('2d');
10838
+
10839
+ // `instanceof HTMLCanvasElement/CanvasRenderingContext2D` fails when the item is
10840
+ // inside an iframe or when running in a protected environment. We could guess the
10841
+ // types from their toString() value but let's keep things flexible and assume it's
10842
+ // a sufficient condition if the item has a context2D which has item as `canvas`.
10843
+ // https://github.com/chartjs/Chart.js/issues/3887
10844
+ // https://github.com/chartjs/Chart.js/issues/4102
10845
+ // https://github.com/chartjs/Chart.js/issues/4152
10846
+ if (context && context.canvas === item) {
10847
+ initCanvas(item, config);
10848
+ return context;
10849
+ }
10850
+
10851
+ return null;
10852
+ },
10853
+
10854
+ releaseContext: function(context) {
10855
+ var canvas = context.canvas;
10856
+ if (!canvas[EXPANDO_KEY]) {
10857
+ return;
10858
+ }
10859
+
10860
+ var initial = canvas[EXPANDO_KEY].initial;
10861
+ ['height', 'width'].forEach(function(prop) {
10862
+ var value = initial[prop];
10863
+ if (helpers.isNullOrUndef(value)) {
10864
+ canvas.removeAttribute(prop);
10865
+ } else {
10866
+ canvas.setAttribute(prop, value);
10867
+ }
10868
+ });
10869
+
10870
+ helpers.each(initial.style || {}, function(value, key) {
10871
+ canvas.style[key] = value;
10872
+ });
10873
+
10874
+ // The canvas render size might have been changed (and thus the state stack discarded),
10875
+ // we can't use save() and restore() to restore the initial state. So make sure that at
10876
+ // least the canvas context is reset to the default state by setting the canvas width.
10877
+ // https://www.w3.org/TR/2011/WD-html5-20110525/the-canvas-element.html
10878
+ canvas.width = canvas.width;
10879
+
10880
+ delete canvas[EXPANDO_KEY];
10881
+ },
10882
+
10883
+ addEventListener: function(chart, type, listener) {
10884
+ var canvas = chart.canvas;
10885
+ if (type === 'resize') {
10886
+ // Note: the resize event is not supported on all browsers.
10887
+ addResizeListener(canvas, listener, chart);
10888
+ return;
10889
+ }
10890
+
10891
+ var expando = listener[EXPANDO_KEY] || (listener[EXPANDO_KEY] = {});
10892
+ var proxies = expando.proxies || (expando.proxies = {});
10893
+ var proxy = proxies[chart.id + '_' + type] = function(event) {
10894
+ listener(fromNativeEvent(event, chart));
10895
+ };
10896
+
10897
+ addEventListener(canvas, type, proxy);
10898
+ },
10899
+
10900
+ removeEventListener: function(chart, type, listener) {
10901
+ var canvas = chart.canvas;
10902
+ if (type === 'resize') {
10903
+ // Note: the resize event is not supported on all browsers.
10904
+ removeResizeListener(canvas, listener);
10905
+ return;
10906
+ }
10907
+
10908
+ var expando = listener[EXPANDO_KEY] || {};
10909
+ var proxies = expando.proxies || {};
10910
+ var proxy = proxies[chart.id + '_' + type];
10911
+ if (!proxy) {
10912
+ return;
10913
+ }
10914
+
10915
+ removeEventListener(canvas, type, proxy);
10916
+ }
10917
+ };
10918
+
10919
+ // DEPRECATIONS
10920
+
10921
+ /**
10922
+ * Provided for backward compatibility, use EventTarget.addEventListener instead.
10923
+ * EventTarget.addEventListener compatibility: Chrome, Opera 7, Safari, FF1.5+, IE9+
10924
+ * @see https://developer.mozilla.org/en-US/docs/Web/API/EventTarget/addEventListener
10925
+ * @function Chart.helpers.addEvent
10926
+ * @deprecated since version 2.7.0
10927
+ * @todo remove at version 3
10928
+ * @private
10929
+ */
10930
+ helpers.addEvent = addEventListener;
10931
+
10932
+ /**
10933
+ * Provided for backward compatibility, use EventTarget.removeEventListener instead.
10934
+ * EventTarget.removeEventListener compatibility: Chrome, Opera 7, Safari, FF1.5+, IE9+
10935
+ * @see https://developer.mozilla.org/en-US/docs/Web/API/EventTarget/removeEventListener
10936
+ * @function Chart.helpers.removeEvent
10937
+ * @deprecated since version 2.7.0
10938
+ * @todo remove at version 3
10939
+ * @private
10940
+ */
10941
+ helpers.removeEvent = removeEventListener;
10942
+
10943
+ },{"45":45}],48:[function(require,module,exports){
10944
+ 'use strict';
10945
+
10946
+ var helpers = require(45);
10947
+ var basic = require(46);
10948
+ var dom = require(47);
10949
+
10950
+ // @TODO Make possible to select another platform at build time.
10951
+ var implementation = dom._enabled ? dom : basic;
10952
+
10953
+ /**
10954
+ * @namespace Chart.platform
10955
+ * @see https://chartjs.gitbooks.io/proposals/content/Platform.html
10956
+ * @since 2.4.0
10957
+ */
10958
+ module.exports = helpers.extend({
10959
+ /**
10960
+ * @since 2.7.0
10961
+ */
10962
+ initialize: function() {},
10963
+
10964
+ /**
10965
+ * Called at chart construction time, returns a context2d instance implementing
10966
+ * the [W3C Canvas 2D Context API standard]{@link https://www.w3.org/TR/2dcontext/}.
10967
+ * @param {*} item - The native item from which to acquire context (platform specific)
10968
+ * @param {Object} options - The chart options
10969
+ * @returns {CanvasRenderingContext2D} context2d instance
10970
+ */
10971
+ acquireContext: function() {},
10972
+
10973
+ /**
10974
+ * Called at chart destruction time, releases any resources associated to the context
10975
+ * previously returned by the acquireContext() method.
10976
+ * @param {CanvasRenderingContext2D} context - The context2d instance
10977
+ * @returns {Boolean} true if the method succeeded, else false
10978
+ */
10979
+ releaseContext: function() {},
10980
+
10981
+ /**
10982
+ * Registers the specified listener on the given chart.
10983
+ * @param {Chart} chart - Chart from which to listen for event
10984
+ * @param {String} type - The ({@link IEvent}) type to listen for
10985
+ * @param {Function} listener - Receives a notification (an object that implements
10986
+ * the {@link IEvent} interface) when an event of the specified type occurs.
10987
+ */
10988
+ addEventListener: function() {},
10989
+
10990
+ /**
10991
+ * Removes the specified listener previously registered with addEventListener.
10992
+ * @param {Chart} chart -Chart from which to remove the listener
10993
+ * @param {String} type - The ({@link IEvent}) type to remove
10994
+ * @param {Function} listener - The listener function to remove from the event target.
10995
+ */
10996
+ removeEventListener: function() {}
10997
+
10998
+ }, implementation);
10999
+
11000
+ /**
11001
+ * @interface IPlatform
11002
+ * Allows abstracting platform dependencies away from the chart
11003
+ * @borrows Chart.platform.acquireContext as acquireContext
11004
+ * @borrows Chart.platform.releaseContext as releaseContext
11005
+ * @borrows Chart.platform.addEventListener as addEventListener
11006
+ * @borrows Chart.platform.removeEventListener as removeEventListener
11007
+ */
11008
+
11009
+ /**
11010
+ * @interface IEvent
11011
+ * @prop {String} type - The event type name, possible values are:
11012
+ * 'contextmenu', 'mouseenter', 'mousedown', 'mousemove', 'mouseup', 'mouseout',
11013
+ * 'click', 'dblclick', 'keydown', 'keypress', 'keyup' and 'resize'
11014
+ * @prop {*} native - The original native event (null for emulated events, e.g. 'resize')
11015
+ * @prop {Number} x - The mouse x position, relative to the canvas (null for incompatible events)
11016
+ * @prop {Number} y - The mouse y position, relative to the canvas (null for incompatible events)
11017
+ */
11018
+
11019
+ },{"45":45,"46":46,"47":47}],49:[function(require,module,exports){
11020
+ /**
11021
+ * Plugin based on discussion from the following Chart.js issues:
11022
+ * @see https://github.com/chartjs/Chart.js/issues/2380#issuecomment-279961569
11023
+ * @see https://github.com/chartjs/Chart.js/issues/2440#issuecomment-256461897
11024
+ */
11025
+
11026
+ 'use strict';
11027
+
11028
+ var defaults = require(25);
11029
+ var elements = require(40);
11030
+ var helpers = require(45);
11031
+
11032
+ defaults._set('global', {
11033
+ plugins: {
11034
+ filler: {
11035
+ propagate: true
11036
+ }
11037
+ }
11038
+ });
11039
+
11040
+ module.exports = function() {
11041
+
11042
+ var mappers = {
11043
+ dataset: function(source) {
11044
+ var index = source.fill;
11045
+ var chart = source.chart;
11046
+ var meta = chart.getDatasetMeta(index);
11047
+ var visible = meta && chart.isDatasetVisible(index);
11048
+ var points = (visible && meta.dataset._children) || [];
11049
+ var length = points.length || 0;
11050
+
11051
+ return !length ? null : function(point, i) {
11052
+ return (i < length && points[i]._view) || null;
11053
+ };
11054
+ },
11055
+
11056
+ boundary: function(source) {
11057
+ var boundary = source.boundary;
11058
+ var x = boundary ? boundary.x : null;
11059
+ var y = boundary ? boundary.y : null;
11060
+
11061
+ return function(point) {
11062
+ return {
11063
+ x: x === null ? point.x : x,
11064
+ y: y === null ? point.y : y,
11065
+ };
11066
+ };
11067
+ }
11068
+ };
11069
+
11070
+ // @todo if (fill[0] === '#')
11071
+ function decodeFill(el, index, count) {
11072
+ var model = el._model || {};
11073
+ var fill = model.fill;
11074
+ var target;
11075
+
11076
+ if (fill === undefined) {
11077
+ fill = !!model.backgroundColor;
11078
+ }
11079
+
11080
+ if (fill === false || fill === null) {
11081
+ return false;
11082
+ }
11083
+
11084
+ if (fill === true) {
11085
+ return 'origin';
11086
+ }
11087
+
11088
+ target = parseFloat(fill, 10);
11089
+ if (isFinite(target) && Math.floor(target) === target) {
11090
+ if (fill[0] === '-' || fill[0] === '+') {
11091
+ target = index + target;
11092
+ }
11093
+
11094
+ if (target === index || target < 0 || target >= count) {
11095
+ return false;
11096
+ }
11097
+
11098
+ return target;
11099
+ }
11100
+
11101
+ switch (fill) {
11102
+ // compatibility
11103
+ case 'bottom':
11104
+ return 'start';
11105
+ case 'top':
11106
+ return 'end';
11107
+ case 'zero':
11108
+ return 'origin';
11109
+ // supported boundaries
11110
+ case 'origin':
11111
+ case 'start':
11112
+ case 'end':
11113
+ return fill;
11114
+ // invalid fill values
11115
+ default:
11116
+ return false;
11117
+ }
11118
+ }
11119
+
11120
+ function computeBoundary(source) {
11121
+ var model = source.el._model || {};
11122
+ var scale = source.el._scale || {};
11123
+ var fill = source.fill;
11124
+ var target = null;
11125
+ var horizontal;
11126
+
11127
+ if (isFinite(fill)) {
11128
+ return null;
11129
+ }
11130
+
11131
+ // Backward compatibility: until v3, we still need to support boundary values set on
11132
+ // the model (scaleTop, scaleBottom and scaleZero) because some external plugins and
11133
+ // controllers might still use it (e.g. the Smith chart).
11134
+
11135
+ if (fill === 'start') {
11136
+ target = model.scaleBottom === undefined ? scale.bottom : model.scaleBottom;
11137
+ } else if (fill === 'end') {
11138
+ target = model.scaleTop === undefined ? scale.top : model.scaleTop;
11139
+ } else if (model.scaleZero !== undefined) {
11140
+ target = model.scaleZero;
11141
+ } else if (scale.getBasePosition) {
11142
+ target = scale.getBasePosition();
11143
+ } else if (scale.getBasePixel) {
11144
+ target = scale.getBasePixel();
11145
+ }
11146
+
11147
+ if (target !== undefined && target !== null) {
11148
+ if (target.x !== undefined && target.y !== undefined) {
11149
+ return target;
11150
+ }
11151
+
11152
+ if (typeof target === 'number' && isFinite(target)) {
11153
+ horizontal = scale.isHorizontal();
11154
+ return {
11155
+ x: horizontal ? target : null,
11156
+ y: horizontal ? null : target
11157
+ };
11158
+ }
11159
+ }
11160
+
11161
+ return null;
11162
+ }
11163
+
11164
+ function resolveTarget(sources, index, propagate) {
11165
+ var source = sources[index];
11166
+ var fill = source.fill;
11167
+ var visited = [index];
11168
+ var target;
11169
+
11170
+ if (!propagate) {
11171
+ return fill;
11172
+ }
11173
+
11174
+ while (fill !== false && visited.indexOf(fill) === -1) {
11175
+ if (!isFinite(fill)) {
11176
+ return fill;
11177
+ }
11178
+
11179
+ target = sources[fill];
11180
+ if (!target) {
11181
+ return false;
11182
+ }
11183
+
11184
+ if (target.visible) {
11185
+ return fill;
11186
+ }
11187
+
11188
+ visited.push(fill);
11189
+ fill = target.fill;
11190
+ }
11191
+
11192
+ return false;
11193
+ }
11194
+
11195
+ function createMapper(source) {
11196
+ var fill = source.fill;
11197
+ var type = 'dataset';
11198
+
11199
+ if (fill === false) {
11200
+ return null;
11201
+ }
11202
+
11203
+ if (!isFinite(fill)) {
11204
+ type = 'boundary';
11205
+ }
11206
+
11207
+ return mappers[type](source);
11208
+ }
11209
+
11210
+ function isDrawable(point) {
11211
+ return point && !point.skip;
11212
+ }
11213
+
11214
+ function drawArea(ctx, curve0, curve1, len0, len1) {
11215
+ var i;
11216
+
11217
+ if (!len0 || !len1) {
11218
+ return;
11219
+ }
11220
+
11221
+ // building first area curve (normal)
11222
+ ctx.moveTo(curve0[0].x, curve0[0].y);
11223
+ for (i = 1; i < len0; ++i) {
11224
+ helpers.canvas.lineTo(ctx, curve0[i - 1], curve0[i]);
11225
+ }
11226
+
11227
+ // joining the two area curves
11228
+ ctx.lineTo(curve1[len1 - 1].x, curve1[len1 - 1].y);
11229
+
11230
+ // building opposite area curve (reverse)
11231
+ for (i = len1 - 1; i > 0; --i) {
11232
+ helpers.canvas.lineTo(ctx, curve1[i], curve1[i - 1], true);
11233
+ }
11234
+ }
11235
+
11236
+ function doFill(ctx, points, mapper, view, color, loop) {
11237
+ var count = points.length;
11238
+ var span = view.spanGaps;
11239
+ var curve0 = [];
11240
+ var curve1 = [];
11241
+ var len0 = 0;
11242
+ var len1 = 0;
11243
+ var i, ilen, index, p0, p1, d0, d1;
11244
+
11245
+ ctx.beginPath();
11246
+
11247
+ for (i = 0, ilen = (count + !!loop); i < ilen; ++i) {
11248
+ index = i % count;
11249
+ p0 = points[index]._view;
11250
+ p1 = mapper(p0, index, view);
11251
+ d0 = isDrawable(p0);
11252
+ d1 = isDrawable(p1);
11253
+
11254
+ if (d0 && d1) {
11255
+ len0 = curve0.push(p0);
11256
+ len1 = curve1.push(p1);
11257
+ } else if (len0 && len1) {
11258
+ if (!span) {
11259
+ drawArea(ctx, curve0, curve1, len0, len1);
11260
+ len0 = len1 = 0;
11261
+ curve0 = [];
11262
+ curve1 = [];
11263
+ } else {
11264
+ if (d0) {
11265
+ curve0.push(p0);
11266
+ }
11267
+ if (d1) {
11268
+ curve1.push(p1);
11269
+ }
11270
+ }
11271
+ }
11272
+ }
11273
+
11274
+ drawArea(ctx, curve0, curve1, len0, len1);
11275
+
11276
+ ctx.closePath();
11277
+ ctx.fillStyle = color;
11278
+ ctx.fill();
11279
+ }
11280
+
11281
+ return {
11282
+ id: 'filler',
11283
+
11284
+ afterDatasetsUpdate: function(chart, options) {
11285
+ var count = (chart.data.datasets || []).length;
11286
+ var propagate = options.propagate;
11287
+ var sources = [];
11288
+ var meta, i, el, source;
11289
+
11290
+ for (i = 0; i < count; ++i) {
11291
+ meta = chart.getDatasetMeta(i);
11292
+ el = meta.dataset;
11293
+ source = null;
11294
+
11295
+ if (el && el._model && el instanceof elements.Line) {
11296
+ source = {
11297
+ visible: chart.isDatasetVisible(i),
11298
+ fill: decodeFill(el, i, count),
11299
+ chart: chart,
11300
+ el: el
11301
+ };
11302
+ }
11303
+
11304
+ meta.$filler = source;
11305
+ sources.push(source);
11306
+ }
11307
+
11308
+ for (i = 0; i < count; ++i) {
11309
+ source = sources[i];
11310
+ if (!source) {
11311
+ continue;
11312
+ }
11313
+
11314
+ source.fill = resolveTarget(sources, i, propagate);
11315
+ source.boundary = computeBoundary(source);
11316
+ source.mapper = createMapper(source);
11317
+ }
11318
+ },
11319
+
11320
+ beforeDatasetDraw: function(chart, args) {
11321
+ var meta = args.meta.$filler;
11322
+ if (!meta) {
11323
+ return;
11324
+ }
11325
+
11326
+ var ctx = chart.ctx;
11327
+ var el = meta.el;
11328
+ var view = el._view;
11329
+ var points = el._children || [];
11330
+ var mapper = meta.mapper;
11331
+ var color = view.backgroundColor || defaults.global.defaultColor;
11332
+
11333
+ if (mapper && color && points.length) {
11334
+ helpers.canvas.clipArea(ctx, chart.chartArea);
11335
+ doFill(ctx, points, mapper, view, color, el._loop);
11336
+ helpers.canvas.unclipArea(ctx);
11337
+ }
11338
+ }
11339
+ };
11340
+ };
11341
+
11342
+ },{"25":25,"40":40,"45":45}],50:[function(require,module,exports){
11343
+ 'use strict';
11344
+
11345
+ var defaults = require(25);
11346
+ var Element = require(26);
11347
+ var helpers = require(45);
11348
+
11349
+ defaults._set('global', {
11350
+ legend: {
11351
+ display: true,
11352
+ position: 'top',
11353
+ fullWidth: true,
11354
+ reverse: false,
11355
+ weight: 1000,
11356
+
11357
+ // a callback that will handle
11358
+ onClick: function(e, legendItem) {
11359
+ var index = legendItem.datasetIndex;
11360
+ var ci = this.chart;
11361
+ var meta = ci.getDatasetMeta(index);
11362
+
11363
+ // See controller.isDatasetVisible comment
11364
+ meta.hidden = meta.hidden === null ? !ci.data.datasets[index].hidden : null;
11365
+
11366
+ // We hid a dataset ... rerender the chart
11367
+ ci.update();
11368
+ },
11369
+
11370
+ onHover: null,
11371
+
11372
+ labels: {
11373
+ boxWidth: 40,
11374
+ padding: 10,
11375
+ // Generates labels shown in the legend
11376
+ // Valid properties to return:
11377
+ // text : text to display
11378
+ // fillStyle : fill of coloured box
11379
+ // strokeStyle: stroke of coloured box
11380
+ // hidden : if this legend item refers to a hidden item
11381
+ // lineCap : cap style for line
11382
+ // lineDash
11383
+ // lineDashOffset :
11384
+ // lineJoin :
11385
+ // lineWidth :
11386
+ generateLabels: function(chart) {
11387
+ var data = chart.data;
11388
+ return helpers.isArray(data.datasets) ? data.datasets.map(function(dataset, i) {
11389
+ return {
11390
+ text: dataset.label,
11391
+ fillStyle: (!helpers.isArray(dataset.backgroundColor) ? dataset.backgroundColor : dataset.backgroundColor[0]),
11392
+ hidden: !chart.isDatasetVisible(i),
11393
+ lineCap: dataset.borderCapStyle,
11394
+ lineDash: dataset.borderDash,
11395
+ lineDashOffset: dataset.borderDashOffset,
11396
+ lineJoin: dataset.borderJoinStyle,
11397
+ lineWidth: dataset.borderWidth,
11398
+ strokeStyle: dataset.borderColor,
11399
+ pointStyle: dataset.pointStyle,
11400
+
11401
+ // Below is extra data used for toggling the datasets
11402
+ datasetIndex: i
11403
+ };
11404
+ }, this) : [];
11405
+ }
11406
+ }
11407
+ },
11408
+
11409
+ legendCallback: function(chart) {
11410
+ var text = [];
11411
+ text.push('<ul class="' + chart.id + '-legend">');
11412
+ for (var i = 0; i < chart.data.datasets.length; i++) {
11413
+ text.push('<li><span style="background-color:' + chart.data.datasets[i].backgroundColor + '"></span>');
11414
+ if (chart.data.datasets[i].label) {
11415
+ text.push(chart.data.datasets[i].label);
11416
+ }
11417
+ text.push('</li>');
11418
+ }
11419
+ text.push('</ul>');
11420
+ return text.join('');
11421
+ }
11422
+ });
11423
+
11424
+ module.exports = function(Chart) {
11425
+
11426
+ var layout = Chart.layoutService;
11427
+ var noop = helpers.noop;
11428
+
11429
+ /**
11430
+ * Helper function to get the box width based on the usePointStyle option
11431
+ * @param labelopts {Object} the label options on the legend
11432
+ * @param fontSize {Number} the label font size
11433
+ * @return {Number} width of the color box area
11434
+ */
11435
+ function getBoxWidth(labelOpts, fontSize) {
11436
+ return labelOpts.usePointStyle ?
11437
+ fontSize * Math.SQRT2 :
11438
+ labelOpts.boxWidth;
11439
+ }
11440
+
11441
+ Chart.Legend = Element.extend({
11442
+
11443
+ initialize: function(config) {
11444
+ helpers.extend(this, config);
11445
+
11446
+ // Contains hit boxes for each dataset (in dataset order)
11447
+ this.legendHitBoxes = [];
11448
+
11449
+ // Are we in doughnut mode which has a different data type
11450
+ this.doughnutMode = false;
11451
+ },
11452
+
11453
+ // These methods are ordered by lifecycle. Utilities then follow.
11454
+ // Any function defined here is inherited by all legend types.
11455
+ // Any function can be extended by the legend type
11456
+
11457
+ beforeUpdate: noop,
11458
+ update: function(maxWidth, maxHeight, margins) {
11459
+ var me = this;
11460
+
11461
+ // Update Lifecycle - Probably don't want to ever extend or overwrite this function ;)
11462
+ me.beforeUpdate();
11463
+
11464
+ // Absorb the master measurements
11465
+ me.maxWidth = maxWidth;
11466
+ me.maxHeight = maxHeight;
11467
+ me.margins = margins;
11468
+
11469
+ // Dimensions
11470
+ me.beforeSetDimensions();
11471
+ me.setDimensions();
11472
+ me.afterSetDimensions();
11473
+ // Labels
11474
+ me.beforeBuildLabels();
11475
+ me.buildLabels();
11476
+ me.afterBuildLabels();
11477
+
11478
+ // Fit
11479
+ me.beforeFit();
11480
+ me.fit();
11481
+ me.afterFit();
11482
+ //
11483
+ me.afterUpdate();
11484
+
11485
+ return me.minSize;
11486
+ },
11487
+ afterUpdate: noop,
11488
+
11489
+ //
11490
+
11491
+ beforeSetDimensions: noop,
11492
+ setDimensions: function() {
11493
+ var me = this;
11494
+ // Set the unconstrained dimension before label rotation
11495
+ if (me.isHorizontal()) {
11496
+ // Reset position before calculating rotation
11497
+ me.width = me.maxWidth;
11498
+ me.left = 0;
11499
+ me.right = me.width;
11500
+ } else {
11501
+ me.height = me.maxHeight;
11502
+
11503
+ // Reset position before calculating rotation
11504
+ me.top = 0;
11505
+ me.bottom = me.height;
11506
+ }
11507
+
11508
+ // Reset padding
11509
+ me.paddingLeft = 0;
11510
+ me.paddingTop = 0;
11511
+ me.paddingRight = 0;
11512
+ me.paddingBottom = 0;
11513
+
11514
+ // Reset minSize
11515
+ me.minSize = {
11516
+ width: 0,
11517
+ height: 0
11518
+ };
11519
+ },
11520
+ afterSetDimensions: noop,
11521
+
11522
+ //
11523
+
11524
+ beforeBuildLabels: noop,
11525
+ buildLabels: function() {
11526
+ var me = this;
11527
+ var labelOpts = me.options.labels || {};
11528
+ var legendItems = helpers.callback(labelOpts.generateLabels, [me.chart], me) || [];
11529
+
11530
+ if (labelOpts.filter) {
11531
+ legendItems = legendItems.filter(function(item) {
11532
+ return labelOpts.filter(item, me.chart.data);
11533
+ });
11534
+ }
11535
+
11536
+ if (me.options.reverse) {
11537
+ legendItems.reverse();
11538
+ }
11539
+
11540
+ me.legendItems = legendItems;
11541
+ },
11542
+ afterBuildLabels: noop,
11543
+
11544
+ //
11545
+
11546
+ beforeFit: noop,
11547
+ fit: function() {
11548
+ var me = this;
11549
+ var opts = me.options;
11550
+ var labelOpts = opts.labels;
11551
+ var display = opts.display;
11552
+
11553
+ var ctx = me.ctx;
11554
+
11555
+ var globalDefault = defaults.global;
11556
+ var valueOrDefault = helpers.valueOrDefault;
11557
+ var fontSize = valueOrDefault(labelOpts.fontSize, globalDefault.defaultFontSize);
11558
+ var fontStyle = valueOrDefault(labelOpts.fontStyle, globalDefault.defaultFontStyle);
11559
+ var fontFamily = valueOrDefault(labelOpts.fontFamily, globalDefault.defaultFontFamily);
11560
+ var labelFont = helpers.fontString(fontSize, fontStyle, fontFamily);
11561
+
11562
+ // Reset hit boxes
11563
+ var hitboxes = me.legendHitBoxes = [];
11564
+
11565
+ var minSize = me.minSize;
11566
+ var isHorizontal = me.isHorizontal();
11567
+
11568
+ if (isHorizontal) {
11569
+ minSize.width = me.maxWidth; // fill all the width
11570
+ minSize.height = display ? 10 : 0;
11571
+ } else {
11572
+ minSize.width = display ? 10 : 0;
11573
+ minSize.height = me.maxHeight; // fill all the height
11574
+ }
11575
+
11576
+ // Increase sizes here
11577
+ if (display) {
11578
+ ctx.font = labelFont;
11579
+
11580
+ if (isHorizontal) {
11581
+ // Labels
11582
+
11583
+ // Width of each line of legend boxes. Labels wrap onto multiple lines when there are too many to fit on one
11584
+ var lineWidths = me.lineWidths = [0];
11585
+ var totalHeight = me.legendItems.length ? fontSize + (labelOpts.padding) : 0;
11586
+
11587
+ ctx.textAlign = 'left';
11588
+ ctx.textBaseline = 'top';
11589
+
11590
+ helpers.each(me.legendItems, function(legendItem, i) {
11591
+ var boxWidth = getBoxWidth(labelOpts, fontSize);
11592
+ var width = boxWidth + (fontSize / 2) + ctx.measureText(legendItem.text).width;
11593
+
11594
+ if (lineWidths[lineWidths.length - 1] + width + labelOpts.padding >= me.width) {
11595
+ totalHeight += fontSize + (labelOpts.padding);
11596
+ lineWidths[lineWidths.length] = me.left;
11597
+ }
11598
+
11599
+ // Store the hitbox width and height here. Final position will be updated in `draw`
11600
+ hitboxes[i] = {
11601
+ left: 0,
11602
+ top: 0,
11603
+ width: width,
11604
+ height: fontSize
11605
+ };
11606
+
11607
+ lineWidths[lineWidths.length - 1] += width + labelOpts.padding;
11608
+ });
11609
+
11610
+ minSize.height += totalHeight;
11611
+
11612
+ } else {
11613
+ var vPadding = labelOpts.padding;
11614
+ var columnWidths = me.columnWidths = [];
11615
+ var totalWidth = labelOpts.padding;
11616
+ var currentColWidth = 0;
11617
+ var currentColHeight = 0;
11618
+ var itemHeight = fontSize + vPadding;
11619
+
11620
+ helpers.each(me.legendItems, function(legendItem, i) {
11621
+ var boxWidth = getBoxWidth(labelOpts, fontSize);
11622
+ var itemWidth = boxWidth + (fontSize / 2) + ctx.measureText(legendItem.text).width;
11623
+
11624
+ // If too tall, go to new column
11625
+ if (currentColHeight + itemHeight > minSize.height) {
11626
+ totalWidth += currentColWidth + labelOpts.padding;
11627
+ columnWidths.push(currentColWidth); // previous column width
11628
+
11629
+ currentColWidth = 0;
11630
+ currentColHeight = 0;
11631
+ }
11632
+
11633
+ // Get max width
11634
+ currentColWidth = Math.max(currentColWidth, itemWidth);
11635
+ currentColHeight += itemHeight;
11636
+
11637
+ // Store the hitbox width and height here. Final position will be updated in `draw`
11638
+ hitboxes[i] = {
11639
+ left: 0,
11640
+ top: 0,
11641
+ width: itemWidth,
11642
+ height: fontSize
11643
+ };
11644
+ });
11645
+
11646
+ totalWidth += currentColWidth;
11647
+ columnWidths.push(currentColWidth);
11648
+ minSize.width += totalWidth;
11649
+ }
11650
+ }
11651
+
11652
+ me.width = minSize.width;
11653
+ me.height = minSize.height;
11654
+ },
11655
+ afterFit: noop,
11656
+
11657
+ // Shared Methods
11658
+ isHorizontal: function() {
11659
+ return this.options.position === 'top' || this.options.position === 'bottom';
11660
+ },
11661
+
11662
+ // Actually draw the legend on the canvas
11663
+ draw: function() {
11664
+ var me = this;
11665
+ var opts = me.options;
11666
+ var labelOpts = opts.labels;
11667
+ var globalDefault = defaults.global;
11668
+ var lineDefault = globalDefault.elements.line;
11669
+ var legendWidth = me.width;
11670
+ var lineWidths = me.lineWidths;
11671
+
11672
+ if (opts.display) {
11673
+ var ctx = me.ctx;
11674
+ var valueOrDefault = helpers.valueOrDefault;
11675
+ var fontColor = valueOrDefault(labelOpts.fontColor, globalDefault.defaultFontColor);
11676
+ var fontSize = valueOrDefault(labelOpts.fontSize, globalDefault.defaultFontSize);
11677
+ var fontStyle = valueOrDefault(labelOpts.fontStyle, globalDefault.defaultFontStyle);
11678
+ var fontFamily = valueOrDefault(labelOpts.fontFamily, globalDefault.defaultFontFamily);
11679
+ var labelFont = helpers.fontString(fontSize, fontStyle, fontFamily);
11680
+ var cursor;
11681
+
11682
+ // Canvas setup
11683
+ ctx.textAlign = 'left';
11684
+ ctx.textBaseline = 'middle';
11685
+ ctx.lineWidth = 0.5;
11686
+ ctx.strokeStyle = fontColor; // for strikethrough effect
11687
+ ctx.fillStyle = fontColor; // render in correct colour
11688
+ ctx.font = labelFont;
11689
+
11690
+ var boxWidth = getBoxWidth(labelOpts, fontSize);
11691
+ var hitboxes = me.legendHitBoxes;
11692
+
11693
+ // current position
11694
+ var drawLegendBox = function(x, y, legendItem) {
11695
+ if (isNaN(boxWidth) || boxWidth <= 0) {
11696
+ return;
11697
+ }
11698
+
11699
+ // Set the ctx for the box
11700
+ ctx.save();
11701
+
11702
+ ctx.fillStyle = valueOrDefault(legendItem.fillStyle, globalDefault.defaultColor);
11703
+ ctx.lineCap = valueOrDefault(legendItem.lineCap, lineDefault.borderCapStyle);
11704
+ ctx.lineDashOffset = valueOrDefault(legendItem.lineDashOffset, lineDefault.borderDashOffset);
11705
+ ctx.lineJoin = valueOrDefault(legendItem.lineJoin, lineDefault.borderJoinStyle);
11706
+ ctx.lineWidth = valueOrDefault(legendItem.lineWidth, lineDefault.borderWidth);
11707
+ ctx.strokeStyle = valueOrDefault(legendItem.strokeStyle, globalDefault.defaultColor);
11708
+ var isLineWidthZero = (valueOrDefault(legendItem.lineWidth, lineDefault.borderWidth) === 0);
11709
+
11710
+ if (ctx.setLineDash) {
11711
+ // IE 9 and 10 do not support line dash
11712
+ ctx.setLineDash(valueOrDefault(legendItem.lineDash, lineDefault.borderDash));
11713
+ }
11714
+
11715
+ if (opts.labels && opts.labels.usePointStyle) {
11716
+ // Recalculate x and y for drawPoint() because its expecting
11717
+ // x and y to be center of figure (instead of top left)
11718
+ var radius = fontSize * Math.SQRT2 / 2;
11719
+ var offSet = radius / Math.SQRT2;
11720
+ var centerX = x + offSet;
11721
+ var centerY = y + offSet;
11722
+
11723
+ // Draw pointStyle as legend symbol
11724
+ helpers.canvas.drawPoint(ctx, legendItem.pointStyle, radius, centerX, centerY);
11725
+ } else {
11726
+ // Draw box as legend symbol
11727
+ if (!isLineWidthZero) {
11728
+ ctx.strokeRect(x, y, boxWidth, fontSize);
11729
+ }
11730
+ ctx.fillRect(x, y, boxWidth, fontSize);
11731
+ }
11732
+
11733
+ ctx.restore();
11734
+ };
11735
+ var fillText = function(x, y, legendItem, textWidth) {
11736
+ var halfFontSize = fontSize / 2;
11737
+ var xLeft = boxWidth + halfFontSize + x;
11738
+ var yMiddle = y + halfFontSize;
11739
+
11740
+ ctx.fillText(legendItem.text, xLeft, yMiddle);
11741
+
11742
+ if (legendItem.hidden) {
11743
+ // Strikethrough the text if hidden
11744
+ ctx.beginPath();
11745
+ ctx.lineWidth = 2;
11746
+ ctx.moveTo(xLeft, yMiddle);
11747
+ ctx.lineTo(xLeft + textWidth, yMiddle);
11748
+ ctx.stroke();
11749
+ }
11750
+ };
11751
+
11752
+ // Horizontal
11753
+ var isHorizontal = me.isHorizontal();
11754
+ if (isHorizontal) {
11755
+ cursor = {
11756
+ x: me.left + ((legendWidth - lineWidths[0]) / 2),
11757
+ y: me.top + labelOpts.padding,
11758
+ line: 0
11759
+ };
11760
+ } else {
11761
+ cursor = {
11762
+ x: me.left + labelOpts.padding,
11763
+ y: me.top + labelOpts.padding,
11764
+ line: 0
11765
+ };
11766
+ }
11767
+
11768
+ var itemHeight = fontSize + labelOpts.padding;
11769
+ helpers.each(me.legendItems, function(legendItem, i) {
11770
+ var textWidth = ctx.measureText(legendItem.text).width;
11771
+ var width = boxWidth + (fontSize / 2) + textWidth;
11772
+ var x = cursor.x;
11773
+ var y = cursor.y;
11774
+
11775
+ if (isHorizontal) {
11776
+ if (x + width >= legendWidth) {
11777
+ y = cursor.y += itemHeight;
11778
+ cursor.line++;
11779
+ x = cursor.x = me.left + ((legendWidth - lineWidths[cursor.line]) / 2);
11780
+ }
11781
+ } else if (y + itemHeight > me.bottom) {
11782
+ x = cursor.x = x + me.columnWidths[cursor.line] + labelOpts.padding;
11783
+ y = cursor.y = me.top + labelOpts.padding;
11784
+ cursor.line++;
11785
+ }
11786
+
11787
+ drawLegendBox(x, y, legendItem);
11788
+
11789
+ hitboxes[i].left = x;
11790
+ hitboxes[i].top = y;
11791
+
11792
+ // Fill the actual label
11793
+ fillText(x, y, legendItem, textWidth);
11794
+
11795
+ if (isHorizontal) {
11796
+ cursor.x += width + (labelOpts.padding);
11797
+ } else {
11798
+ cursor.y += itemHeight;
11799
+ }
11800
+
11801
+ });
11802
+ }
11803
+ },
11804
+
11805
+ /**
11806
+ * Handle an event
11807
+ * @private
11808
+ * @param {IEvent} event - The event to handle
11809
+ * @return {Boolean} true if a change occured
11810
+ */
11811
+ handleEvent: function(e) {
11812
+ var me = this;
11813
+ var opts = me.options;
11814
+ var type = e.type === 'mouseup' ? 'click' : e.type;
11815
+ var changed = false;
11816
+
11817
+ if (type === 'mousemove') {
11818
+ if (!opts.onHover) {
11819
+ return;
11820
+ }
11821
+ } else if (type === 'click') {
11822
+ if (!opts.onClick) {
11823
+ return;
11824
+ }
11825
+ } else {
11826
+ return;
11827
+ }
11828
+
11829
+ // Chart event already has relative position in it
11830
+ var x = e.x;
11831
+ var y = e.y;
11832
+
11833
+ if (x >= me.left && x <= me.right && y >= me.top && y <= me.bottom) {
11834
+ // See if we are touching one of the dataset boxes
11835
+ var lh = me.legendHitBoxes;
11836
+ for (var i = 0; i < lh.length; ++i) {
11837
+ var hitBox = lh[i];
11838
+
11839
+ if (x >= hitBox.left && x <= hitBox.left + hitBox.width && y >= hitBox.top && y <= hitBox.top + hitBox.height) {
11840
+ // Touching an element
11841
+ if (type === 'click') {
11842
+ // use e.native for backwards compatibility
11843
+ opts.onClick.call(me, e.native, me.legendItems[i]);
11844
+ changed = true;
11845
+ break;
11846
+ } else if (type === 'mousemove') {
11847
+ // use e.native for backwards compatibility
11848
+ opts.onHover.call(me, e.native, me.legendItems[i]);
11849
+ changed = true;
11850
+ break;
11851
+ }
11852
+ }
11853
+ }
11854
+ }
11855
+
11856
+ return changed;
11857
+ }
11858
+ });
11859
+
11860
+ function createNewLegendAndAttach(chart, legendOpts) {
11861
+ var legend = new Chart.Legend({
11862
+ ctx: chart.ctx,
11863
+ options: legendOpts,
11864
+ chart: chart
11865
+ });
11866
+
11867
+ layout.configure(chart, legend, legendOpts);
11868
+ layout.addBox(chart, legend);
11869
+ chart.legend = legend;
11870
+ }
11871
+
11872
+ return {
11873
+ id: 'legend',
11874
+
11875
+ beforeInit: function(chart) {
11876
+ var legendOpts = chart.options.legend;
11877
+
11878
+ if (legendOpts) {
11879
+ createNewLegendAndAttach(chart, legendOpts);
11880
+ }
11881
+ },
11882
+
11883
+ beforeUpdate: function(chart) {
11884
+ var legendOpts = chart.options.legend;
11885
+ var legend = chart.legend;
11886
+
11887
+ if (legendOpts) {
11888
+ helpers.mergeIf(legendOpts, defaults.global.legend);
11889
+
11890
+ if (legend) {
11891
+ layout.configure(chart, legend, legendOpts);
11892
+ legend.options = legendOpts;
11893
+ } else {
11894
+ createNewLegendAndAttach(chart, legendOpts);
11895
+ }
11896
+ } else if (legend) {
11897
+ layout.removeBox(chart, legend);
11898
+ delete chart.legend;
11899
+ }
11900
+ },
11901
+
11902
+ afterEvent: function(chart, e) {
11903
+ var legend = chart.legend;
11904
+ if (legend) {
11905
+ legend.handleEvent(e);
11906
+ }
11907
+ }
11908
+ };
11909
+ };
11910
+
11911
+ },{"25":25,"26":26,"45":45}],51:[function(require,module,exports){
11912
+ 'use strict';
11913
+
11914
+ var defaults = require(25);
11915
+ var Element = require(26);
11916
+ var helpers = require(45);
11917
+
11918
+ defaults._set('global', {
11919
+ title: {
11920
+ display: false,
11921
+ fontStyle: 'bold',
11922
+ fullWidth: true,
11923
+ lineHeight: 1.2,
11924
+ padding: 10,
11925
+ position: 'top',
11926
+ text: '',
11927
+ weight: 2000 // by default greater than legend (1000) to be above
11928
+ }
11929
+ });
11930
+
11931
+ module.exports = function(Chart) {
11932
+
11933
+ var layout = Chart.layoutService;
11934
+ var noop = helpers.noop;
11935
+
11936
+ Chart.Title = Element.extend({
11937
+ initialize: function(config) {
11938
+ var me = this;
11939
+ helpers.extend(me, config);
11940
+
11941
+ // Contains hit boxes for each dataset (in dataset order)
11942
+ me.legendHitBoxes = [];
11943
+ },
11944
+
11945
+ // These methods are ordered by lifecycle. Utilities then follow.
11946
+
11947
+ beforeUpdate: noop,
11948
+ update: function(maxWidth, maxHeight, margins) {
11949
+ var me = this;
11950
+
11951
+ // Update Lifecycle - Probably don't want to ever extend or overwrite this function ;)
11952
+ me.beforeUpdate();
11953
+
11954
+ // Absorb the master measurements
11955
+ me.maxWidth = maxWidth;
11956
+ me.maxHeight = maxHeight;
11957
+ me.margins = margins;
11958
+
11959
+ // Dimensions
11960
+ me.beforeSetDimensions();
11961
+ me.setDimensions();
11962
+ me.afterSetDimensions();
11963
+ // Labels
11964
+ me.beforeBuildLabels();
11965
+ me.buildLabels();
11966
+ me.afterBuildLabels();
11967
+
11968
+ // Fit
11969
+ me.beforeFit();
11970
+ me.fit();
11971
+ me.afterFit();
11972
+ //
11973
+ me.afterUpdate();
11974
+
11975
+ return me.minSize;
11976
+
11977
+ },
11978
+ afterUpdate: noop,
11979
+
11980
+ //
11981
+
11982
+ beforeSetDimensions: noop,
11983
+ setDimensions: function() {
11984
+ var me = this;
11985
+ // Set the unconstrained dimension before label rotation
11986
+ if (me.isHorizontal()) {
11987
+ // Reset position before calculating rotation
11988
+ me.width = me.maxWidth;
11989
+ me.left = 0;
11990
+ me.right = me.width;
11991
+ } else {
11992
+ me.height = me.maxHeight;
11993
+
11994
+ // Reset position before calculating rotation
11995
+ me.top = 0;
11996
+ me.bottom = me.height;
11997
+ }
11998
+
11999
+ // Reset padding
12000
+ me.paddingLeft = 0;
12001
+ me.paddingTop = 0;
12002
+ me.paddingRight = 0;
12003
+ me.paddingBottom = 0;
12004
+
12005
+ // Reset minSize
12006
+ me.minSize = {
12007
+ width: 0,
12008
+ height: 0
12009
+ };
12010
+ },
12011
+ afterSetDimensions: noop,
12012
+
12013
+ //
12014
+
12015
+ beforeBuildLabels: noop,
12016
+ buildLabels: noop,
12017
+ afterBuildLabels: noop,
12018
+
12019
+ //
12020
+
12021
+ beforeFit: noop,
12022
+ fit: function() {
12023
+ var me = this;
12024
+ var valueOrDefault = helpers.valueOrDefault;
12025
+ var opts = me.options;
12026
+ var display = opts.display;
12027
+ var fontSize = valueOrDefault(opts.fontSize, defaults.global.defaultFontSize);
12028
+ var minSize = me.minSize;
12029
+ var lineCount = helpers.isArray(opts.text) ? opts.text.length : 1;
12030
+ var lineHeight = helpers.options.toLineHeight(opts.lineHeight, fontSize);
12031
+ var textSize = display ? (lineCount * lineHeight) + (opts.padding * 2) : 0;
12032
+
12033
+ if (me.isHorizontal()) {
12034
+ minSize.width = me.maxWidth; // fill all the width
12035
+ minSize.height = textSize;
12036
+ } else {
12037
+ minSize.width = textSize;
12038
+ minSize.height = me.maxHeight; // fill all the height
12039
+ }
12040
+
12041
+ me.width = minSize.width;
12042
+ me.height = minSize.height;
12043
+
12044
+ },
12045
+ afterFit: noop,
12046
+
12047
+ // Shared Methods
12048
+ isHorizontal: function() {
12049
+ var pos = this.options.position;
12050
+ return pos === 'top' || pos === 'bottom';
12051
+ },
12052
+
12053
+ // Actually draw the title block on the canvas
12054
+ draw: function() {
12055
+ var me = this;
12056
+ var ctx = me.ctx;
12057
+ var valueOrDefault = helpers.valueOrDefault;
12058
+ var opts = me.options;
12059
+ var globalDefaults = defaults.global;
12060
+
12061
+ if (opts.display) {
12062
+ var fontSize = valueOrDefault(opts.fontSize, globalDefaults.defaultFontSize);
12063
+ var fontStyle = valueOrDefault(opts.fontStyle, globalDefaults.defaultFontStyle);
12064
+ var fontFamily = valueOrDefault(opts.fontFamily, globalDefaults.defaultFontFamily);
12065
+ var titleFont = helpers.fontString(fontSize, fontStyle, fontFamily);
12066
+ var lineHeight = helpers.options.toLineHeight(opts.lineHeight, fontSize);
12067
+ var offset = lineHeight / 2 + opts.padding;
12068
+ var rotation = 0;
12069
+ var top = me.top;
12070
+ var left = me.left;
12071
+ var bottom = me.bottom;
12072
+ var right = me.right;
12073
+ var maxWidth, titleX, titleY;
12074
+
12075
+ ctx.fillStyle = valueOrDefault(opts.fontColor, globalDefaults.defaultFontColor); // render in correct colour
12076
+ ctx.font = titleFont;
12077
+
12078
+ // Horizontal
12079
+ if (me.isHorizontal()) {
12080
+ titleX = left + ((right - left) / 2); // midpoint of the width
12081
+ titleY = top + offset;
12082
+ maxWidth = right - left;
12083
+ } else {
12084
+ titleX = opts.position === 'left' ? left + offset : right - offset;
12085
+ titleY = top + ((bottom - top) / 2);
12086
+ maxWidth = bottom - top;
12087
+ rotation = Math.PI * (opts.position === 'left' ? -0.5 : 0.5);
12088
+ }
12089
+
12090
+ ctx.save();
12091
+ ctx.translate(titleX, titleY);
12092
+ ctx.rotate(rotation);
12093
+ ctx.textAlign = 'center';
12094
+ ctx.textBaseline = 'middle';
12095
+
12096
+ var text = opts.text;
12097
+ if (helpers.isArray(text)) {
12098
+ var y = 0;
12099
+ for (var i = 0; i < text.length; ++i) {
12100
+ ctx.fillText(text[i], 0, y, maxWidth);
12101
+ y += lineHeight;
12102
+ }
12103
+ } else {
12104
+ ctx.fillText(text, 0, 0, maxWidth);
12105
+ }
12106
+
12107
+ ctx.restore();
12108
+ }
12109
+ }
12110
+ });
12111
+
12112
+ function createNewTitleBlockAndAttach(chart, titleOpts) {
12113
+ var title = new Chart.Title({
12114
+ ctx: chart.ctx,
12115
+ options: titleOpts,
12116
+ chart: chart
12117
+ });
12118
+
12119
+ layout.configure(chart, title, titleOpts);
12120
+ layout.addBox(chart, title);
12121
+ chart.titleBlock = title;
12122
+ }
12123
+
12124
+ return {
12125
+ id: 'title',
12126
+
12127
+ beforeInit: function(chart) {
12128
+ var titleOpts = chart.options.title;
12129
+
12130
+ if (titleOpts) {
12131
+ createNewTitleBlockAndAttach(chart, titleOpts);
12132
+ }
12133
+ },
12134
+
12135
+ beforeUpdate: function(chart) {
12136
+ var titleOpts = chart.options.title;
12137
+ var titleBlock = chart.titleBlock;
12138
+
12139
+ if (titleOpts) {
12140
+ helpers.mergeIf(titleOpts, defaults.global.title);
12141
+
12142
+ if (titleBlock) {
12143
+ layout.configure(chart, titleBlock, titleOpts);
12144
+ titleBlock.options = titleOpts;
12145
+ } else {
12146
+ createNewTitleBlockAndAttach(chart, titleOpts);
12147
+ }
12148
+ } else if (titleBlock) {
12149
+ Chart.layoutService.removeBox(chart, titleBlock);
12150
+ delete chart.titleBlock;
12151
+ }
12152
+ }
12153
+ };
12154
+ };
12155
+
12156
+ },{"25":25,"26":26,"45":45}],52:[function(require,module,exports){
12157
+ 'use strict';
12158
+
12159
+ module.exports = function(Chart) {
12160
+
12161
+ // Default config for a category scale
12162
+ var defaultConfig = {
12163
+ position: 'bottom'
12164
+ };
12165
+
12166
+ var DatasetScale = Chart.Scale.extend({
12167
+ /**
12168
+ * Internal function to get the correct labels. If data.xLabels or data.yLabels are defined, use those
12169
+ * else fall back to data.labels
12170
+ * @private
12171
+ */
12172
+ getLabels: function() {
12173
+ var data = this.chart.data;
12174
+ return this.options.labels || (this.isHorizontal() ? data.xLabels : data.yLabels) || data.labels;
12175
+ },
12176
+
12177
+ determineDataLimits: function() {
12178
+ var me = this;
12179
+ var labels = me.getLabels();
12180
+ me.minIndex = 0;
12181
+ me.maxIndex = labels.length - 1;
12182
+ var findIndex;
12183
+
12184
+ if (me.options.ticks.min !== undefined) {
12185
+ // user specified min value
12186
+ findIndex = labels.indexOf(me.options.ticks.min);
12187
+ me.minIndex = findIndex !== -1 ? findIndex : me.minIndex;
12188
+ }
12189
+
12190
+ if (me.options.ticks.max !== undefined) {
12191
+ // user specified max value
12192
+ findIndex = labels.indexOf(me.options.ticks.max);
12193
+ me.maxIndex = findIndex !== -1 ? findIndex : me.maxIndex;
12194
+ }
12195
+
12196
+ me.min = labels[me.minIndex];
12197
+ me.max = labels[me.maxIndex];
12198
+ },
12199
+
12200
+ buildTicks: function() {
12201
+ var me = this;
12202
+ var labels = me.getLabels();
12203
+ // If we are viewing some subset of labels, slice the original array
12204
+ me.ticks = (me.minIndex === 0 && me.maxIndex === labels.length - 1) ? labels : labels.slice(me.minIndex, me.maxIndex + 1);
12205
+ },
12206
+
12207
+ getLabelForIndex: function(index, datasetIndex) {
12208
+ var me = this;
12209
+ var data = me.chart.data;
12210
+ var isHorizontal = me.isHorizontal();
12211
+
12212
+ if (data.yLabels && !isHorizontal) {
12213
+ return me.getRightValue(data.datasets[datasetIndex].data[index]);
12214
+ }
12215
+ return me.ticks[index - me.minIndex];
12216
+ },
12217
+
12218
+ // Used to get data value locations. Value can either be an index or a numerical value
12219
+ getPixelForValue: function(value, index) {
12220
+ var me = this;
12221
+ var offset = me.options.offset;
12222
+ // 1 is added because we need the length but we have the indexes
12223
+ var offsetAmt = Math.max((me.maxIndex + 1 - me.minIndex - (offset ? 0 : 1)), 1);
12224
+
12225
+ // If value is a data object, then index is the index in the data array,
12226
+ // not the index of the scale. We need to change that.
12227
+ var valueCategory;
12228
+ if (value !== undefined && value !== null) {
12229
+ valueCategory = me.isHorizontal() ? value.x : value.y;
12230
+ }
12231
+ if (valueCategory !== undefined || (value !== undefined && isNaN(index))) {
12232
+ var labels = me.getLabels();
12233
+ value = valueCategory || value;
12234
+ var idx = labels.indexOf(value);
12235
+ index = idx !== -1 ? idx : index;
12236
+ }
12237
+
12238
+ if (me.isHorizontal()) {
12239
+ var valueWidth = me.width / offsetAmt;
12240
+ var widthOffset = (valueWidth * (index - me.minIndex));
12241
+
12242
+ if (offset) {
12243
+ widthOffset += (valueWidth / 2);
12244
+ }
12245
+
12246
+ return me.left + Math.round(widthOffset);
12247
+ }
12248
+ var valueHeight = me.height / offsetAmt;
12249
+ var heightOffset = (valueHeight * (index - me.minIndex));
12250
+
12251
+ if (offset) {
12252
+ heightOffset += (valueHeight / 2);
12253
+ }
12254
+
12255
+ return me.top + Math.round(heightOffset);
12256
+ },
12257
+ getPixelForTick: function(index) {
12258
+ return this.getPixelForValue(this.ticks[index], index + this.minIndex, null);
12259
+ },
12260
+ getValueForPixel: function(pixel) {
12261
+ var me = this;
12262
+ var offset = me.options.offset;
12263
+ var value;
12264
+ var offsetAmt = Math.max((me._ticks.length - (offset ? 0 : 1)), 1);
12265
+ var horz = me.isHorizontal();
12266
+ var valueDimension = (horz ? me.width : me.height) / offsetAmt;
12267
+
12268
+ pixel -= horz ? me.left : me.top;
12269
+
12270
+ if (offset) {
12271
+ pixel -= (valueDimension / 2);
12272
+ }
12273
+
12274
+ if (pixel <= 0) {
12275
+ value = 0;
12276
+ } else {
12277
+ value = Math.round(pixel / valueDimension);
12278
+ }
12279
+
12280
+ return value + me.minIndex;
12281
+ },
12282
+ getBasePixel: function() {
12283
+ return this.bottom;
12284
+ }
12285
+ });
12286
+
12287
+ Chart.scaleService.registerScaleType('category', DatasetScale, defaultConfig);
12288
+
12289
+ };
12290
+
12291
+ },{}],53:[function(require,module,exports){
12292
+ 'use strict';
12293
+
12294
+ var defaults = require(25);
12295
+ var helpers = require(45);
12296
+ var Ticks = require(34);
12297
+
12298
+ module.exports = function(Chart) {
12299
+
12300
+ var defaultConfig = {
12301
+ position: 'left',
12302
+ ticks: {
12303
+ callback: Ticks.formatters.linear
12304
+ }
12305
+ };
12306
+
12307
+ var LinearScale = Chart.LinearScaleBase.extend({
12308
+
12309
+ determineDataLimits: function() {
12310
+ var me = this;
12311
+ var opts = me.options;
12312
+ var chart = me.chart;
12313
+ var data = chart.data;
12314
+ var datasets = data.datasets;
12315
+ var isHorizontal = me.isHorizontal();
12316
+ var DEFAULT_MIN = 0;
12317
+ var DEFAULT_MAX = 1;
12318
+
12319
+ function IDMatches(meta) {
12320
+ return isHorizontal ? meta.xAxisID === me.id : meta.yAxisID === me.id;
12321
+ }
12322
+
12323
+ // First Calculate the range
12324
+ me.min = null;
12325
+ me.max = null;
12326
+
12327
+ var hasStacks = opts.stacked;
12328
+ if (hasStacks === undefined) {
12329
+ helpers.each(datasets, function(dataset, datasetIndex) {
12330
+ if (hasStacks) {
12331
+ return;
12332
+ }
12333
+
12334
+ var meta = chart.getDatasetMeta(datasetIndex);
12335
+ if (chart.isDatasetVisible(datasetIndex) && IDMatches(meta) &&
12336
+ meta.stack !== undefined) {
12337
+ hasStacks = true;
12338
+ }
12339
+ });
12340
+ }
12341
+
12342
+ if (opts.stacked || hasStacks) {
12343
+ var valuesPerStack = {};
12344
+
12345
+ helpers.each(datasets, function(dataset, datasetIndex) {
12346
+ var meta = chart.getDatasetMeta(datasetIndex);
12347
+ var key = [
12348
+ meta.type,
12349
+ // we have a separate stack for stack=undefined datasets when the opts.stacked is undefined
12350
+ ((opts.stacked === undefined && meta.stack === undefined) ? datasetIndex : ''),
12351
+ meta.stack
12352
+ ].join('.');
12353
+
12354
+ if (valuesPerStack[key] === undefined) {
12355
+ valuesPerStack[key] = {
12356
+ positiveValues: [],
12357
+ negativeValues: []
12358
+ };
12359
+ }
12360
+
12361
+ // Store these per type
12362
+ var positiveValues = valuesPerStack[key].positiveValues;
12363
+ var negativeValues = valuesPerStack[key].negativeValues;
12364
+
12365
+ if (chart.isDatasetVisible(datasetIndex) && IDMatches(meta)) {
12366
+ helpers.each(dataset.data, function(rawValue, index) {
12367
+ var value = +me.getRightValue(rawValue);
12368
+ if (isNaN(value) || meta.data[index].hidden) {
12369
+ return;
12370
+ }
12371
+
12372
+ positiveValues[index] = positiveValues[index] || 0;
12373
+ negativeValues[index] = negativeValues[index] || 0;
12374
+
12375
+ if (opts.relativePoints) {
12376
+ positiveValues[index] = 100;
12377
+ } else if (value < 0) {
12378
+ negativeValues[index] += value;
12379
+ } else {
12380
+ positiveValues[index] += value;
12381
+ }
12382
+ });
12383
+ }
12384
+ });
12385
+
12386
+ helpers.each(valuesPerStack, function(valuesForType) {
12387
+ var values = valuesForType.positiveValues.concat(valuesForType.negativeValues);
12388
+ var minVal = helpers.min(values);
12389
+ var maxVal = helpers.max(values);
12390
+ me.min = me.min === null ? minVal : Math.min(me.min, minVal);
12391
+ me.max = me.max === null ? maxVal : Math.max(me.max, maxVal);
12392
+ });
12393
+
12394
+ } else {
12395
+ helpers.each(datasets, function(dataset, datasetIndex) {
12396
+ var meta = chart.getDatasetMeta(datasetIndex);
12397
+ if (chart.isDatasetVisible(datasetIndex) && IDMatches(meta)) {
12398
+ helpers.each(dataset.data, function(rawValue, index) {
12399
+ var value = +me.getRightValue(rawValue);
12400
+ if (isNaN(value) || meta.data[index].hidden) {
12401
+ return;
12402
+ }
12403
+
12404
+ if (me.min === null) {
12405
+ me.min = value;
12406
+ } else if (value < me.min) {
12407
+ me.min = value;
12408
+ }
12409
+
12410
+ if (me.max === null) {
12411
+ me.max = value;
12412
+ } else if (value > me.max) {
12413
+ me.max = value;
12414
+ }
12415
+ });
12416
+ }
12417
+ });
12418
+ }
12419
+
12420
+ me.min = isFinite(me.min) && !isNaN(me.min) ? me.min : DEFAULT_MIN;
12421
+ me.max = isFinite(me.max) && !isNaN(me.max) ? me.max : DEFAULT_MAX;
12422
+
12423
+ // Common base implementation to handle ticks.min, ticks.max, ticks.beginAtZero
12424
+ this.handleTickRangeOptions();
12425
+ },
12426
+ getTickLimit: function() {
12427
+ var maxTicks;
12428
+ var me = this;
12429
+ var tickOpts = me.options.ticks;
12430
+
12431
+ if (me.isHorizontal()) {
12432
+ maxTicks = Math.min(tickOpts.maxTicksLimit ? tickOpts.maxTicksLimit : 11, Math.ceil(me.width / 50));
12433
+ } else {
12434
+ // The factor of 2 used to scale the font size has been experimentally determined.
12435
+ var tickFontSize = helpers.valueOrDefault(tickOpts.fontSize, defaults.global.defaultFontSize);
12436
+ maxTicks = Math.min(tickOpts.maxTicksLimit ? tickOpts.maxTicksLimit : 11, Math.ceil(me.height / (2 * tickFontSize)));
12437
+ }
12438
+
12439
+ return maxTicks;
12440
+ },
12441
+ // Called after the ticks are built. We need
12442
+ handleDirectionalChanges: function() {
12443
+ if (!this.isHorizontal()) {
12444
+ // We are in a vertical orientation. The top value is the highest. So reverse the array
12445
+ this.ticks.reverse();
12446
+ }
12447
+ },
12448
+ getLabelForIndex: function(index, datasetIndex) {
12449
+ return +this.getRightValue(this.chart.data.datasets[datasetIndex].data[index]);
12450
+ },
12451
+ // Utils
12452
+ getPixelForValue: function(value) {
12453
+ // This must be called after fit has been run so that
12454
+ // this.left, this.top, this.right, and this.bottom have been defined
12455
+ var me = this;
12456
+ var start = me.start;
12457
+
12458
+ var rightValue = +me.getRightValue(value);
12459
+ var pixel;
12460
+ var range = me.end - start;
12461
+
12462
+ if (me.isHorizontal()) {
12463
+ pixel = me.left + (me.width / range * (rightValue - start));
12464
+ return Math.round(pixel);
12465
+ }
12466
+
12467
+ pixel = me.bottom - (me.height / range * (rightValue - start));
12468
+ return Math.round(pixel);
12469
+ },
12470
+ getValueForPixel: function(pixel) {
12471
+ var me = this;
12472
+ var isHorizontal = me.isHorizontal();
12473
+ var innerDimension = isHorizontal ? me.width : me.height;
12474
+ var offset = (isHorizontal ? pixel - me.left : me.bottom - pixel) / innerDimension;
12475
+ return me.start + ((me.end - me.start) * offset);
12476
+ },
12477
+ getPixelForTick: function(index) {
12478
+ return this.getPixelForValue(this.ticksAsNumbers[index]);
12479
+ }
12480
+ });
12481
+ Chart.scaleService.registerScaleType('linear', LinearScale, defaultConfig);
12482
+
12483
+ };
12484
+
12485
+ },{"25":25,"34":34,"45":45}],54:[function(require,module,exports){
12486
+ 'use strict';
12487
+
12488
+ var helpers = require(45);
12489
+ var Ticks = require(34);
12490
+
12491
+ module.exports = function(Chart) {
12492
+
12493
+ var noop = helpers.noop;
12494
+
12495
+ Chart.LinearScaleBase = Chart.Scale.extend({
12496
+ getRightValue: function(value) {
12497
+ if (typeof value === 'string') {
12498
+ return +value;
12499
+ }
12500
+ return Chart.Scale.prototype.getRightValue.call(this, value);
12501
+ },
12502
+
12503
+ handleTickRangeOptions: function() {
12504
+ var me = this;
12505
+ var opts = me.options;
12506
+ var tickOpts = opts.ticks;
12507
+
12508
+ // If we are forcing it to begin at 0, but 0 will already be rendered on the chart,
12509
+ // do nothing since that would make the chart weird. If the user really wants a weird chart
12510
+ // axis, they can manually override it
12511
+ if (tickOpts.beginAtZero) {
12512
+ var minSign = helpers.sign(me.min);
12513
+ var maxSign = helpers.sign(me.max);
12514
+
12515
+ if (minSign < 0 && maxSign < 0) {
12516
+ // move the top up to 0
12517
+ me.max = 0;
12518
+ } else if (minSign > 0 && maxSign > 0) {
12519
+ // move the bottom down to 0
12520
+ me.min = 0;
12521
+ }
12522
+ }
12523
+
12524
+ var setMin = tickOpts.min !== undefined || tickOpts.suggestedMin !== undefined;
12525
+ var setMax = tickOpts.max !== undefined || tickOpts.suggestedMax !== undefined;
12526
+
12527
+ if (tickOpts.min !== undefined) {
12528
+ me.min = tickOpts.min;
12529
+ } else if (tickOpts.suggestedMin !== undefined) {
12530
+ if (me.min === null) {
12531
+ me.min = tickOpts.suggestedMin;
12532
+ } else {
12533
+ me.min = Math.min(me.min, tickOpts.suggestedMin);
12534
+ }
12535
+ }
12536
+
12537
+ if (tickOpts.max !== undefined) {
12538
+ me.max = tickOpts.max;
12539
+ } else if (tickOpts.suggestedMax !== undefined) {
12540
+ if (me.max === null) {
12541
+ me.max = tickOpts.suggestedMax;
12542
+ } else {
12543
+ me.max = Math.max(me.max, tickOpts.suggestedMax);
12544
+ }
12545
+ }
12546
+
12547
+ if (setMin !== setMax) {
12548
+ // We set the min or the max but not both.
12549
+ // So ensure that our range is good
12550
+ // Inverted or 0 length range can happen when
12551
+ // ticks.min is set, and no datasets are visible
12552
+ if (me.min >= me.max) {
12553
+ if (setMin) {
12554
+ me.max = me.min + 1;
12555
+ } else {
12556
+ me.min = me.max - 1;
12557
+ }
12558
+ }
12559
+ }
12560
+
12561
+ if (me.min === me.max) {
12562
+ me.max++;
12563
+
12564
+ if (!tickOpts.beginAtZero) {
12565
+ me.min--;
12566
+ }
12567
+ }
12568
+ },
12569
+ getTickLimit: noop,
12570
+ handleDirectionalChanges: noop,
12571
+
12572
+ buildTicks: function() {
12573
+ var me = this;
12574
+ var opts = me.options;
12575
+ var tickOpts = opts.ticks;
12576
+
12577
+ // Figure out what the max number of ticks we can support it is based on the size of
12578
+ // the axis area. For now, we say that the minimum tick spacing in pixels must be 50
12579
+ // We also limit the maximum number of ticks to 11 which gives a nice 10 squares on
12580
+ // the graph. Make sure we always have at least 2 ticks
12581
+ var maxTicks = me.getTickLimit();
12582
+ maxTicks = Math.max(2, maxTicks);
12583
+
12584
+ var numericGeneratorOptions = {
12585
+ maxTicks: maxTicks,
12586
+ min: tickOpts.min,
12587
+ max: tickOpts.max,
12588
+ stepSize: helpers.valueOrDefault(tickOpts.fixedStepSize, tickOpts.stepSize)
12589
+ };
12590
+ var ticks = me.ticks = Ticks.generators.linear(numericGeneratorOptions, me);
12591
+
12592
+ me.handleDirectionalChanges();
12593
+
12594
+ // At this point, we need to update our max and min given the tick values since we have expanded the
12595
+ // range of the scale
12596
+ me.max = helpers.max(ticks);
12597
+ me.min = helpers.min(ticks);
12598
+
12599
+ if (tickOpts.reverse) {
12600
+ ticks.reverse();
12601
+
12602
+ me.start = me.max;
12603
+ me.end = me.min;
12604
+ } else {
12605
+ me.start = me.min;
12606
+ me.end = me.max;
12607
+ }
12608
+ },
12609
+ convertTicksToLabels: function() {
12610
+ var me = this;
12611
+ me.ticksAsNumbers = me.ticks.slice();
12612
+ me.zeroLineIndex = me.ticks.indexOf(0);
12613
+
12614
+ Chart.Scale.prototype.convertTicksToLabels.call(me);
12615
+ }
12616
+ });
12617
+ };
12618
+
12619
+ },{"34":34,"45":45}],55:[function(require,module,exports){
12620
+ 'use strict';
12621
+
12622
+ var helpers = require(45);
12623
+ var Ticks = require(34);
12624
+
12625
+ module.exports = function(Chart) {
12626
+
12627
+ var defaultConfig = {
12628
+ position: 'left',
12629
+
12630
+ // label settings
12631
+ ticks: {
12632
+ callback: Ticks.formatters.logarithmic
12633
+ }
12634
+ };
12635
+
12636
+ var LogarithmicScale = Chart.Scale.extend({
12637
+ determineDataLimits: function() {
12638
+ var me = this;
12639
+ var opts = me.options;
12640
+ var tickOpts = opts.ticks;
12641
+ var chart = me.chart;
12642
+ var data = chart.data;
12643
+ var datasets = data.datasets;
12644
+ var valueOrDefault = helpers.valueOrDefault;
12645
+ var isHorizontal = me.isHorizontal();
12646
+ function IDMatches(meta) {
12647
+ return isHorizontal ? meta.xAxisID === me.id : meta.yAxisID === me.id;
12648
+ }
12649
+
12650
+ // Calculate Range
12651
+ me.min = null;
12652
+ me.max = null;
12653
+ me.minNotZero = null;
12654
+
12655
+ var hasStacks = opts.stacked;
12656
+ if (hasStacks === undefined) {
12657
+ helpers.each(datasets, function(dataset, datasetIndex) {
12658
+ if (hasStacks) {
12659
+ return;
12660
+ }
12661
+
12662
+ var meta = chart.getDatasetMeta(datasetIndex);
12663
+ if (chart.isDatasetVisible(datasetIndex) && IDMatches(meta) &&
12664
+ meta.stack !== undefined) {
12665
+ hasStacks = true;
12666
+ }
12667
+ });
12668
+ }
12669
+
12670
+ if (opts.stacked || hasStacks) {
12671
+ var valuesPerStack = {};
12672
+
12673
+ helpers.each(datasets, function(dataset, datasetIndex) {
12674
+ var meta = chart.getDatasetMeta(datasetIndex);
12675
+ var key = [
12676
+ meta.type,
12677
+ // we have a separate stack for stack=undefined datasets when the opts.stacked is undefined
12678
+ ((opts.stacked === undefined && meta.stack === undefined) ? datasetIndex : ''),
12679
+ meta.stack
12680
+ ].join('.');
12681
+
12682
+ if (chart.isDatasetVisible(datasetIndex) && IDMatches(meta)) {
12683
+ if (valuesPerStack[key] === undefined) {
12684
+ valuesPerStack[key] = [];
12685
+ }
12686
+
12687
+ helpers.each(dataset.data, function(rawValue, index) {
12688
+ var values = valuesPerStack[key];
12689
+ var value = +me.getRightValue(rawValue);
12690
+ if (isNaN(value) || meta.data[index].hidden) {
12691
+ return;
12692
+ }
12693
+
12694
+ values[index] = values[index] || 0;
12695
+
12696
+ if (opts.relativePoints) {
12697
+ values[index] = 100;
12698
+ } else {
12699
+ // Don't need to split positive and negative since the log scale can't handle a 0 crossing
12700
+ values[index] += value;
12701
+ }
12702
+ });
12703
+ }
12704
+ });
12705
+
12706
+ helpers.each(valuesPerStack, function(valuesForType) {
12707
+ var minVal = helpers.min(valuesForType);
12708
+ var maxVal = helpers.max(valuesForType);
12709
+ me.min = me.min === null ? minVal : Math.min(me.min, minVal);
12710
+ me.max = me.max === null ? maxVal : Math.max(me.max, maxVal);
12711
+ });
12712
+
12713
+ } else {
12714
+ helpers.each(datasets, function(dataset, datasetIndex) {
12715
+ var meta = chart.getDatasetMeta(datasetIndex);
12716
+ if (chart.isDatasetVisible(datasetIndex) && IDMatches(meta)) {
12717
+ helpers.each(dataset.data, function(rawValue, index) {
12718
+ var value = +me.getRightValue(rawValue);
12719
+ if (isNaN(value) || meta.data[index].hidden) {
12720
+ return;
12721
+ }
12722
+
12723
+ if (me.min === null) {
12724
+ me.min = value;
12725
+ } else if (value < me.min) {
12726
+ me.min = value;
12727
+ }
12728
+
12729
+ if (me.max === null) {
12730
+ me.max = value;
12731
+ } else if (value > me.max) {
12732
+ me.max = value;
12733
+ }
12734
+
12735
+ if (value !== 0 && (me.minNotZero === null || value < me.minNotZero)) {
12736
+ me.minNotZero = value;
12737
+ }
12738
+ });
12739
+ }
12740
+ });
12741
+ }
12742
+
12743
+ me.min = valueOrDefault(tickOpts.min, me.min);
12744
+ me.max = valueOrDefault(tickOpts.max, me.max);
12745
+
12746
+ if (me.min === me.max) {
12747
+ if (me.min !== 0 && me.min !== null) {
12748
+ me.min = Math.pow(10, Math.floor(helpers.log10(me.min)) - 1);
12749
+ me.max = Math.pow(10, Math.floor(helpers.log10(me.max)) + 1);
12750
+ } else {
12751
+ me.min = 1;
12752
+ me.max = 10;
12753
+ }
12754
+ }
12755
+ },
12756
+ buildTicks: function() {
12757
+ var me = this;
12758
+ var opts = me.options;
12759
+ var tickOpts = opts.ticks;
12760
+
12761
+ var generationOptions = {
12762
+ min: tickOpts.min,
12763
+ max: tickOpts.max
12764
+ };
12765
+ var ticks = me.ticks = Ticks.generators.logarithmic(generationOptions, me);
12766
+
12767
+ if (!me.isHorizontal()) {
12768
+ // We are in a vertical orientation. The top value is the highest. So reverse the array
12769
+ ticks.reverse();
12770
+ }
12771
+
12772
+ // At this point, we need to update our max and min given the tick values since we have expanded the
12773
+ // range of the scale
12774
+ me.max = helpers.max(ticks);
12775
+ me.min = helpers.min(ticks);
12776
+
12777
+ if (tickOpts.reverse) {
12778
+ ticks.reverse();
12779
+
12780
+ me.start = me.max;
12781
+ me.end = me.min;
12782
+ } else {
12783
+ me.start = me.min;
12784
+ me.end = me.max;
12785
+ }
12786
+ },
12787
+ convertTicksToLabels: function() {
12788
+ this.tickValues = this.ticks.slice();
12789
+
12790
+ Chart.Scale.prototype.convertTicksToLabels.call(this);
12791
+ },
12792
+ // Get the correct tooltip label
12793
+ getLabelForIndex: function(index, datasetIndex) {
12794
+ return +this.getRightValue(this.chart.data.datasets[datasetIndex].data[index]);
12795
+ },
12796
+ getPixelForTick: function(index) {
12797
+ return this.getPixelForValue(this.tickValues[index]);
12798
+ },
12799
+ getPixelForValue: function(value) {
12800
+ var me = this;
12801
+ var start = me.start;
12802
+ var newVal = +me.getRightValue(value);
12803
+ var opts = me.options;
12804
+ var tickOpts = opts.ticks;
12805
+ var innerDimension, pixel, range;
12806
+
12807
+ if (me.isHorizontal()) {
12808
+ range = helpers.log10(me.end) - helpers.log10(start); // todo: if start === 0
12809
+ if (newVal === 0) {
12810
+ pixel = me.left;
12811
+ } else {
12812
+ innerDimension = me.width;
12813
+ pixel = me.left + (innerDimension / range * (helpers.log10(newVal) - helpers.log10(start)));
12814
+ }
12815
+ } else {
12816
+ // Bottom - top since pixels increase downward on a screen
12817
+ innerDimension = me.height;
12818
+ if (start === 0 && !tickOpts.reverse) {
12819
+ range = helpers.log10(me.end) - helpers.log10(me.minNotZero);
12820
+ if (newVal === start) {
12821
+ pixel = me.bottom;
12822
+ } else if (newVal === me.minNotZero) {
12823
+ pixel = me.bottom - innerDimension * 0.02;
12824
+ } else {
12825
+ pixel = me.bottom - innerDimension * 0.02 - (innerDimension * 0.98 / range * (helpers.log10(newVal) - helpers.log10(me.minNotZero)));
12826
+ }
12827
+ } else if (me.end === 0 && tickOpts.reverse) {
12828
+ range = helpers.log10(me.start) - helpers.log10(me.minNotZero);
12829
+ if (newVal === me.end) {
12830
+ pixel = me.top;
12831
+ } else if (newVal === me.minNotZero) {
12832
+ pixel = me.top + innerDimension * 0.02;
12833
+ } else {
12834
+ pixel = me.top + innerDimension * 0.02 + (innerDimension * 0.98 / range * (helpers.log10(newVal) - helpers.log10(me.minNotZero)));
12835
+ }
12836
+ } else if (newVal === 0) {
12837
+ pixel = tickOpts.reverse ? me.top : me.bottom;
12838
+ } else {
12839
+ range = helpers.log10(me.end) - helpers.log10(start);
12840
+ innerDimension = me.height;
12841
+ pixel = me.bottom - (innerDimension / range * (helpers.log10(newVal) - helpers.log10(start)));
12842
+ }
12843
+ }
12844
+ return pixel;
12845
+ },
12846
+ getValueForPixel: function(pixel) {
12847
+ var me = this;
12848
+ var range = helpers.log10(me.end) - helpers.log10(me.start);
12849
+ var value, innerDimension;
12850
+
12851
+ if (me.isHorizontal()) {
12852
+ innerDimension = me.width;
12853
+ value = me.start * Math.pow(10, (pixel - me.left) * range / innerDimension);
12854
+ } else { // todo: if start === 0
12855
+ innerDimension = me.height;
12856
+ value = Math.pow(10, (me.bottom - pixel) * range / innerDimension) / me.start;
12857
+ }
12858
+ return value;
12859
+ }
12860
+ });
12861
+ Chart.scaleService.registerScaleType('logarithmic', LogarithmicScale, defaultConfig);
12862
+
12863
+ };
12864
+
12865
+ },{"34":34,"45":45}],56:[function(require,module,exports){
12866
+ 'use strict';
12867
+
12868
+ var defaults = require(25);
12869
+ var helpers = require(45);
12870
+ var Ticks = require(34);
12871
+
12872
+ module.exports = function(Chart) {
12873
+
12874
+ var globalDefaults = defaults.global;
12875
+
12876
+ var defaultConfig = {
12877
+ display: true,
12878
+
12879
+ // Boolean - Whether to animate scaling the chart from the centre
12880
+ animate: true,
12881
+ position: 'chartArea',
12882
+
12883
+ angleLines: {
12884
+ display: true,
12885
+ color: 'rgba(0, 0, 0, 0.1)',
12886
+ lineWidth: 1
12887
+ },
12888
+
12889
+ gridLines: {
12890
+ circular: false
12891
+ },
12892
+
12893
+ // label settings
12894
+ ticks: {
12895
+ // Boolean - Show a backdrop to the scale label
12896
+ showLabelBackdrop: true,
12897
+
12898
+ // String - The colour of the label backdrop
12899
+ backdropColor: 'rgba(255,255,255,0.75)',
12900
+
12901
+ // Number - The backdrop padding above & below the label in pixels
12902
+ backdropPaddingY: 2,
12903
+
12904
+ // Number - The backdrop padding to the side of the label in pixels
12905
+ backdropPaddingX: 2,
12906
+
12907
+ callback: Ticks.formatters.linear
12908
+ },
12909
+
12910
+ pointLabels: {
12911
+ // Boolean - if true, show point labels
12912
+ display: true,
12913
+
12914
+ // Number - Point label font size in pixels
12915
+ fontSize: 10,
12916
+
12917
+ // Function - Used to convert point labels
12918
+ callback: function(label) {
12919
+ return label;
12920
+ }
12921
+ }
12922
+ };
12923
+
12924
+ function getValueCount(scale) {
12925
+ var opts = scale.options;
12926
+ return opts.angleLines.display || opts.pointLabels.display ? scale.chart.data.labels.length : 0;
12927
+ }
12928
+
12929
+ function getPointLabelFontOptions(scale) {
12930
+ var pointLabelOptions = scale.options.pointLabels;
12931
+ var fontSize = helpers.valueOrDefault(pointLabelOptions.fontSize, globalDefaults.defaultFontSize);
12932
+ var fontStyle = helpers.valueOrDefault(pointLabelOptions.fontStyle, globalDefaults.defaultFontStyle);
12933
+ var fontFamily = helpers.valueOrDefault(pointLabelOptions.fontFamily, globalDefaults.defaultFontFamily);
12934
+ var font = helpers.fontString(fontSize, fontStyle, fontFamily);
12935
+
12936
+ return {
12937
+ size: fontSize,
12938
+ style: fontStyle,
12939
+ family: fontFamily,
12940
+ font: font
12941
+ };
12942
+ }
12943
+
12944
+ function measureLabelSize(ctx, fontSize, label) {
12945
+ if (helpers.isArray(label)) {
12946
+ return {
12947
+ w: helpers.longestText(ctx, ctx.font, label),
12948
+ h: (label.length * fontSize) + ((label.length - 1) * 1.5 * fontSize)
12949
+ };
12950
+ }
12951
+
12952
+ return {
12953
+ w: ctx.measureText(label).width,
12954
+ h: fontSize
12955
+ };
12956
+ }
12957
+
12958
+ function determineLimits(angle, pos, size, min, max) {
12959
+ if (angle === min || angle === max) {
12960
+ return {
12961
+ start: pos - (size / 2),
12962
+ end: pos + (size / 2)
12963
+ };
12964
+ } else if (angle < min || angle > max) {
12965
+ return {
12966
+ start: pos - size - 5,
12967
+ end: pos
12968
+ };
12969
+ }
12970
+
12971
+ return {
12972
+ start: pos,
12973
+ end: pos + size + 5
12974
+ };
12975
+ }
12976
+
12977
+ /**
12978
+ * Helper function to fit a radial linear scale with point labels
12979
+ */
12980
+ function fitWithPointLabels(scale) {
12981
+ /*
12982
+ * Right, this is really confusing and there is a lot of maths going on here
12983
+ * The gist of the problem is here: https://gist.github.com/nnnick/696cc9c55f4b0beb8fe9
12984
+ *
12985
+ * Reaction: https://dl.dropboxusercontent.com/u/34601363/toomuchscience.gif
12986
+ *
12987
+ * Solution:
12988
+ *
12989
+ * We assume the radius of the polygon is half the size of the canvas at first
12990
+ * at each index we check if the text overlaps.
12991
+ *
12992
+ * Where it does, we store that angle and that index.
12993
+ *
12994
+ * After finding the largest index and angle we calculate how much we need to remove
12995
+ * from the shape radius to move the point inwards by that x.
12996
+ *
12997
+ * We average the left and right distances to get the maximum shape radius that can fit in the box
12998
+ * along with labels.
12999
+ *
13000
+ * Once we have that, we can find the centre point for the chart, by taking the x text protrusion
13001
+ * on each side, removing that from the size, halving it and adding the left x protrusion width.
13002
+ *
13003
+ * This will mean we have a shape fitted to the canvas, as large as it can be with the labels
13004
+ * and position it in the most space efficient manner
13005
+ *
13006
+ * https://dl.dropboxusercontent.com/u/34601363/yeahscience.gif
13007
+ */
13008
+
13009
+ var plFont = getPointLabelFontOptions(scale);
13010
+
13011
+ // Get maximum radius of the polygon. Either half the height (minus the text width) or half the width.
13012
+ // Use this to calculate the offset + change. - Make sure L/R protrusion is at least 0 to stop issues with centre points
13013
+ var largestPossibleRadius = Math.min(scale.height / 2, scale.width / 2);
13014
+ var furthestLimits = {
13015
+ r: scale.width,
13016
+ l: 0,
13017
+ t: scale.height,
13018
+ b: 0
13019
+ };
13020
+ var furthestAngles = {};
13021
+ var i, textSize, pointPosition;
13022
+
13023
+ scale.ctx.font = plFont.font;
13024
+ scale._pointLabelSizes = [];
13025
+
13026
+ var valueCount = getValueCount(scale);
13027
+ for (i = 0; i < valueCount; i++) {
13028
+ pointPosition = scale.getPointPosition(i, largestPossibleRadius);
13029
+ textSize = measureLabelSize(scale.ctx, plFont.size, scale.pointLabels[i] || '');
13030
+ scale._pointLabelSizes[i] = textSize;
13031
+
13032
+ // Add quarter circle to make degree 0 mean top of circle
13033
+ var angleRadians = scale.getIndexAngle(i);
13034
+ var angle = helpers.toDegrees(angleRadians) % 360;
13035
+ var hLimits = determineLimits(angle, pointPosition.x, textSize.w, 0, 180);
13036
+ var vLimits = determineLimits(angle, pointPosition.y, textSize.h, 90, 270);
13037
+
13038
+ if (hLimits.start < furthestLimits.l) {
13039
+ furthestLimits.l = hLimits.start;
13040
+ furthestAngles.l = angleRadians;
13041
+ }
13042
+
13043
+ if (hLimits.end > furthestLimits.r) {
13044
+ furthestLimits.r = hLimits.end;
13045
+ furthestAngles.r = angleRadians;
13046
+ }
13047
+
13048
+ if (vLimits.start < furthestLimits.t) {
13049
+ furthestLimits.t = vLimits.start;
13050
+ furthestAngles.t = angleRadians;
13051
+ }
13052
+
13053
+ if (vLimits.end > furthestLimits.b) {
13054
+ furthestLimits.b = vLimits.end;
13055
+ furthestAngles.b = angleRadians;
13056
+ }
13057
+ }
13058
+
13059
+ scale.setReductions(largestPossibleRadius, furthestLimits, furthestAngles);
13060
+ }
13061
+
13062
+ /**
13063
+ * Helper function to fit a radial linear scale with no point labels
13064
+ */
13065
+ function fit(scale) {
13066
+ var largestPossibleRadius = Math.min(scale.height / 2, scale.width / 2);
13067
+ scale.drawingArea = Math.round(largestPossibleRadius);
13068
+ scale.setCenterPoint(0, 0, 0, 0);
13069
+ }
13070
+
13071
+ function getTextAlignForAngle(angle) {
13072
+ if (angle === 0 || angle === 180) {
13073
+ return 'center';
13074
+ } else if (angle < 180) {
13075
+ return 'left';
13076
+ }
13077
+
13078
+ return 'right';
13079
+ }
13080
+
13081
+ function fillText(ctx, text, position, fontSize) {
13082
+ if (helpers.isArray(text)) {
13083
+ var y = position.y;
13084
+ var spacing = 1.5 * fontSize;
13085
+
13086
+ for (var i = 0; i < text.length; ++i) {
13087
+ ctx.fillText(text[i], position.x, y);
13088
+ y += spacing;
13089
+ }
13090
+ } else {
13091
+ ctx.fillText(text, position.x, position.y);
13092
+ }
13093
+ }
13094
+
13095
+ function adjustPointPositionForLabelHeight(angle, textSize, position) {
13096
+ if (angle === 90 || angle === 270) {
13097
+ position.y -= (textSize.h / 2);
13098
+ } else if (angle > 270 || angle < 90) {
13099
+ position.y -= textSize.h;
13100
+ }
13101
+ }
13102
+
13103
+ function drawPointLabels(scale) {
13104
+ var ctx = scale.ctx;
13105
+ var valueOrDefault = helpers.valueOrDefault;
13106
+ var opts = scale.options;
13107
+ var angleLineOpts = opts.angleLines;
13108
+ var pointLabelOpts = opts.pointLabels;
13109
+
13110
+ ctx.lineWidth = angleLineOpts.lineWidth;
13111
+ ctx.strokeStyle = angleLineOpts.color;
13112
+
13113
+ var outerDistance = scale.getDistanceFromCenterForValue(opts.ticks.reverse ? scale.min : scale.max);
13114
+
13115
+ // Point Label Font
13116
+ var plFont = getPointLabelFontOptions(scale);
13117
+
13118
+ ctx.textBaseline = 'top';
13119
+
13120
+ for (var i = getValueCount(scale) - 1; i >= 0; i--) {
13121
+ if (angleLineOpts.display) {
13122
+ var outerPosition = scale.getPointPosition(i, outerDistance);
13123
+ ctx.beginPath();
13124
+ ctx.moveTo(scale.xCenter, scale.yCenter);
13125
+ ctx.lineTo(outerPosition.x, outerPosition.y);
13126
+ ctx.stroke();
13127
+ ctx.closePath();
13128
+ }
13129
+
13130
+ if (pointLabelOpts.display) {
13131
+ // Extra 3px out for some label spacing
13132
+ var pointLabelPosition = scale.getPointPosition(i, outerDistance + 5);
13133
+
13134
+ // Keep this in loop since we may support array properties here
13135
+ var pointLabelFontColor = valueOrDefault(pointLabelOpts.fontColor, globalDefaults.defaultFontColor);
13136
+ ctx.font = plFont.font;
13137
+ ctx.fillStyle = pointLabelFontColor;
13138
+
13139
+ var angleRadians = scale.getIndexAngle(i);
13140
+ var angle = helpers.toDegrees(angleRadians);
13141
+ ctx.textAlign = getTextAlignForAngle(angle);
13142
+ adjustPointPositionForLabelHeight(angle, scale._pointLabelSizes[i], pointLabelPosition);
13143
+ fillText(ctx, scale.pointLabels[i] || '', pointLabelPosition, plFont.size);
13144
+ }
13145
+ }
13146
+ }
13147
+
13148
+ function drawRadiusLine(scale, gridLineOpts, radius, index) {
13149
+ var ctx = scale.ctx;
13150
+ ctx.strokeStyle = helpers.valueAtIndexOrDefault(gridLineOpts.color, index - 1);
13151
+ ctx.lineWidth = helpers.valueAtIndexOrDefault(gridLineOpts.lineWidth, index - 1);
13152
+
13153
+ if (scale.options.gridLines.circular) {
13154
+ // Draw circular arcs between the points
13155
+ ctx.beginPath();
13156
+ ctx.arc(scale.xCenter, scale.yCenter, radius, 0, Math.PI * 2);
13157
+ ctx.closePath();
13158
+ ctx.stroke();
13159
+ } else {
13160
+ // Draw straight lines connecting each index
13161
+ var valueCount = getValueCount(scale);
13162
+
13163
+ if (valueCount === 0) {
13164
+ return;
13165
+ }
13166
+
13167
+ ctx.beginPath();
13168
+ var pointPosition = scale.getPointPosition(0, radius);
13169
+ ctx.moveTo(pointPosition.x, pointPosition.y);
13170
+
13171
+ for (var i = 1; i < valueCount; i++) {
13172
+ pointPosition = scale.getPointPosition(i, radius);
13173
+ ctx.lineTo(pointPosition.x, pointPosition.y);
13174
+ }
13175
+
13176
+ ctx.closePath();
13177
+ ctx.stroke();
13178
+ }
13179
+ }
13180
+
13181
+ function numberOrZero(param) {
13182
+ return helpers.isNumber(param) ? param : 0;
13183
+ }
13184
+
13185
+ var LinearRadialScale = Chart.LinearScaleBase.extend({
13186
+ setDimensions: function() {
13187
+ var me = this;
13188
+ var opts = me.options;
13189
+ var tickOpts = opts.ticks;
13190
+ // Set the unconstrained dimension before label rotation
13191
+ me.width = me.maxWidth;
13192
+ me.height = me.maxHeight;
13193
+ me.xCenter = Math.round(me.width / 2);
13194
+ me.yCenter = Math.round(me.height / 2);
13195
+
13196
+ var minSize = helpers.min([me.height, me.width]);
13197
+ var tickFontSize = helpers.valueOrDefault(tickOpts.fontSize, globalDefaults.defaultFontSize);
13198
+ me.drawingArea = opts.display ? (minSize / 2) - (tickFontSize / 2 + tickOpts.backdropPaddingY) : (minSize / 2);
13199
+ },
13200
+ determineDataLimits: function() {
13201
+ var me = this;
13202
+ var chart = me.chart;
13203
+ var min = Number.POSITIVE_INFINITY;
13204
+ var max = Number.NEGATIVE_INFINITY;
13205
+
13206
+ helpers.each(chart.data.datasets, function(dataset, datasetIndex) {
13207
+ if (chart.isDatasetVisible(datasetIndex)) {
13208
+ var meta = chart.getDatasetMeta(datasetIndex);
13209
+
13210
+ helpers.each(dataset.data, function(rawValue, index) {
13211
+ var value = +me.getRightValue(rawValue);
13212
+ if (isNaN(value) || meta.data[index].hidden) {
13213
+ return;
13214
+ }
13215
+
13216
+ min = Math.min(value, min);
13217
+ max = Math.max(value, max);
13218
+ });
13219
+ }
13220
+ });
13221
+
13222
+ me.min = (min === Number.POSITIVE_INFINITY ? 0 : min);
13223
+ me.max = (max === Number.NEGATIVE_INFINITY ? 0 : max);
13224
+
13225
+ // Common base implementation to handle ticks.min, ticks.max, ticks.beginAtZero
13226
+ me.handleTickRangeOptions();
13227
+ },
13228
+ getTickLimit: function() {
13229
+ var tickOpts = this.options.ticks;
13230
+ var tickFontSize = helpers.valueOrDefault(tickOpts.fontSize, globalDefaults.defaultFontSize);
13231
+ return Math.min(tickOpts.maxTicksLimit ? tickOpts.maxTicksLimit : 11, Math.ceil(this.drawingArea / (1.5 * tickFontSize)));
13232
+ },
13233
+ convertTicksToLabels: function() {
13234
+ var me = this;
13235
+
13236
+ Chart.LinearScaleBase.prototype.convertTicksToLabels.call(me);
13237
+
13238
+ // Point labels
13239
+ me.pointLabels = me.chart.data.labels.map(me.options.pointLabels.callback, me);
13240
+ },
13241
+ getLabelForIndex: function(index, datasetIndex) {
13242
+ return +this.getRightValue(this.chart.data.datasets[datasetIndex].data[index]);
13243
+ },
13244
+ fit: function() {
13245
+ if (this.options.pointLabels.display) {
13246
+ fitWithPointLabels(this);
13247
+ } else {
13248
+ fit(this);
13249
+ }
13250
+ },
13251
+ /**
13252
+ * Set radius reductions and determine new radius and center point
13253
+ * @private
13254
+ */
13255
+ setReductions: function(largestPossibleRadius, furthestLimits, furthestAngles) {
13256
+ var me = this;
13257
+ var radiusReductionLeft = furthestLimits.l / Math.sin(furthestAngles.l);
13258
+ var radiusReductionRight = Math.max(furthestLimits.r - me.width, 0) / Math.sin(furthestAngles.r);
13259
+ var radiusReductionTop = -furthestLimits.t / Math.cos(furthestAngles.t);
13260
+ var radiusReductionBottom = -Math.max(furthestLimits.b - me.height, 0) / Math.cos(furthestAngles.b);
13261
+
13262
+ radiusReductionLeft = numberOrZero(radiusReductionLeft);
13263
+ radiusReductionRight = numberOrZero(radiusReductionRight);
13264
+ radiusReductionTop = numberOrZero(radiusReductionTop);
13265
+ radiusReductionBottom = numberOrZero(radiusReductionBottom);
13266
+
13267
+ me.drawingArea = Math.min(
13268
+ Math.round(largestPossibleRadius - (radiusReductionLeft + radiusReductionRight) / 2),
13269
+ Math.round(largestPossibleRadius - (radiusReductionTop + radiusReductionBottom) / 2));
13270
+ me.setCenterPoint(radiusReductionLeft, radiusReductionRight, radiusReductionTop, radiusReductionBottom);
13271
+ },
13272
+ setCenterPoint: function(leftMovement, rightMovement, topMovement, bottomMovement) {
13273
+ var me = this;
13274
+ var maxRight = me.width - rightMovement - me.drawingArea;
13275
+ var maxLeft = leftMovement + me.drawingArea;
13276
+ var maxTop = topMovement + me.drawingArea;
13277
+ var maxBottom = me.height - bottomMovement - me.drawingArea;
13278
+
13279
+ me.xCenter = Math.round(((maxLeft + maxRight) / 2) + me.left);
13280
+ me.yCenter = Math.round(((maxTop + maxBottom) / 2) + me.top);
13281
+ },
13282
+
13283
+ getIndexAngle: function(index) {
13284
+ var angleMultiplier = (Math.PI * 2) / getValueCount(this);
13285
+ var startAngle = this.chart.options && this.chart.options.startAngle ?
13286
+ this.chart.options.startAngle :
13287
+ 0;
13288
+
13289
+ var startAngleRadians = startAngle * Math.PI * 2 / 360;
13290
+
13291
+ // Start from the top instead of right, so remove a quarter of the circle
13292
+ return index * angleMultiplier + startAngleRadians;
13293
+ },
13294
+ getDistanceFromCenterForValue: function(value) {
13295
+ var me = this;
13296
+
13297
+ if (value === null) {
13298
+ return 0; // null always in center
13299
+ }
13300
+
13301
+ // Take into account half font size + the yPadding of the top value
13302
+ var scalingFactor = me.drawingArea / (me.max - me.min);
13303
+ if (me.options.ticks.reverse) {
13304
+ return (me.max - value) * scalingFactor;
13305
+ }
13306
+ return (value - me.min) * scalingFactor;
13307
+ },
13308
+ getPointPosition: function(index, distanceFromCenter) {
13309
+ var me = this;
13310
+ var thisAngle = me.getIndexAngle(index) - (Math.PI / 2);
13311
+ return {
13312
+ x: Math.round(Math.cos(thisAngle) * distanceFromCenter) + me.xCenter,
13313
+ y: Math.round(Math.sin(thisAngle) * distanceFromCenter) + me.yCenter
13314
+ };
13315
+ },
13316
+ getPointPositionForValue: function(index, value) {
13317
+ return this.getPointPosition(index, this.getDistanceFromCenterForValue(value));
13318
+ },
13319
+
13320
+ getBasePosition: function() {
13321
+ var me = this;
13322
+ var min = me.min;
13323
+ var max = me.max;
13324
+
13325
+ return me.getPointPositionForValue(0,
13326
+ me.beginAtZero ? 0 :
13327
+ min < 0 && max < 0 ? max :
13328
+ min > 0 && max > 0 ? min :
13329
+ 0);
13330
+ },
13331
+
13332
+ draw: function() {
13333
+ var me = this;
13334
+ var opts = me.options;
13335
+ var gridLineOpts = opts.gridLines;
13336
+ var tickOpts = opts.ticks;
13337
+ var valueOrDefault = helpers.valueOrDefault;
13338
+
13339
+ if (opts.display) {
13340
+ var ctx = me.ctx;
13341
+ var startAngle = this.getIndexAngle(0);
13342
+
13343
+ // Tick Font
13344
+ var tickFontSize = valueOrDefault(tickOpts.fontSize, globalDefaults.defaultFontSize);
13345
+ var tickFontStyle = valueOrDefault(tickOpts.fontStyle, globalDefaults.defaultFontStyle);
13346
+ var tickFontFamily = valueOrDefault(tickOpts.fontFamily, globalDefaults.defaultFontFamily);
13347
+ var tickLabelFont = helpers.fontString(tickFontSize, tickFontStyle, tickFontFamily);
13348
+
13349
+ helpers.each(me.ticks, function(label, index) {
13350
+ // Don't draw a centre value (if it is minimum)
13351
+ if (index > 0 || tickOpts.reverse) {
13352
+ var yCenterOffset = me.getDistanceFromCenterForValue(me.ticksAsNumbers[index]);
13353
+
13354
+ // Draw circular lines around the scale
13355
+ if (gridLineOpts.display && index !== 0) {
13356
+ drawRadiusLine(me, gridLineOpts, yCenterOffset, index);
13357
+ }
13358
+
13359
+ if (tickOpts.display) {
13360
+ var tickFontColor = valueOrDefault(tickOpts.fontColor, globalDefaults.defaultFontColor);
13361
+ ctx.font = tickLabelFont;
13362
+
13363
+ ctx.save();
13364
+ ctx.translate(me.xCenter, me.yCenter);
13365
+ ctx.rotate(startAngle);
13366
+
13367
+ if (tickOpts.showLabelBackdrop) {
13368
+ var labelWidth = ctx.measureText(label).width;
13369
+ ctx.fillStyle = tickOpts.backdropColor;
13370
+ ctx.fillRect(
13371
+ -labelWidth / 2 - tickOpts.backdropPaddingX,
13372
+ -yCenterOffset - tickFontSize / 2 - tickOpts.backdropPaddingY,
13373
+ labelWidth + tickOpts.backdropPaddingX * 2,
13374
+ tickFontSize + tickOpts.backdropPaddingY * 2
13375
+ );
13376
+ }
13377
+
13378
+ ctx.textAlign = 'center';
13379
+ ctx.textBaseline = 'middle';
13380
+ ctx.fillStyle = tickFontColor;
13381
+ ctx.fillText(label, 0, -yCenterOffset);
13382
+ ctx.restore();
13383
+ }
13384
+ }
13385
+ });
13386
+
13387
+ if (opts.angleLines.display || opts.pointLabels.display) {
13388
+ drawPointLabels(me);
13389
+ }
13390
+ }
13391
+ }
13392
+ });
13393
+ Chart.scaleService.registerScaleType('radialLinear', LinearRadialScale, defaultConfig);
13394
+
13395
+ };
13396
+
13397
+ },{"25":25,"34":34,"45":45}],57:[function(require,module,exports){
13398
+ /* global window: false */
13399
+ 'use strict';
13400
+
13401
+ var moment = require(1);
13402
+ moment = typeof moment === 'function' ? moment : window.moment;
13403
+
13404
+ var defaults = require(25);
13405
+ var helpers = require(45);
13406
+
13407
+ // Integer constants are from the ES6 spec.
13408
+ var MIN_INTEGER = Number.MIN_SAFE_INTEGER || -9007199254740991;
13409
+ var MAX_INTEGER = Number.MAX_SAFE_INTEGER || 9007199254740991;
13410
+
13411
+ var INTERVALS = {
13412
+ millisecond: {
13413
+ common: true,
13414
+ size: 1,
13415
+ steps: [1, 2, 5, 10, 20, 50, 100, 250, 500]
13416
+ },
13417
+ second: {
13418
+ common: true,
13419
+ size: 1000,
13420
+ steps: [1, 2, 5, 10, 30]
13421
+ },
13422
+ minute: {
13423
+ common: true,
13424
+ size: 60000,
13425
+ steps: [1, 2, 5, 10, 30]
13426
+ },
13427
+ hour: {
13428
+ common: true,
13429
+ size: 3600000,
13430
+ steps: [1, 2, 3, 6, 12]
13431
+ },
13432
+ day: {
13433
+ common: true,
13434
+ size: 86400000,
13435
+ steps: [1, 2, 5]
13436
+ },
13437
+ week: {
13438
+ common: false,
13439
+ size: 604800000,
13440
+ steps: [1, 2, 3, 4]
13441
+ },
13442
+ month: {
13443
+ common: true,
13444
+ size: 2.628e9,
13445
+ steps: [1, 2, 3]
13446
+ },
13447
+ quarter: {
13448
+ common: false,
13449
+ size: 7.884e9,
13450
+ steps: [1, 2, 3, 4]
13451
+ },
13452
+ year: {
13453
+ common: true,
13454
+ size: 3.154e10
13455
+ }
13456
+ };
13457
+
13458
+ var UNITS = Object.keys(INTERVALS);
13459
+
13460
+ function sorter(a, b) {
13461
+ return a - b;
13462
+ }
13463
+
13464
+ function arrayUnique(items) {
13465
+ var hash = {};
13466
+ var out = [];
13467
+ var i, ilen, item;
13468
+
13469
+ for (i = 0, ilen = items.length; i < ilen; ++i) {
13470
+ item = items[i];
13471
+ if (!hash[item]) {
13472
+ hash[item] = true;
13473
+ out.push(item);
13474
+ }
13475
+ }
13476
+
13477
+ return out;
13478
+ }
13479
+
13480
+ /**
13481
+ * Returns an array of {time, pos} objects used to interpolate a specific `time` or position
13482
+ * (`pos`) on the scale, by searching entries before and after the requested value. `pos` is
13483
+ * a decimal between 0 and 1: 0 being the start of the scale (left or top) and 1 the other
13484
+ * extremity (left + width or top + height). Note that it would be more optimized to directly
13485
+ * store pre-computed pixels, but the scale dimensions are not guaranteed at the time we need
13486
+ * to create the lookup table. The table ALWAYS contains at least two items: min and max.
13487
+ *
13488
+ * @param {Number[]} timestamps - timestamps sorted from lowest to highest.
13489
+ * @param {String} distribution - If 'linear', timestamps will be spread linearly along the min
13490
+ * and max range, so basically, the table will contains only two items: {min, 0} and {max, 1}.
13491
+ * If 'series', timestamps will be positioned at the same distance from each other. In this
13492
+ * case, only timestamps that break the time linearity are registered, meaning that in the
13493
+ * best case, all timestamps are linear, the table contains only min and max.
13494
+ */
13495
+ function buildLookupTable(timestamps, min, max, distribution) {
13496
+ if (distribution === 'linear' || !timestamps.length) {
13497
+ return [
13498
+ {time: min, pos: 0},
13499
+ {time: max, pos: 1}
13500
+ ];
13501
+ }
13502
+
13503
+ var table = [];
13504
+ var items = [min];
13505
+ var i, ilen, prev, curr, next;
13506
+
13507
+ for (i = 0, ilen = timestamps.length; i < ilen; ++i) {
13508
+ curr = timestamps[i];
13509
+ if (curr > min && curr < max) {
13510
+ items.push(curr);
13511
+ }
13512
+ }
13513
+
13514
+ items.push(max);
13515
+
13516
+ for (i = 0, ilen = items.length; i < ilen; ++i) {
13517
+ next = items[i + 1];
13518
+ prev = items[i - 1];
13519
+ curr = items[i];
13520
+
13521
+ // only add points that breaks the scale linearity
13522
+ if (prev === undefined || next === undefined || Math.round((next + prev) / 2) !== curr) {
13523
+ table.push({time: curr, pos: i / (ilen - 1)});
13524
+ }
13525
+ }
13526
+
13527
+ return table;
13528
+ }
13529
+
13530
+ // @see adapted from http://www.anujgakhar.com/2014/03/01/binary-search-in-javascript/
13531
+ function lookup(table, key, value) {
13532
+ var lo = 0;
13533
+ var hi = table.length - 1;
13534
+ var mid, i0, i1;
13535
+
13536
+ while (lo >= 0 && lo <= hi) {
13537
+ mid = (lo + hi) >> 1;
13538
+ i0 = table[mid - 1] || null;
13539
+ i1 = table[mid];
13540
+
13541
+ if (!i0) {
13542
+ // given value is outside table (before first item)
13543
+ return {lo: null, hi: i1};
13544
+ } else if (i1[key] < value) {
13545
+ lo = mid + 1;
13546
+ } else if (i0[key] > value) {
13547
+ hi = mid - 1;
13548
+ } else {
13549
+ return {lo: i0, hi: i1};
13550
+ }
13551
+ }
13552
+
13553
+ // given value is outside table (after last item)
13554
+ return {lo: i1, hi: null};
13555
+ }
13556
+
13557
+ /**
13558
+ * Linearly interpolates the given source `value` using the table items `skey` values and
13559
+ * returns the associated `tkey` value. For example, interpolate(table, 'time', 42, 'pos')
13560
+ * returns the position for a timestamp equal to 42. If value is out of bounds, values at
13561
+ * index [0, 1] or [n - 1, n] are used for the interpolation.
13562
+ */
13563
+ function interpolate(table, skey, sval, tkey) {
13564
+ var range = lookup(table, skey, sval);
13565
+
13566
+ // Note: the lookup table ALWAYS contains at least 2 items (min and max)
13567
+ var prev = !range.lo ? table[0] : !range.hi ? table[table.length - 2] : range.lo;
13568
+ var next = !range.lo ? table[1] : !range.hi ? table[table.length - 1] : range.hi;
13569
+
13570
+ var span = next[skey] - prev[skey];
13571
+ var ratio = span ? (sval - prev[skey]) / span : 0;
13572
+ var offset = (next[tkey] - prev[tkey]) * ratio;
13573
+
13574
+ return prev[tkey] + offset;
13575
+ }
13576
+
13577
+ /**
13578
+ * Convert the given value to a moment object using the given time options.
13579
+ * @see http://momentjs.com/docs/#/parsing/
13580
+ */
13581
+ function momentify(value, options) {
13582
+ var parser = options.parser;
13583
+ var format = options.parser || options.format;
13584
+
13585
+ if (typeof parser === 'function') {
13586
+ return parser(value);
13587
+ }
13588
+
13589
+ if (typeof value === 'string' && typeof format === 'string') {
13590
+ return moment(value, format);
13591
+ }
13592
+
13593
+ if (!(value instanceof moment)) {
13594
+ value = moment(value);
13595
+ }
13596
+
13597
+ if (value.isValid()) {
13598
+ return value;
13599
+ }
13600
+
13601
+ // Labels are in an incompatible moment format and no `parser` has been provided.
13602
+ // The user might still use the deprecated `format` option to convert his inputs.
13603
+ if (typeof format === 'function') {
13604
+ return format(value);
13605
+ }
13606
+
13607
+ return value;
13608
+ }
13609
+
13610
+ function parse(input, scale) {
13611
+ if (helpers.isNullOrUndef(input)) {
13612
+ return null;
13613
+ }
13614
+
13615
+ var options = scale.options.time;
13616
+ var value = momentify(scale.getRightValue(input), options);
13617
+ if (!value.isValid()) {
13618
+ return null;
13619
+ }
13620
+
13621
+ if (options.round) {
13622
+ value.startOf(options.round);
13623
+ }
13624
+
13625
+ return value.valueOf();
13626
+ }
13627
+
13628
+ /**
13629
+ * Returns the number of unit to skip to be able to display up to `capacity` number of ticks
13630
+ * in `unit` for the given `min` / `max` range and respecting the interval steps constraints.
13631
+ */
13632
+ function determineStepSize(min, max, unit, capacity) {
13633
+ var range = max - min;
13634
+ var interval = INTERVALS[unit];
13635
+ var milliseconds = interval.size;
13636
+ var steps = interval.steps;
13637
+ var i, ilen, factor;
13638
+
13639
+ if (!steps) {
13640
+ return Math.ceil(range / ((capacity || 1) * milliseconds));
13641
+ }
13642
+
13643
+ for (i = 0, ilen = steps.length; i < ilen; ++i) {
13644
+ factor = steps[i];
13645
+ if (Math.ceil(range / (milliseconds * factor)) <= capacity) {
13646
+ break;
13647
+ }
13648
+ }
13649
+
13650
+ return factor;
13651
+ }
13652
+
13653
+ /**
13654
+ * Figures out what unit results in an appropriate number of auto-generated ticks
13655
+ */
13656
+ function determineUnitForAutoTicks(minUnit, min, max, capacity) {
13657
+ var ilen = UNITS.length;
13658
+ var i, interval, factor;
13659
+
13660
+ for (i = UNITS.indexOf(minUnit); i < ilen - 1; ++i) {
13661
+ interval = INTERVALS[UNITS[i]];
13662
+ factor = interval.steps ? interval.steps[interval.steps.length - 1] : MAX_INTEGER;
13663
+
13664
+ if (interval.common && Math.ceil((max - min) / (factor * interval.size)) <= capacity) {
13665
+ return UNITS[i];
13666
+ }
13667
+ }
13668
+
13669
+ return UNITS[ilen - 1];
13670
+ }
13671
+
13672
+ /**
13673
+ * Figures out what unit to format a set of ticks with
13674
+ */
13675
+ function determineUnitForFormatting(ticks, minUnit, min, max) {
13676
+ var duration = moment.duration(moment(max).diff(moment(min)));
13677
+ var ilen = UNITS.length;
13678
+ var i, unit;
13679
+
13680
+ for (i = ilen - 1; i >= UNITS.indexOf(minUnit); i--) {
13681
+ unit = UNITS[i];
13682
+ if (INTERVALS[unit].common && duration.as(unit) >= ticks.length) {
13683
+ return unit;
13684
+ }
13685
+ }
13686
+
13687
+ return UNITS[minUnit ? UNITS.indexOf(minUnit) : 0];
13688
+ }
13689
+
13690
+ function determineMajorUnit(unit) {
13691
+ for (var i = UNITS.indexOf(unit) + 1, ilen = UNITS.length; i < ilen; ++i) {
13692
+ if (INTERVALS[UNITS[i]].common) {
13693
+ return UNITS[i];
13694
+ }
13695
+ }
13696
+ }
13697
+
13698
+ /**
13699
+ * Generates a maximum of `capacity` timestamps between min and max, rounded to the
13700
+ * `minor` unit, aligned on the `major` unit and using the given scale time `options`.
13701
+ * Important: this method can return ticks outside the min and max range, it's the
13702
+ * responsibility of the calling code to clamp values if needed.
13703
+ */
13704
+ function generate(min, max, capacity, options) {
13705
+ var timeOpts = options.time;
13706
+ var minor = timeOpts.unit || determineUnitForAutoTicks(timeOpts.minUnit, min, max, capacity);
13707
+ var major = determineMajorUnit(minor);
13708
+ var stepSize = helpers.valueOrDefault(timeOpts.stepSize, timeOpts.unitStepSize);
13709
+ var weekday = minor === 'week' ? timeOpts.isoWeekday : false;
13710
+ var majorTicksEnabled = options.ticks.major.enabled;
13711
+ var interval = INTERVALS[minor];
13712
+ var first = moment(min);
13713
+ var last = moment(max);
13714
+ var ticks = [];
13715
+ var time;
13716
+
13717
+ if (!stepSize) {
13718
+ stepSize = determineStepSize(min, max, minor, capacity);
13719
+ }
13720
+
13721
+ // For 'week' unit, handle the first day of week option
13722
+ if (weekday) {
13723
+ first = first.isoWeekday(weekday);
13724
+ last = last.isoWeekday(weekday);
13725
+ }
13726
+
13727
+ // Align first/last ticks on unit
13728
+ first = first.startOf(weekday ? 'day' : minor);
13729
+ last = last.startOf(weekday ? 'day' : minor);
13730
+
13731
+ // Make sure that the last tick include max
13732
+ if (last < max) {
13733
+ last.add(1, minor);
13734
+ }
13735
+
13736
+ time = moment(first);
13737
+
13738
+ if (majorTicksEnabled && major && !weekday && !timeOpts.round) {
13739
+ // Align the first tick on the previous `minor` unit aligned on the `major` unit:
13740
+ // we first aligned time on the previous `major` unit then add the number of full
13741
+ // stepSize there is between first and the previous major time.
13742
+ time.startOf(major);
13743
+ time.add(~~((first - time) / (interval.size * stepSize)) * stepSize, minor);
13744
+ }
13745
+
13746
+ for (; time < last; time.add(stepSize, minor)) {
13747
+ ticks.push(+time);
13748
+ }
13749
+
13750
+ ticks.push(+time);
13751
+
13752
+ return ticks;
13753
+ }
13754
+
13755
+ /**
13756
+ * Returns the right and left offsets from edges in the form of {left, right}.
13757
+ * Offsets are added when the `offset` option is true.
13758
+ */
13759
+ function computeOffsets(table, ticks, min, max, options) {
13760
+ var left = 0;
13761
+ var right = 0;
13762
+ var upper, lower;
13763
+
13764
+ if (options.offset && ticks.length) {
13765
+ if (!options.time.min) {
13766
+ upper = ticks.length > 1 ? ticks[1] : max;
13767
+ lower = ticks[0];
13768
+ left = (
13769
+ interpolate(table, 'time', upper, 'pos') -
13770
+ interpolate(table, 'time', lower, 'pos')
13771
+ ) / 2;
13772
+ }
13773
+ if (!options.time.max) {
13774
+ upper = ticks[ticks.length - 1];
13775
+ lower = ticks.length > 1 ? ticks[ticks.length - 2] : min;
13776
+ right = (
13777
+ interpolate(table, 'time', upper, 'pos') -
13778
+ interpolate(table, 'time', lower, 'pos')
13779
+ ) / 2;
13780
+ }
13781
+ }
13782
+
13783
+ return {left: left, right: right};
13784
+ }
13785
+
13786
+ function ticksFromTimestamps(values, majorUnit) {
13787
+ var ticks = [];
13788
+ var i, ilen, value, major;
13789
+
13790
+ for (i = 0, ilen = values.length; i < ilen; ++i) {
13791
+ value = values[i];
13792
+ major = majorUnit ? value === +moment(value).startOf(majorUnit) : false;
13793
+
13794
+ ticks.push({
13795
+ value: value,
13796
+ major: major
13797
+ });
13798
+ }
13799
+
13800
+ return ticks;
13801
+ }
13802
+
13803
+ module.exports = function(Chart) {
13804
+
13805
+ var defaultConfig = {
13806
+ position: 'bottom',
13807
+
13808
+ /**
13809
+ * Data distribution along the scale:
13810
+ * - 'linear': data are spread according to their time (distances can vary),
13811
+ * - 'series': data are spread at the same distance from each other.
13812
+ * @see https://github.com/chartjs/Chart.js/pull/4507
13813
+ * @since 2.7.0
13814
+ */
13815
+ distribution: 'linear',
13816
+
13817
+ /**
13818
+ * Scale boundary strategy (bypassed by min/max time options)
13819
+ * - `data`: make sure data are fully visible, ticks outside are removed
13820
+ * - `ticks`: make sure ticks are fully visible, data outside are truncated
13821
+ * @see https://github.com/chartjs/Chart.js/pull/4556
13822
+ * @since 2.7.0
13823
+ */
13824
+ bounds: 'data',
13825
+
13826
+ time: {
13827
+ 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
13828
+ format: false, // DEPRECATED false == date objects, moment object, callback or a pattern string from http://momentjs.com/docs/#/parsing/string-format/
13829
+ unit: false, // false == automatic or override with week, month, year, etc.
13830
+ round: false, // none, or override with week, month, year, etc.
13831
+ displayFormat: false, // DEPRECATED
13832
+ isoWeekday: false, // override week start day - see http://momentjs.com/docs/#/get-set/iso-weekday/
13833
+ minUnit: 'millisecond',
13834
+
13835
+ // defaults to unit's corresponding unitFormat below or override using pattern string from http://momentjs.com/docs/#/displaying/format/
13836
+ displayFormats: {
13837
+ millisecond: 'h:mm:ss.SSS a', // 11:20:01.123 AM,
13838
+ second: 'h:mm:ss a', // 11:20:01 AM
13839
+ minute: 'h:mm a', // 11:20 AM
13840
+ hour: 'hA', // 5PM
13841
+ day: 'MMM D', // Sep 4
13842
+ week: 'll', // Week 46, or maybe "[W]WW - YYYY" ?
13843
+ month: 'MMM YYYY', // Sept 2015
13844
+ quarter: '[Q]Q - YYYY', // Q3
13845
+ year: 'YYYY' // 2015
13846
+ },
13847
+ },
13848
+ ticks: {
13849
+ autoSkip: false,
13850
+
13851
+ /**
13852
+ * Ticks generation input values:
13853
+ * - 'auto': generates "optimal" ticks based on scale size and time options.
13854
+ * - 'data': generates ticks from data (including labels from data {t|x|y} objects).
13855
+ * - 'labels': generates ticks from user given `data.labels` values ONLY.
13856
+ * @see https://github.com/chartjs/Chart.js/pull/4507
13857
+ * @since 2.7.0
13858
+ */
13859
+ source: 'auto',
13860
+
13861
+ major: {
13862
+ enabled: false
13863
+ }
13864
+ }
13865
+ };
13866
+
13867
+ var TimeScale = Chart.Scale.extend({
13868
+ initialize: function() {
13869
+ if (!moment) {
13870
+ 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');
13871
+ }
13872
+
13873
+ this.mergeTicksOptions();
13874
+
13875
+ Chart.Scale.prototype.initialize.call(this);
13876
+ },
13877
+
13878
+ update: function() {
13879
+ var me = this;
13880
+ var options = me.options;
13881
+
13882
+ // DEPRECATIONS: output a message only one time per update
13883
+ if (options.time && options.time.format) {
13884
+ console.warn('options.time.format is deprecated and replaced by options.time.parser.');
13885
+ }
13886
+
13887
+ return Chart.Scale.prototype.update.apply(me, arguments);
13888
+ },
13889
+
13890
+ /**
13891
+ * Allows data to be referenced via 't' attribute
13892
+ */
13893
+ getRightValue: function(rawValue) {
13894
+ if (rawValue && rawValue.t !== undefined) {
13895
+ rawValue = rawValue.t;
13896
+ }
13897
+ return Chart.Scale.prototype.getRightValue.call(this, rawValue);
13898
+ },
13899
+
13900
+ determineDataLimits: function() {
13901
+ var me = this;
13902
+ var chart = me.chart;
13903
+ var timeOpts = me.options.time;
13904
+ var min = MAX_INTEGER;
13905
+ var max = MIN_INTEGER;
13906
+ var timestamps = [];
13907
+ var datasets = [];
13908
+ var labels = [];
13909
+ var i, j, ilen, jlen, data, timestamp;
13910
+
13911
+ // Convert labels to timestamps
13912
+ for (i = 0, ilen = chart.data.labels.length; i < ilen; ++i) {
13913
+ labels.push(parse(chart.data.labels[i], me));
13914
+ }
13915
+
13916
+ // Convert data to timestamps
13917
+ for (i = 0, ilen = (chart.data.datasets || []).length; i < ilen; ++i) {
13918
+ if (chart.isDatasetVisible(i)) {
13919
+ data = chart.data.datasets[i].data;
13920
+
13921
+ // Let's consider that all data have the same format.
13922
+ if (helpers.isObject(data[0])) {
13923
+ datasets[i] = [];
13924
+
13925
+ for (j = 0, jlen = data.length; j < jlen; ++j) {
13926
+ timestamp = parse(data[j], me);
13927
+ timestamps.push(timestamp);
13928
+ datasets[i][j] = timestamp;
13929
+ }
13930
+ } else {
13931
+ timestamps.push.apply(timestamps, labels);
13932
+ datasets[i] = labels.slice(0);
13933
+ }
13934
+ } else {
13935
+ datasets[i] = [];
13936
+ }
13937
+ }
13938
+
13939
+ if (labels.length) {
13940
+ // Sort labels **after** data have been converted
13941
+ labels = arrayUnique(labels).sort(sorter);
13942
+ min = Math.min(min, labels[0]);
13943
+ max = Math.max(max, labels[labels.length - 1]);
13944
+ }
13945
+
13946
+ if (timestamps.length) {
13947
+ timestamps = arrayUnique(timestamps).sort(sorter);
13948
+ min = Math.min(min, timestamps[0]);
13949
+ max = Math.max(max, timestamps[timestamps.length - 1]);
13950
+ }
13951
+
13952
+ min = parse(timeOpts.min, me) || min;
13953
+ max = parse(timeOpts.max, me) || max;
13954
+
13955
+ // In case there is no valid min/max, let's use today limits
13956
+ min = min === MAX_INTEGER ? +moment().startOf('day') : min;
13957
+ max = max === MIN_INTEGER ? +moment().endOf('day') + 1 : max;
13958
+
13959
+ // Make sure that max is strictly higher than min (required by the lookup table)
13960
+ me.min = Math.min(min, max);
13961
+ me.max = Math.max(min + 1, max);
13962
+
13963
+ // PRIVATE
13964
+ me._horizontal = me.isHorizontal();
13965
+ me._table = [];
13966
+ me._timestamps = {
13967
+ data: timestamps,
13968
+ datasets: datasets,
13969
+ labels: labels
13970
+ };
13971
+ },
13972
+
13973
+ buildTicks: function() {
13974
+ var me = this;
13975
+ var min = me.min;
13976
+ var max = me.max;
13977
+ var options = me.options;
13978
+ var timeOpts = options.time;
13979
+ var timestamps = [];
13980
+ var ticks = [];
13981
+ var i, ilen, timestamp;
13982
+
13983
+ switch (options.ticks.source) {
13984
+ case 'data':
13985
+ timestamps = me._timestamps.data;
13986
+ break;
13987
+ case 'labels':
13988
+ timestamps = me._timestamps.labels;
13989
+ break;
13990
+ case 'auto':
13991
+ default:
13992
+ timestamps = generate(min, max, me.getLabelCapacity(min), options);
13993
+ }
13994
+
13995
+ if (options.bounds === 'ticks' && timestamps.length) {
13996
+ min = timestamps[0];
13997
+ max = timestamps[timestamps.length - 1];
13998
+ }
13999
+
14000
+ // Enforce limits with user min/max options
14001
+ min = parse(timeOpts.min, me) || min;
14002
+ max = parse(timeOpts.max, me) || max;
14003
+
14004
+ // Remove ticks outside the min/max range
14005
+ for (i = 0, ilen = timestamps.length; i < ilen; ++i) {
14006
+ timestamp = timestamps[i];
14007
+ if (timestamp >= min && timestamp <= max) {
14008
+ ticks.push(timestamp);
14009
+ }
14010
+ }
14011
+
14012
+ me.min = min;
14013
+ me.max = max;
14014
+
14015
+ // PRIVATE
14016
+ me._unit = timeOpts.unit || determineUnitForFormatting(ticks, timeOpts.minUnit, me.min, me.max);
14017
+ me._majorUnit = determineMajorUnit(me._unit);
14018
+ me._table = buildLookupTable(me._timestamps.data, min, max, options.distribution);
14019
+ me._offsets = computeOffsets(me._table, ticks, min, max, options);
14020
+
14021
+ return ticksFromTimestamps(ticks, me._majorUnit);
14022
+ },
14023
+
14024
+ getLabelForIndex: function(index, datasetIndex) {
14025
+ var me = this;
14026
+ var data = me.chart.data;
14027
+ var timeOpts = me.options.time;
14028
+ var label = data.labels && index < data.labels.length ? data.labels[index] : '';
14029
+ var value = data.datasets[datasetIndex].data[index];
14030
+
14031
+ if (helpers.isObject(value)) {
14032
+ label = me.getRightValue(value);
14033
+ }
14034
+ if (timeOpts.tooltipFormat) {
14035
+ label = momentify(label, timeOpts).format(timeOpts.tooltipFormat);
14036
+ }
14037
+
14038
+ return label;
14039
+ },
14040
+
14041
+ /**
14042
+ * Function to format an individual tick mark
14043
+ * @private
14044
+ */
14045
+ tickFormatFunction: function(tick, index, ticks, formatOverride) {
14046
+ var me = this;
14047
+ var options = me.options;
14048
+ var time = tick.valueOf();
14049
+ var formats = options.time.displayFormats;
14050
+ var minorFormat = formats[me._unit];
14051
+ var majorUnit = me._majorUnit;
14052
+ var majorFormat = formats[majorUnit];
14053
+ var majorTime = tick.clone().startOf(majorUnit).valueOf();
14054
+ var majorTickOpts = options.ticks.major;
14055
+ var major = majorTickOpts.enabled && majorUnit && majorFormat && time === majorTime;
14056
+ var label = tick.format(formatOverride ? formatOverride : major ? majorFormat : minorFormat);
14057
+ var tickOpts = major ? majorTickOpts : options.ticks.minor;
14058
+ var formatter = helpers.valueOrDefault(tickOpts.callback, tickOpts.userCallback);
14059
+
14060
+ return formatter ? formatter(label, index, ticks) : label;
14061
+ },
14062
+
14063
+ convertTicksToLabels: function(ticks) {
14064
+ var labels = [];
14065
+ var i, ilen;
14066
+
14067
+ for (i = 0, ilen = ticks.length; i < ilen; ++i) {
14068
+ labels.push(this.tickFormatFunction(moment(ticks[i].value), i, ticks));
14069
+ }
14070
+
14071
+ return labels;
14072
+ },
14073
+
14074
+ /**
14075
+ * @private
14076
+ */
14077
+ getPixelForOffset: function(time) {
14078
+ var me = this;
14079
+ var size = me._horizontal ? me.width : me.height;
14080
+ var start = me._horizontal ? me.left : me.top;
14081
+ var pos = interpolate(me._table, 'time', time, 'pos');
14082
+
14083
+ return start + size * (me._offsets.left + pos) / (me._offsets.left + 1 + me._offsets.right);
14084
+ },
14085
+
14086
+ getPixelForValue: function(value, index, datasetIndex) {
14087
+ var me = this;
14088
+ var time = null;
14089
+
14090
+ if (index !== undefined && datasetIndex !== undefined) {
14091
+ time = me._timestamps.datasets[datasetIndex][index];
14092
+ }
14093
+
14094
+ if (time === null) {
14095
+ time = parse(value, me);
14096
+ }
14097
+
14098
+ if (time !== null) {
14099
+ return me.getPixelForOffset(time);
14100
+ }
14101
+ },
14102
+
14103
+ getPixelForTick: function(index) {
14104
+ var ticks = this.getTicks();
14105
+ return index >= 0 && index < ticks.length ?
14106
+ this.getPixelForOffset(ticks[index].value) :
14107
+ null;
14108
+ },
14109
+
14110
+ getValueForPixel: function(pixel) {
14111
+ var me = this;
14112
+ var size = me._horizontal ? me.width : me.height;
14113
+ var start = me._horizontal ? me.left : me.top;
14114
+ var pos = (size ? (pixel - start) / size : 0) * (me._offsets.left + 1 + me._offsets.left) - me._offsets.right;
14115
+ var time = interpolate(me._table, 'pos', pos, 'time');
14116
+
14117
+ return moment(time);
14118
+ },
14119
+
14120
+ /**
14121
+ * Crude approximation of what the label width might be
14122
+ * @private
14123
+ */
14124
+ getLabelWidth: function(label) {
14125
+ var me = this;
14126
+ var ticksOpts = me.options.ticks;
14127
+ var tickLabelWidth = me.ctx.measureText(label).width;
14128
+ var angle = helpers.toRadians(ticksOpts.maxRotation);
14129
+ var cosRotation = Math.cos(angle);
14130
+ var sinRotation = Math.sin(angle);
14131
+ var tickFontSize = helpers.valueOrDefault(ticksOpts.fontSize, defaults.global.defaultFontSize);
14132
+
14133
+ return (tickLabelWidth * cosRotation) + (tickFontSize * sinRotation);
14134
+ },
14135
+
14136
+ /**
14137
+ * @private
14138
+ */
14139
+ getLabelCapacity: function(exampleTime) {
14140
+ var me = this;
14141
+
14142
+ var formatOverride = me.options.time.displayFormats.millisecond; // Pick the longest format for guestimation
14143
+
14144
+ var exampleLabel = me.tickFormatFunction(moment(exampleTime), 0, [], formatOverride);
14145
+ var tickLabelWidth = me.getLabelWidth(exampleLabel);
14146
+ var innerWidth = me.isHorizontal() ? me.width : me.height;
14147
+
14148
+ return Math.floor(innerWidth / tickLabelWidth);
14149
+ }
14150
+ });
14151
+
14152
+ Chart.scaleService.registerScaleType('time', TimeScale, defaultConfig);
14153
+ };
14154
+
14155
+ },{"1":1,"25":25,"45":45}]},{},[7])(7)
14156
+ });
scripts/chartjs/chart.min.js ADDED
@@ -0,0 +1,14156 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ /*!
2
+ * Chart.js
3
+ * http://chartjs.org/
4
+ * Version: 2.7.1
5
+ *
6
+ * Copyright 2017 Nick Downie
7
+ * Released under the MIT license
8
+ * https://github.com/chartjs/Chart.js/blob/master/LICENSE.md
9
+ */
10
+ (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 e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);var f=new Error("Cannot find module '"+o+"'");throw f.code="MODULE_NOT_FOUND",f}var l=n[o]={exports:{}};t[o][0].call(l.exports,function(e){var n=t[o][1][e];return s(n?n:e)},l,l.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o<r.length;o++)s(r[o]);return s})({1:[function(require,module,exports){
11
+
12
+ },{}],2:[function(require,module,exports){
13
+ /* MIT license */
14
+ var colorNames = require(6);
15
+
16
+ module.exports = {
17
+ getRgba: getRgba,
18
+ getHsla: getHsla,
19
+ getRgb: getRgb,
20
+ getHsl: getHsl,
21
+ getHwb: getHwb,
22
+ getAlpha: getAlpha,
23
+
24
+ hexString: hexString,
25
+ rgbString: rgbString,
26
+ rgbaString: rgbaString,
27
+ percentString: percentString,
28
+ percentaString: percentaString,
29
+ hslString: hslString,
30
+ hslaString: hslaString,
31
+ hwbString: hwbString,
32
+ keyword: keyword
33
+ }
34
+
35
+ function getRgba(string) {
36
+ if (!string) {
37
+ return;
38
+ }
39
+ var abbr = /^#([a-fA-F0-9]{3})$/i,
40
+ hex = /^#([a-fA-F0-9]{6})$/i,
41
+ rgba = /^rgba?\(\s*([+-]?\d+)\s*,\s*([+-]?\d+)\s*,\s*([+-]?\d+)\s*(?:,\s*([+-]?[\d\.]+)\s*)?\)$/i,
42
+ per = /^rgba?\(\s*([+-]?[\d\.]+)\%\s*,\s*([+-]?[\d\.]+)\%\s*,\s*([+-]?[\d\.]+)\%\s*(?:,\s*([+-]?[\d\.]+)\s*)?\)$/i,
43
+ keyword = /(\w+)/;
44
+
45
+ var rgb = [0, 0, 0],
46
+ a = 1,
47
+ match = string.match(abbr);
48
+ if (match) {
49
+ match = match[1];
50
+ for (var i = 0; i < rgb.length; i++) {
51
+ rgb[i] = parseInt(match[i] + match[i], 16);
52
+ }
53
+ }
54
+ else if (match = string.match(hex)) {
55
+ match = match[1];
56
+ for (var i = 0; i < rgb.length; i++) {
57
+ rgb[i] = parseInt(match.slice(i * 2, i * 2 + 2), 16);
58
+ }
59
+ }
60
+ else if (match = string.match(rgba)) {
61
+ for (var i = 0; i < rgb.length; i++) {
62
+ rgb[i] = parseInt(match[i + 1]);
63
+ }
64
+ a = parseFloat(match[4]);
65
+ }
66
+ else if (match = string.match(per)) {
67
+ for (var i = 0; i < rgb.length; i++) {
68
+ rgb[i] = Math.round(parseFloat(match[i + 1]) * 2.55);
69
+ }
70
+ a = parseFloat(match[4]);
71
+ }
72
+ else if (match = string.match(keyword)) {
73
+ if (match[1] == "transparent") {
74
+ return [0, 0, 0, 0];
75
+ }
76
+ rgb = colorNames[match[1]];
77
+ if (!rgb) {
78
+ return;
79
+ }
80
+ }
81
+
82
+ for (var i = 0; i < rgb.length; i++) {
83
+ rgb[i] = scale(rgb[i], 0, 255);
84
+ }
85
+ if (!a && a != 0) {
86
+ a = 1;
87
+ }
88
+ else {
89
+ a = scale(a, 0, 1);
90
+ }
91
+ rgb[3] = a;
92
+ return rgb;
93
+ }
94
+
95
+ function getHsla(string) {
96
+ if (!string) {
97
+ return;
98
+ }
99
+ var hsl = /^hsla?\(\s*([+-]?\d+)(?:deg)?\s*,\s*([+-]?[\d\.]+)%\s*,\s*([+-]?[\d\.]+)%\s*(?:,\s*([+-]?[\d\.]+)\s*)?\)/;
100
+ var match = string.match(hsl);
101
+ if (match) {
102
+ var alpha = parseFloat(match[4]);
103
+ var h = scale(parseInt(match[1]), 0, 360),
104
+ s = scale(parseFloat(match[2]), 0, 100),
105
+ l = scale(parseFloat(match[3]), 0, 100),
106
+ a = scale(isNaN(alpha) ? 1 : alpha, 0, 1);
107
+ return [h, s, l, a];
108
+ }
109
+ }
110
+
111
+ function getHwb(string) {
112
+ if (!string) {
113
+ return;
114
+ }
115
+ var hwb = /^hwb\(\s*([+-]?\d+)(?:deg)?\s*,\s*([+-]?[\d\.]+)%\s*,\s*([+-]?[\d\.]+)%\s*(?:,\s*([+-]?[\d\.]+)\s*)?\)/;
116
+ var match = string.match(hwb);
117
+ if (match) {
118
+ var alpha = parseFloat(match[4]);
119
+ var h = scale(parseInt(match[1]), 0, 360),
120
+ w = scale(parseFloat(match[2]), 0, 100),
121
+ b = scale(parseFloat(match[3]), 0, 100),
122
+ a = scale(isNaN(alpha) ? 1 : alpha, 0, 1);
123
+ return [h, w, b, a];
124
+ }
125
+ }
126
+
127
+ function getRgb(string) {
128
+ var rgba = getRgba(string);
129
+ return rgba && rgba.slice(0, 3);
130
+ }
131
+
132
+ function getHsl(string) {
133
+ var hsla = getHsla(string);
134
+ return hsla && hsla.slice(0, 3);
135
+ }
136
+
137
+ function getAlpha(string) {
138
+ var vals = getRgba(string);
139
+ if (vals) {
140
+ return vals[3];
141
+ }
142
+ else if (vals = getHsla(string)) {
143
+ return vals[3];
144
+ }
145
+ else if (vals = getHwb(string)) {
146
+ return vals[3];
147
+ }
148
+ }
149
+
150
+ // generators
151
+ function hexString(rgb) {
152
+ return "#" + hexDouble(rgb[0]) + hexDouble(rgb[1])
153
+ + hexDouble(rgb[2]);
154
+ }
155
+
156
+ function rgbString(rgba, alpha) {
157
+ if (alpha < 1 || (rgba[3] && rgba[3] < 1)) {
158
+ return rgbaString(rgba, alpha);
159
+ }
160
+ return "rgb(" + rgba[0] + ", " + rgba[1] + ", " + rgba[2] + ")";
161
+ }
162
+
163
+ function rgbaString(rgba, alpha) {
164
+ if (alpha === undefined) {
165
+ alpha = (rgba[3] !== undefined ? rgba[3] : 1);
166
+ }
167
+ return "rgba(" + rgba[0] + ", " + rgba[1] + ", " + rgba[2]
168
+ + ", " + alpha + ")";
169
+ }
170
+
171
+ function percentString(rgba, alpha) {
172
+ if (alpha < 1 || (rgba[3] && rgba[3] < 1)) {
173
+ return percentaString(rgba, alpha);
174
+ }
175
+ var r = Math.round(rgba[0]/255 * 100),
176
+ g = Math.round(rgba[1]/255 * 100),
177
+ b = Math.round(rgba[2]/255 * 100);
178
+
179
+ return "rgb(" + r + "%, " + g + "%, " + b + "%)";
180
+ }
181
+
182
+ function percentaString(rgba, alpha) {
183
+ var r = Math.round(rgba[0]/255 * 100),
184
+ g = Math.round(rgba[1]/255 * 100),
185
+ b = Math.round(rgba[2]/255 * 100);
186
+ return "rgba(" + r + "%, " + g + "%, " + b + "%, " + (alpha || rgba[3] || 1) + ")";
187
+ }
188
+
189
+ function hslString(hsla, alpha) {
190
+ if (alpha < 1 || (hsla[3] && hsla[3] < 1)) {
191
+ return hslaString(hsla, alpha);
192
+ }
193
+ return "hsl(" + hsla[0] + ", " + hsla[1] + "%, " + hsla[2] + "%)";
194
+ }
195
+
196
+ function hslaString(hsla, alpha) {
197
+ if (alpha === undefined) {
198
+ alpha = (hsla[3] !== undefined ? hsla[3] : 1);
199
+ }
200
+ return "hsla(" + hsla[0] + ", " + hsla[1] + "%, " + hsla[2] + "%, "
201
+ + alpha + ")";
202
+ }
203
+
204
+ // hwb is a bit different than rgb(a) & hsl(a) since there is no alpha specific syntax
205
+ // (hwb have alpha optional & 1 is default value)
206
+ function hwbString(hwb, alpha) {
207
+ if (alpha === undefined) {
208
+ alpha = (hwb[3] !== undefined ? hwb[3] : 1);
209
+ }
210
+ return "hwb(" + hwb[0] + ", " + hwb[1] + "%, " + hwb[2] + "%"
211
+ + (alpha !== undefined && alpha !== 1 ? ", " + alpha : "") + ")";
212
+ }
213
+
214
+ function keyword(rgb) {
215
+ return reverseNames[rgb.slice(0, 3)];
216
+ }
217
+
218
+ // helpers
219
+ function scale(num, min, max) {
220
+ return Math.min(Math.max(min, num), max);
221
+ }
222
+
223
+ function hexDouble(num) {
224
+ var str = num.toString(16).toUpperCase();
225
+ return (str.length < 2) ? "0" + str : str;
226
+ }
227
+
228
+
229
+ //create a list of reverse color names
230
+ var reverseNames = {};
231
+ for (var name in colorNames) {
232
+ reverseNames[colorNames[name]] = name;
233
+ }
234
+
235
+ },{"6":6}],3:[function(require,module,exports){
236
+ /* MIT license */
237
+ var convert = require(5);
238
+ var string = require(2);
239
+
240
+ var Color = function (obj) {
241
+ if (obj instanceof Color) {
242
+ return obj;
243
+ }
244
+ if (!(this instanceof Color)) {
245
+ return new Color(obj);
246
+ }
247
+
248
+ this.valid = false;
249
+ this.values = {
250
+ rgb: [0, 0, 0],
251
+ hsl: [0, 0, 0],
252
+ hsv: [0, 0, 0],
253
+ hwb: [0, 0, 0],
254
+ cmyk: [0, 0, 0, 0],
255
+ alpha: 1
256
+ };
257
+
258
+ // parse Color() argument
259
+ var vals;
260
+ if (typeof obj === 'string') {
261
+ vals = string.getRgba(obj);
262
+ if (vals) {
263
+ this.setValues('rgb', vals);
264
+ } else if (vals = string.getHsla(obj)) {
265
+ this.setValues('hsl', vals);
266
+ } else if (vals = string.getHwb(obj)) {
267
+ this.setValues('hwb', vals);
268
+ }
269
+ } else if (typeof obj === 'object') {
270
+ vals = obj;
271
+ if (vals.r !== undefined || vals.red !== undefined) {
272
+ this.setValues('rgb', vals);
273
+ } else if (vals.l !== undefined || vals.lightness !== undefined) {
274
+ this.setValues('hsl', vals);
275
+ } else if (vals.v !== undefined || vals.value !== undefined) {
276
+ this.setValues('hsv', vals);
277
+ } else if (vals.w !== undefined || vals.whiteness !== undefined) {
278
+ this.setValues('hwb', vals);
279
+ } else if (vals.c !== undefined || vals.cyan !== undefined) {
280
+ this.setValues('cmyk', vals);
281
+ }
282
+ }
283
+ };
284
+
285
+
286
+ Color.prototype = {
287
+ isValid: function () {
288
+ return this.valid;
289
+ },
290
+ rgb: function () {
291
+ return this.setSpace('rgb', arguments);
292
+ },
293
+ hsl: function () {
294
+ return this.setSpace('hsl', arguments);
295
+ },
296
+ hsv: function () {
297
+ return this.setSpace('hsv', arguments);
298
+ },
299
+ hwb: function () {
300
+ return this.setSpace('hwb', arguments);
301
+ },
302
+ cmyk: function () {
303
+ return this.setSpace('cmyk', arguments);
304
+ },
305
+
306
+ rgbArray: function () {
307
+ return this.values.rgb;
308
+ },
309
+ hslArray: function () {
310
+ return this.values.hsl;
311
+ },
312
+ hsvArray: function () {
313
+ return this.values.hsv;
314
+ },
315
+ hwbArray: function () {
316
+ var values = this.values;
317
+ if (values.alpha !== 1) {
318
+ return values.hwb.concat([values.alpha]);
319
+ }
320
+ return values.hwb;
321
+ },
322
+ cmykArray: function () {
323
+ return this.values.cmyk;
324
+ },
325
+ rgbaArray: function () {
326
+ var values = this.values;
327
+ return values.rgb.concat([values.alpha]);
328
+ },
329
+ hslaArray: function () {
330
+ var values = this.values;
331
+ return values.hsl.concat([values.alpha]);
332
+ },
333
+ alpha: function (val) {
334
+ if (val === undefined) {
335
+ return this.values.alpha;
336
+ }
337
+ this.setValues('alpha', val);
338
+ return this;
339
+ },
340
+
341
+ red: function (val) {
342
+ return this.setChannel('rgb', 0, val);
343
+ },
344
+ green: function (val) {
345
+ return this.setChannel('rgb', 1, val);
346
+ },
347
+ blue: function (val) {
348
+ return this.setChannel('rgb', 2, val);
349
+ },
350
+ hue: function (val) {
351
+ if (val) {
352
+ val %= 360;
353
+ val = val < 0 ? 360 + val : val;
354
+ }
355
+ return this.setChannel('hsl', 0, val);
356
+ },
357
+ saturation: function (val) {
358
+ return this.setChannel('hsl', 1, val);
359
+ },
360
+ lightness: function (val) {
361
+ return this.setChannel('hsl', 2, val);
362
+ },
363
+ saturationv: function (val) {
364
+ return this.setChannel('hsv', 1, val);
365
+ },
366
+ whiteness: function (val) {
367
+ return this.setChannel('hwb', 1, val);
368
+ },
369
+ blackness: function (val) {
370
+ return this.setChannel('hwb', 2, val);
371
+ },
372
+ value: function (val) {
373
+ return this.setChannel('hsv', 2, val);
374
+ },
375
+ cyan: function (val) {
376
+ return this.setChannel('cmyk', 0, val);
377
+ },
378
+ magenta: function (val) {
379
+ return this.setChannel('cmyk', 1, val);
380
+ },
381
+ yellow: function (val) {
382
+ return this.setChannel('cmyk', 2, val);
383
+ },
384
+ black: function (val) {
385
+ return this.setChannel('cmyk', 3, val);
386
+ },
387
+
388
+ hexString: function () {
389
+ return string.hexString(this.values.rgb);
390
+ },
391
+ rgbString: function () {
392
+ return string.rgbString(this.values.rgb, this.values.alpha);
393
+ },
394
+ rgbaString: function () {
395
+ return string.rgbaString(this.values.rgb, this.values.alpha);
396
+ },
397
+ percentString: function () {
398
+ return string.percentString(this.values.rgb, this.values.alpha);
399
+ },
400
+ hslString: function () {
401
+ return string.hslString(this.values.hsl, this.values.alpha);
402
+ },
403
+ hslaString: function () {
404
+ return string.hslaString(this.values.hsl, this.values.alpha);
405
+ },
406
+ hwbString: function () {
407
+ return string.hwbString(this.values.hwb, this.values.alpha);
408
+ },
409
+ keyword: function () {
410
+ return string.keyword(this.values.rgb, this.values.alpha);
411
+ },
412
+
413
+ rgbNumber: function () {
414
+ var rgb = this.values.rgb;
415
+ return (rgb[0] << 16) | (rgb[1] << 8) | rgb[2];
416
+ },
417
+
418
+ luminosity: function () {
419
+ // http://www.w3.org/TR/WCAG20/#relativeluminancedef
420
+ var rgb = this.values.rgb;
421
+ var lum = [];
422
+ for (var i = 0; i < rgb.length; i++) {
423
+ var chan = rgb[i] / 255;
424
+ lum[i] = (chan <= 0.03928) ? chan / 12.92 : Math.pow(((chan + 0.055) / 1.055), 2.4);
425
+ }
426
+ return 0.2126 * lum[0] + 0.7152 * lum[1] + 0.0722 * lum[2];
427
+ },
428
+
429
+ contrast: function (color2) {
430
+ // http://www.w3.org/TR/WCAG20/#contrast-ratiodef
431
+ var lum1 = this.luminosity();
432
+ var lum2 = color2.luminosity();
433
+ if (lum1 > lum2) {
434
+ return (lum1 + 0.05) / (lum2 + 0.05);
435
+ }
436
+ return (lum2 + 0.05) / (lum1 + 0.05);
437
+ },
438
+
439
+ level: function (color2) {
440
+ var contrastRatio = this.contrast(color2);
441
+ if (contrastRatio >= 7.1) {
442
+ return 'AAA';
443
+ }
444
+
445
+ return (contrastRatio >= 4.5) ? 'AA' : '';
446
+ },
447
+
448
+ dark: function () {
449
+ // YIQ equation from http://24ways.org/2010/calculating-color-contrast
450
+ var rgb = this.values.rgb;
451
+ var yiq = (rgb[0] * 299 + rgb[1] * 587 + rgb[2] * 114) / 1000;
452
+ return yiq < 128;
453
+ },
454
+
455
+ light: function () {
456
+ return !this.dark();
457
+ },
458
+
459
+ negate: function () {
460
+ var rgb = [];
461
+ for (var i = 0; i < 3; i++) {
462
+ rgb[i] = 255 - this.values.rgb[i];
463
+ }
464
+ this.setValues('rgb', rgb);
465
+ return this;
466
+ },
467
+
468
+ lighten: function (ratio) {
469
+ var hsl = this.values.hsl;
470
+ hsl[2] += hsl[2] * ratio;
471
+ this.setValues('hsl', hsl);
472
+ return this;
473
+ },
474
+
475
+ darken: function (ratio) {
476
+ var hsl = this.values.hsl;
477
+ hsl[2] -= hsl[2] * ratio;
478
+ this.setValues('hsl', hsl);
479
+ return this;
480
+ },
481
+
482
+ saturate: function (ratio) {
483
+ var hsl = this.values.hsl;
484
+ hsl[1] += hsl[1] * ratio;
485
+ this.setValues('hsl', hsl);
486
+ return this;
487
+ },
488
+
489
+ desaturate: function (ratio) {
490
+ var hsl = this.values.hsl;
491
+ hsl[1] -= hsl[1] * ratio;
492
+ this.setValues('hsl', hsl);
493
+ return this;
494
+ },
495
+
496
+ whiten: function (ratio) {
497
+ var hwb = this.values.hwb;
498
+ hwb[1] += hwb[1] * ratio;
499
+ this.setValues('hwb', hwb);
500
+ return this;
501
+ },
502
+
503
+ blacken: function (ratio) {
504
+ var hwb = this.values.hwb;
505
+ hwb[2] += hwb[2] * ratio;
506
+ this.setValues('hwb', hwb);
507
+ return this;
508
+ },
509
+
510
+ greyscale: function () {
511
+ var rgb = this.values.rgb;
512
+ // http://en.wikipedia.org/wiki/Grayscale#Converting_color_to_grayscale
513
+ var val = rgb[0] * 0.3 + rgb[1] * 0.59 + rgb[2] * 0.11;
514
+ this.setValues('rgb', [val, val, val]);
515
+ return this;
516
+ },
517
+
518
+ clearer: function (ratio) {
519
+ var alpha = this.values.alpha;
520
+ this.setValues('alpha', alpha - (alpha * ratio));
521
+ return this;
522
+ },
523
+
524
+ opaquer: function (ratio) {
525
+ var alpha = this.values.alpha;
526
+ this.setValues('alpha', alpha + (alpha * ratio));
527
+ return this;
528
+ },
529
+
530
+ rotate: function (degrees) {
531
+ var hsl = this.values.hsl;
532
+ var hue = (hsl[0] + degrees) % 360;
533
+ hsl[0] = hue < 0 ? 360 + hue : hue;
534
+ this.setValues('hsl', hsl);
535
+ return this;
536
+ },
537
+
538
+ /**
539
+ * Ported from sass implementation in C
540
+ * https://github.com/sass/libsass/blob/0e6b4a2850092356aa3ece07c6b249f0221caced/functions.cpp#L209
541
+ */
542
+ mix: function (mixinColor, weight) {
543
+ var color1 = this;
544
+ var color2 = mixinColor;
545
+ var p = weight === undefined ? 0.5 : weight;
546
+
547
+ var w = 2 * p - 1;
548
+ var a = color1.alpha() - color2.alpha();
549
+
550
+ var w1 = (((w * a === -1) ? w : (w + a) / (1 + w * a)) + 1) / 2.0;
551
+ var w2 = 1 - w1;
552
+
553
+ return this
554
+ .rgb(
555
+ w1 * color1.red() + w2 * color2.red(),
556
+ w1 * color1.green() + w2 * color2.green(),
557
+ w1 * color1.blue() + w2 * color2.blue()
558
+ )
559
+ .alpha(color1.alpha() * p + color2.alpha() * (1 - p));
560
+ },
561
+
562
+ toJSON: function () {
563
+ return this.rgb();
564
+ },
565
+
566
+ clone: function () {
567
+ // NOTE(SB): using node-clone creates a dependency to Buffer when using browserify,
568
+ // making the final build way to big to embed in Chart.js. So let's do it manually,
569
+ // assuming that values to clone are 1 dimension arrays containing only numbers,
570
+ // except 'alpha' which is a number.
571
+ var result = new Color();
572
+ var source = this.values;
573
+ var target = result.values;
574
+ var value, type;
575
+
576
+ for (var prop in source) {
577
+ if (source.hasOwnProperty(prop)) {
578
+ value = source[prop];
579
+ type = ({}).toString.call(value);
580
+ if (type === '[object Array]') {
581
+ target[prop] = value.slice(0);
582
+ } else if (type === '[object Number]') {
583
+ target[prop] = value;
584
+ } else {
585
+ console.error('unexpected color value:', value);
586
+ }
587
+ }
588
+ }
589
+
590
+ return result;
591
+ }
592
+ };
593
+
594
+ Color.prototype.spaces = {
595
+ rgb: ['red', 'green', 'blue'],
596
+ hsl: ['hue', 'saturation', 'lightness'],
597
+ hsv: ['hue', 'saturation', 'value'],
598
+ hwb: ['hue', 'whiteness', 'blackness'],
599
+ cmyk: ['cyan', 'magenta', 'yellow', 'black']
600
+ };
601
+
602
+ Color.prototype.maxes = {
603
+ rgb: [255, 255, 255],
604
+ hsl: [360, 100, 100],
605
+ hsv: [360, 100, 100],
606
+ hwb: [360, 100, 100],
607
+ cmyk: [100, 100, 100, 100]
608
+ };
609
+
610
+ Color.prototype.getValues = function (space) {
611
+ var values = this.values;
612
+ var vals = {};
613
+
614
+ for (var i = 0; i < space.length; i++) {
615
+ vals[space.charAt(i)] = values[space][i];
616
+ }
617
+
618
+ if (values.alpha !== 1) {
619
+ vals.a = values.alpha;
620
+ }
621
+
622
+ // {r: 255, g: 255, b: 255, a: 0.4}
623
+ return vals;
624
+ };
625
+
626
+ Color.prototype.setValues = function (space, vals) {
627
+ var values = this.values;
628
+ var spaces = this.spaces;
629
+ var maxes = this.maxes;
630
+ var alpha = 1;
631
+ var i;
632
+
633
+ this.valid = true;
634
+
635
+ if (space === 'alpha') {
636
+ alpha = vals;
637
+ } else if (vals.length) {
638
+ // [10, 10, 10]
639
+ values[space] = vals.slice(0, space.length);
640
+ alpha = vals[space.length];
641
+ } else if (vals[space.charAt(0)] !== undefined) {
642
+ // {r: 10, g: 10, b: 10}
643
+ for (i = 0; i < space.length; i++) {
644
+ values[space][i] = vals[space.charAt(i)];
645
+ }
646
+
647
+ alpha = vals.a;
648
+ } else if (vals[spaces[space][0]] !== undefined) {
649
+ // {red: 10, green: 10, blue: 10}
650
+ var chans = spaces[space];
651
+
652
+ for (i = 0; i < space.length; i++) {
653
+ values[space][i] = vals[chans[i]];
654
+ }
655
+
656
+ alpha = vals.alpha;
657
+ }
658
+
659
+ values.alpha = Math.max(0, Math.min(1, (alpha === undefined ? values.alpha : alpha)));
660
+
661
+ if (space === 'alpha') {
662
+ return false;
663
+ }
664
+
665
+ var capped;
666
+
667
+ // cap values of the space prior converting all values
668
+ for (i = 0; i < space.length; i++) {
669
+ capped = Math.max(0, Math.min(maxes[space][i], values[space][i]));
670
+ values[space][i] = Math.round(capped);
671
+ }
672
+
673
+ // convert to all the other color spaces
674
+ for (var sname in spaces) {
675
+ if (sname !== space) {
676
+ values[sname] = convert[space][sname](values[space]);
677
+ }
678
+ }
679
+
680
+ return true;
681
+ };
682
+
683
+ Color.prototype.setSpace = function (space, args) {
684
+ var vals = args[0];
685
+
686
+ if (vals === undefined) {
687
+ // color.rgb()
688
+ return this.getValues(space);
689
+ }
690
+
691
+ // color.rgb(10, 10, 10)
692
+ if (typeof vals === 'number') {
693
+ vals = Array.prototype.slice.call(args);
694
+ }
695
+
696
+ this.setValues(space, vals);
697
+ return this;
698
+ };
699
+
700
+ Color.prototype.setChannel = function (space, index, val) {
701
+ var svalues = this.values[space];
702
+ if (val === undefined) {
703
+ // color.red()
704
+ return svalues[index];
705
+ } else if (val === svalues[index]) {
706
+ // color.red(color.red())
707
+ return this;
708
+ }
709
+
710
+ // color.red(100)
711
+ svalues[index] = val;
712
+ this.setValues(space, svalues);
713
+
714
+ return this;
715
+ };
716
+
717
+ //if (typeof window !== 'undefined') {
718
+ // window.Color = Color;
719
+ //}
720
+ if (typeof window !== 'undefined') {
721
+ window.Chart = window.Chart || {};
722
+ window.Chart.Color = Color;
723
+
724
+ if (typeof window.Color === 'undefined') {
725
+ // maintain backward compatibility ONLY if no other Color lib has been defined
726
+ window.Color = Color;
727
+ }
728
+ }
729
+
730
+
731
+ module.exports = Color;
732
+
733
+ },{"2":2,"5":5}],4:[function(require,module,exports){
734
+ /* MIT license */
735
+
736
+ module.exports = {
737
+ rgb2hsl: rgb2hsl,
738
+ rgb2hsv: rgb2hsv,
739
+ rgb2hwb: rgb2hwb,
740
+ rgb2cmyk: rgb2cmyk,
741
+ rgb2keyword: rgb2keyword,
742
+ rgb2xyz: rgb2xyz,
743
+ rgb2lab: rgb2lab,
744
+ rgb2lch: rgb2lch,
745
+
746
+ hsl2rgb: hsl2rgb,
747
+ hsl2hsv: hsl2hsv,
748
+ hsl2hwb: hsl2hwb,
749
+ hsl2cmyk: hsl2cmyk,
750
+ hsl2keyword: hsl2keyword,
751
+
752
+ hsv2rgb: hsv2rgb,
753
+ hsv2hsl: hsv2hsl,
754
+ hsv2hwb: hsv2hwb,
755
+ hsv2cmyk: hsv2cmyk,
756
+ hsv2keyword: hsv2keyword,
757
+
758
+ hwb2rgb: hwb2rgb,
759
+ hwb2hsl: hwb2hsl,
760
+ hwb2hsv: hwb2hsv,
761
+ hwb2cmyk: hwb2cmyk,
762
+ hwb2keyword: hwb2keyword,
763
+
764
+ cmyk2rgb: cmyk2rgb,
765
+ cmyk2hsl: cmyk2hsl,
766
+ cmyk2hsv: cmyk2hsv,
767
+ cmyk2hwb: cmyk2hwb,
768
+ cmyk2keyword: cmyk2keyword,
769
+
770
+ keyword2rgb: keyword2rgb,
771
+ keyword2hsl: keyword2hsl,
772
+ keyword2hsv: keyword2hsv,
773
+ keyword2hwb: keyword2hwb,
774
+ keyword2cmyk: keyword2cmyk,
775
+ keyword2lab: keyword2lab,
776
+ keyword2xyz: keyword2xyz,
777
+
778
+ xyz2rgb: xyz2rgb,
779
+ xyz2lab: xyz2lab,
780
+ xyz2lch: xyz2lch,
781
+
782
+ lab2xyz: lab2xyz,
783
+ lab2rgb: lab2rgb,
784
+ lab2lch: lab2lch,
785
+
786
+ lch2lab: lch2lab,
787
+ lch2xyz: lch2xyz,
788
+ lch2rgb: lch2rgb
789
+ }
790
+
791
+
792
+ function rgb2hsl(rgb) {
793
+ var r = rgb[0]/255,
794
+ g = rgb[1]/255,
795
+ b = rgb[2]/255,
796
+ min = Math.min(r, g, b),
797
+ max = Math.max(r, g, b),
798
+ delta = max - min,
799
+ h, s, l;
800
+
801
+ if (max == min)
802
+ h = 0;
803
+ else if (r == max)
804
+ h = (g - b) / delta;
805
+ else if (g == max)
806
+ h = 2 + (b - r) / delta;
807
+ else if (b == max)
808
+ h = 4 + (r - g)/ delta;
809
+
810
+ h = Math.min(h * 60, 360);
811
+
812
+ if (h < 0)
813
+ h += 360;
814
+
815
+ l = (min + max) / 2;
816
+
817
+ if (max == min)
818
+ s = 0;
819
+ else if (l <= 0.5)
820
+ s = delta / (max + min);
821
+ else
822
+ s = delta / (2 - max - min);
823
+
824
+ return [h, s * 100, l * 100];
825
+ }
826
+
827
+ function rgb2hsv(rgb) {
828
+ var r = rgb[0],
829
+ g = rgb[1],
830
+ b = rgb[2],
831
+ min = Math.min(r, g, b),
832
+ max = Math.max(r, g, b),
833
+ delta = max - min,
834
+ h, s, v;
835
+
836
+ if (max == 0)
837
+ s = 0;
838
+ else
839
+ s = (delta/max * 1000)/10;
840
+
841
+ if (max == min)
842
+ h = 0;
843
+ else if (r == max)
844
+ h = (g - b) / delta;
845
+ else if (g == max)
846
+ h = 2 + (b - r) / delta;
847
+ else if (b == max)
848
+ h = 4 + (r - g) / delta;
849
+
850
+ h = Math.min(h * 60, 360);
851
+
852
+ if (h < 0)
853
+ h += 360;
854
+
855
+ v = ((max / 255) * 1000) / 10;
856
+
857
+ return [h, s, v];
858
+ }
859
+
860
+ function rgb2hwb(rgb) {
861
+ var r = rgb[0],
862
+ g = rgb[1],
863
+ b = rgb[2],
864
+ h = rgb2hsl(rgb)[0],
865
+ w = 1/255 * Math.min(r, Math.min(g, b)),
866
+ b = 1 - 1/255 * Math.max(r, Math.max(g, b));
867
+
868
+ return [h, w * 100, b * 100];
869
+ }
870
+
871
+ function rgb2cmyk(rgb) {
872
+ var r = rgb[0] / 255,
873
+ g = rgb[1] / 255,
874
+ b = rgb[2] / 255,
875
+ c, m, y, k;
876
+
877
+ k = Math.min(1 - r, 1 - g, 1 - b);
878
+ c = (1 - r - k) / (1 - k) || 0;
879
+ m = (1 - g - k) / (1 - k) || 0;
880
+ y = (1 - b - k) / (1 - k) || 0;
881
+ return [c * 100, m * 100, y * 100, k * 100];
882
+ }
883
+
884
+ function rgb2keyword(rgb) {
885
+ return reverseKeywords[JSON.stringify(rgb)];
886
+ }
887
+
888
+ function rgb2xyz(rgb) {
889
+ var r = rgb[0] / 255,
890
+ g = rgb[1] / 255,
891
+ b = rgb[2] / 255;
892
+
893
+ // assume sRGB
894
+ r = r > 0.04045 ? Math.pow(((r + 0.055) / 1.055), 2.4) : (r / 12.92);
895
+ g = g > 0.04045 ? Math.pow(((g + 0.055) / 1.055), 2.4) : (g / 12.92);
896
+ b = b > 0.04045 ? Math.pow(((b + 0.055) / 1.055), 2.4) : (b / 12.92);
897
+
898
+ var x = (r * 0.4124) + (g * 0.3576) + (b * 0.1805);
899
+ var y = (r * 0.2126) + (g * 0.7152) + (b * 0.0722);
900
+ var z = (r * 0.0193) + (g * 0.1192) + (b * 0.9505);
901
+
902
+ return [x * 100, y *100, z * 100];
903
+ }
904
+
905
+ function rgb2lab(rgb) {
906
+ var xyz = rgb2xyz(rgb),
907
+ x = xyz[0],
908
+ y = xyz[1],
909
+ z = xyz[2],
910
+ l, a, b;
911
+
912
+ x /= 95.047;
913
+ y /= 100;
914
+ z /= 108.883;
915
+
916
+ x = x > 0.008856 ? Math.pow(x, 1/3) : (7.787 * x) + (16 / 116);
917
+ y = y > 0.008856 ? Math.pow(y, 1/3) : (7.787 * y) + (16 / 116);
918
+ z = z > 0.008856 ? Math.pow(z, 1/3) : (7.787 * z) + (16 / 116);
919
+
920
+ l = (116 * y) - 16;
921
+ a = 500 * (x - y);
922
+ b = 200 * (y - z);
923
+
924
+ return [l, a, b];
925
+ }
926
+
927
+ function rgb2lch(args) {
928
+ return lab2lch(rgb2lab(args));
929
+ }
930
+
931
+ function hsl2rgb(hsl) {
932
+ var h = hsl[0] / 360,
933
+ s = hsl[1] / 100,
934
+ l = hsl[2] / 100,
935
+ t1, t2, t3, rgb, val;
936
+
937
+ if (s == 0) {
938
+ val = l * 255;
939
+ return [val, val, val];
940
+ }
941
+
942
+ if (l < 0.5)
943
+ t2 = l * (1 + s);
944
+ else
945
+ t2 = l + s - l * s;
946
+ t1 = 2 * l - t2;
947
+
948
+ rgb = [0, 0, 0];
949
+ for (var i = 0; i < 3; i++) {
950
+ t3 = h + 1 / 3 * - (i - 1);
951
+ t3 < 0 && t3++;
952
+ t3 > 1 && t3--;
953
+
954
+ if (6 * t3 < 1)
955
+ val = t1 + (t2 - t1) * 6 * t3;
956
+ else if (2 * t3 < 1)
957
+ val = t2;
958
+ else if (3 * t3 < 2)
959
+ val = t1 + (t2 - t1) * (2 / 3 - t3) * 6;
960
+ else
961
+ val = t1;
962
+
963
+ rgb[i] = val * 255;
964
+ }
965
+
966
+ return rgb;
967
+ }
968
+
969
+ function hsl2hsv(hsl) {
970
+ var h = hsl[0],
971
+ s = hsl[1] / 100,
972
+ l = hsl[2] / 100,
973
+ sv, v;
974
+
975
+ if(l === 0) {
976
+ // no need to do calc on black
977
+ // also avoids divide by 0 error
978
+ return [0, 0, 0];
979
+ }
980
+
981
+ l *= 2;
982
+ s *= (l <= 1) ? l : 2 - l;
983
+ v = (l + s) / 2;
984
+ sv = (2 * s) / (l + s);
985
+ return [h, sv * 100, v * 100];
986
+ }
987
+
988
+ function hsl2hwb(args) {
989
+ return rgb2hwb(hsl2rgb(args));
990
+ }
991
+
992
+ function hsl2cmyk(args) {
993
+ return rgb2cmyk(hsl2rgb(args));
994
+ }
995
+
996
+ function hsl2keyword(args) {
997
+ return rgb2keyword(hsl2rgb(args));
998
+ }
999
+
1000
+
1001
+ function hsv2rgb(hsv) {
1002
+ var h = hsv[0] / 60,
1003
+ s = hsv[1] / 100,
1004
+ v = hsv[2] / 100,
1005
+ hi = Math.floor(h) % 6;
1006
+
1007
+ var f = h - Math.floor(h),
1008
+ p = 255 * v * (1 - s),
1009
+ q = 255 * v * (1 - (s * f)),
1010
+ t = 255 * v * (1 - (s * (1 - f))),
1011
+ v = 255 * v;
1012
+
1013
+ switch(hi) {
1014
+ case 0:
1015
+ return [v, t, p];
1016
+ case 1:
1017
+ return [q, v, p];
1018
+ case 2:
1019
+ return [p, v, t];
1020
+ case 3:
1021
+ return [p, q, v];
1022
+ case 4:
1023
+ return [t, p, v];
1024
+ case 5:
1025
+ return [v, p, q];
1026
+ }
1027
+ }
1028
+
1029
+ function hsv2hsl(hsv) {
1030
+ var h = hsv[0],
1031
+ s = hsv[1] / 100,
1032
+ v = hsv[2] / 100,
1033
+ sl, l;
1034
+
1035
+ l = (2 - s) * v;
1036
+ sl = s * v;
1037
+ sl /= (l <= 1) ? l : 2 - l;
1038
+ sl = sl || 0;
1039
+ l /= 2;
1040
+ return [h, sl * 100, l * 100];
1041
+ }
1042
+
1043
+ function hsv2hwb(args) {
1044
+ return rgb2hwb(hsv2rgb(args))
1045
+ }
1046
+
1047
+ function hsv2cmyk(args) {
1048
+ return rgb2cmyk(hsv2rgb(args));
1049
+ }
1050
+
1051
+ function hsv2keyword(args) {
1052
+ return rgb2keyword(hsv2rgb(args));
1053
+ }
1054
+
1055
+ // http://dev.w3.org/csswg/css-color/#hwb-to-rgb
1056
+ function hwb2rgb(hwb) {
1057
+ var h = hwb[0] / 360,
1058
+ wh = hwb[1] / 100,
1059
+ bl = hwb[2] / 100,
1060
+ ratio = wh + bl,
1061
+ i, v, f, n;
1062
+
1063
+ // wh + bl cant be > 1
1064
+ if (ratio > 1) {
1065
+ wh /= ratio;
1066
+ bl /= ratio;
1067
+ }
1068
+
1069
+ i = Math.floor(6 * h);
1070
+ v = 1 - bl;
1071
+ f = 6 * h - i;
1072
+ if ((i & 0x01) != 0) {
1073
+ f = 1 - f;
1074
+ }
1075
+ n = wh + f * (v - wh); // linear interpolation
1076
+
1077
+ switch (i) {
1078
+ default:
1079
+ case 6:
1080
+ case 0: r = v; g = n; b = wh; break;
1081
+ case 1: r = n; g = v; b = wh; break;
1082
+ case 2: r = wh; g = v; b = n; break;
1083
+ case 3: r = wh; g = n; b = v; break;
1084
+ case 4: r = n; g = wh; b = v; break;
1085
+ case 5: r = v; g = wh; b = n; break;
1086
+ }
1087
+
1088
+ return [r * 255, g * 255, b * 255];
1089
+ }
1090
+
1091
+ function hwb2hsl(args) {
1092
+ return rgb2hsl(hwb2rgb(args));
1093
+ }
1094
+
1095
+ function hwb2hsv(args) {
1096
+ return rgb2hsv(hwb2rgb(args));
1097
+ }
1098
+
1099
+ function hwb2cmyk(args) {
1100
+ return rgb2cmyk(hwb2rgb(args));
1101
+ }
1102
+
1103
+ function hwb2keyword(args) {
1104
+ return rgb2keyword(hwb2rgb(args));
1105
+ }
1106
+
1107
+ function cmyk2rgb(cmyk) {
1108
+ var c = cmyk[0] / 100,
1109
+ m = cmyk[1] / 100,
1110
+ y = cmyk[2] / 100,
1111
+ k = cmyk[3] / 100,
1112
+ r, g, b;
1113
+
1114
+ r = 1 - Math.min(1, c * (1 - k) + k);
1115
+ g = 1 - Math.min(1, m * (1 - k) + k);
1116
+ b = 1 - Math.min(1, y * (1 - k) + k);
1117
+ return [r * 255, g * 255, b * 255];
1118
+ }
1119
+
1120
+ function cmyk2hsl(args) {
1121
+ return rgb2hsl(cmyk2rgb(args));
1122
+ }
1123
+
1124
+ function cmyk2hsv(args) {
1125
+ return rgb2hsv(cmyk2rgb(args));
1126
+ }
1127
+
1128
+ function cmyk2hwb(args) {
1129
+ return rgb2hwb(cmyk2rgb(args));
1130
+ }
1131
+
1132
+ function cmyk2keyword(args) {
1133
+ return rgb2keyword(cmyk2rgb(args));
1134
+ }
1135
+
1136
+
1137
+ function xyz2rgb(xyz) {
1138
+ var x = xyz[0] / 100,
1139
+ y = xyz[1] / 100,
1140
+ z = xyz[2] / 100,
1141
+ r, g, b;
1142
+
1143
+ r = (x * 3.2406) + (y * -1.5372) + (z * -0.4986);
1144
+ g = (x * -0.9689) + (y * 1.8758) + (z * 0.0415);
1145
+ b = (x * 0.0557) + (y * -0.2040) + (z * 1.0570);
1146
+
1147
+ // assume sRGB
1148
+ r = r > 0.0031308 ? ((1.055 * Math.pow(r, 1.0 / 2.4)) - 0.055)
1149
+ : r = (r * 12.92);
1150
+
1151
+ g = g > 0.0031308 ? ((1.055 * Math.pow(g, 1.0 / 2.4)) - 0.055)
1152
+ : g = (g * 12.92);
1153
+
1154
+ b = b > 0.0031308 ? ((1.055 * Math.pow(b, 1.0 / 2.4)) - 0.055)
1155
+ : b = (b * 12.92);
1156
+
1157
+ r = Math.min(Math.max(0, r), 1);
1158
+ g = Math.min(Math.max(0, g), 1);
1159
+ b = Math.min(Math.max(0, b), 1);
1160
+
1161
+ return [r * 255, g * 255, b * 255];
1162
+ }
1163
+
1164
+ function xyz2lab(xyz) {
1165
+ var x = xyz[0],
1166
+ y = xyz[1],
1167
+ z = xyz[2],
1168
+ l, a, b;
1169
+
1170
+ x /= 95.047;
1171
+ y /= 100;
1172
+ z /= 108.883;
1173
+
1174
+ x = x > 0.008856 ? Math.pow(x, 1/3) : (7.787 * x) + (16 / 116);
1175
+ y = y > 0.008856 ? Math.pow(y, 1/3) : (7.787 * y) + (16 / 116);
1176
+ z = z > 0.008856 ? Math.pow(z, 1/3) : (7.787 * z) + (16 / 116);
1177
+
1178
+ l = (116 * y) - 16;
1179
+ a = 500 * (x - y);
1180
+ b = 200 * (y - z);
1181
+
1182
+ return [l, a, b];
1183
+ }
1184
+
1185
+ function xyz2lch(args) {
1186
+ return lab2lch(xyz2lab(args));
1187
+ }
1188
+
1189
+ function lab2xyz(lab) {
1190
+ var l = lab[0],
1191
+ a = lab[1],
1192
+ b = lab[2],
1193
+ x, y, z, y2;
1194
+
1195
+ if (l <= 8) {
1196
+ y = (l * 100) / 903.3;
1197
+ y2 = (7.787 * (y / 100)) + (16 / 116);
1198
+ } else {
1199
+ y = 100 * Math.pow((l + 16) / 116, 3);
1200
+ y2 = Math.pow(y / 100, 1/3);
1201
+ }
1202
+
1203
+ x = x / 95.047 <= 0.008856 ? x = (95.047 * ((a / 500) + y2 - (16 / 116))) / 7.787 : 95.047 * Math.pow((a / 500) + y2, 3);
1204
+
1205
+ z = z / 108.883 <= 0.008859 ? z = (108.883 * (y2 - (b / 200) - (16 / 116))) / 7.787 : 108.883 * Math.pow(y2 - (b / 200), 3);
1206
+
1207
+ return [x, y, z];
1208
+ }
1209
+
1210
+ function lab2lch(lab) {
1211
+ var l = lab[0],
1212
+ a = lab[1],
1213
+ b = lab[2],
1214
+ hr, h, c;
1215
+
1216
+ hr = Math.atan2(b, a);
1217
+ h = hr * 360 / 2 / Math.PI;
1218
+ if (h < 0) {
1219
+ h += 360;
1220
+ }
1221
+ c = Math.sqrt(a * a + b * b);
1222
+ return [l, c, h];
1223
+ }
1224
+
1225
+ function lab2rgb(args) {
1226
+ return xyz2rgb(lab2xyz(args));
1227
+ }
1228
+
1229
+ function lch2lab(lch) {
1230
+ var l = lch[0],
1231
+ c = lch[1],
1232
+ h = lch[2],
1233
+ a, b, hr;
1234
+
1235
+ hr = h / 360 * 2 * Math.PI;
1236
+ a = c * Math.cos(hr);
1237
+ b = c * Math.sin(hr);
1238
+ return [l, a, b];
1239
+ }
1240
+
1241
+ function lch2xyz(args) {
1242
+ return lab2xyz(lch2lab(args));
1243
+ }
1244
+
1245
+ function lch2rgb(args) {
1246
+ return lab2rgb(lch2lab(args));
1247
+ }
1248
+
1249
+ function keyword2rgb(keyword) {
1250
+ return cssKeywords[keyword];
1251
+ }
1252
+
1253
+ function keyword2hsl(args) {
1254
+ return rgb2hsl(keyword2rgb(args));
1255
+ }
1256
+
1257
+ function keyword2hsv(args) {
1258
+ return rgb2hsv(keyword2rgb(args));
1259
+ }
1260
+
1261
+ function keyword2hwb(args) {
1262
+ return rgb2hwb(keyword2rgb(args));
1263
+ }
1264
+
1265
+ function keyword2cmyk(args) {
1266
+ return rgb2cmyk(keyword2rgb(args));
1267
+ }
1268
+
1269
+ function keyword2lab(args) {
1270
+ return rgb2lab(keyword2rgb(args));
1271
+ }
1272
+
1273
+ function keyword2xyz(args) {
1274
+ return rgb2xyz(keyword2rgb(args));
1275
+ }
1276
+
1277
+ var cssKeywords = {
1278
+ aliceblue: [240,248,255],
1279
+ antiquewhite: [250,235,215],
1280
+ aqua: [0,255,255],
1281
+ aquamarine: [127,255,212],
1282
+ azure: [240,255,255],
1283
+ beige: [245,245,220],
1284
+ bisque: [255,228,196],
1285
+ black: [0,0,0],
1286
+ blanchedalmond: [255,235,205],
1287
+ blue: [0,0,255],
1288
+ blueviolet: [138,43,226],
1289
+ brown: [165,42,42],
1290
+ burlywood: [222,184,135],
1291
+ cadetblue: [95,158,160],
1292
+ chartreuse: [127,255,0],
1293
+ chocolate: [210,105,30],
1294
+ coral: [255,127,80],
1295
+ cornflowerblue: [100,149,237],
1296
+ cornsilk: [255,248,220],
1297
+ crimson: [220,20,60],
1298
+ cyan: [0,255,255],
1299
+ darkblue: [0,0,139],
1300
+ darkcyan: [0,139,139],
1301
+ darkgoldenrod: [184,134,11],
1302
+ darkgray: [169,169,169],
1303
+ darkgreen: [0,100,0],
1304
+ darkgrey: [169,169,169],
1305
+ darkkhaki: [189,183,107],
1306
+ darkmagenta: [139,0,139],
1307
+ darkolivegreen: [85,107,47],
1308
+ darkorange: [255,140,0],
1309
+ darkorchid: [153,50,204],
1310
+ darkred: [139,0,0],
1311
+ darksalmon: [233,150,122],
1312
+ darkseagreen: [143,188,143],
1313
+ darkslateblue: [72,61,139],
1314
+ darkslategray: [47,79,79],
1315
+ darkslategrey: [47,79,79],
1316
+ darkturquoise: [0,206,209],
1317
+ darkviolet: [148,0,211],
1318
+ deeppink: [255,20,147],
1319
+ deepskyblue: [0,191,255],
1320
+ dimgray: [105,105,105],
1321
+ dimgrey: [105,105,105],
1322
+ dodgerblue: [30,144,255],
1323
+ firebrick: [178,34,34],
1324
+ floralwhite: [255,250,240],
1325
+ forestgreen: [34,139,34],
1326
+ fuchsia: [255,0,255],
1327
+ gainsboro: [220,220,220],
1328
+ ghostwhite: [248,248,255],
1329
+ gold: [255,215,0],
1330
+ goldenrod: [218,165,32],
1331
+ gray: [128,128,128],
1332
+ green: [0,128,0],
1333
+ greenyellow: [173,255,47],
1334
+ grey: [128,128,128],
1335
+ honeydew: [240,255,240],
1336
+ hotpink: [255,105,180],
1337
+ indianred: [205,92,92],
1338
+ indigo: [75,0,130],
1339
+ ivory: [255,255,240],
1340
+ khaki: [240,230,140],
1341
+ lavender: [230,230,250],
1342
+ lavenderblush: [255,240,245],
1343
+ lawngreen: [124,252,0],
1344
+ lemonchiffon: [255,250,205],
1345
+ lightblue: [173,216,230],
1346
+ lightcoral: [240,128,128],
1347
+ lightcyan: [224,255,255],
1348
+ lightgoldenrodyellow: [250,250,210],
1349
+ lightgray: [211,211,211],
1350
+ lightgreen: [144,238,144],
1351
+ lightgrey: [211,211,211],
1352
+ lightpink: [255,182,193],
1353
+ lightsalmon: [255,160,122],
1354
+ lightseagreen: [32,178,170],
1355
+ lightskyblue: [135,206,250],
1356
+ lightslategray: [119,136,153],
1357
+ lightslategrey: [119,136,153],
1358
+ lightsteelblue: [176,196,222],
1359
+ lightyellow: [255,255,224],
1360
+ lime: [0,255,0],
1361
+ limegreen: [50,205,50],
1362
+ linen: [250,240,230],
1363
+ magenta: [255,0,255],
1364
+ maroon: [128,0,0],
1365
+ mediumaquamarine: [102,205,170],
1366
+ mediumblue: [0,0,205],
1367
+ mediumorchid: [186,85,211],
1368
+ mediumpurple: [147,112,219],
1369
+ mediumseagreen: [60,179,113],
1370
+ mediumslateblue: [123,104,238],
1371
+ mediumspringgreen: [0,250,154],
1372
+ mediumturquoise: [72,209,204],
1373
+ mediumvioletred: [199,21,133],
1374
+ midnightblue: [25,25,112],
1375
+ mintcream: [245,255,250],
1376
+ mistyrose: [255,228,225],
1377
+ moccasin: [255,228,181],
1378
+ navajowhite: [255,222,173],
1379
+ navy: [0,0,128],
1380
+ oldlace: [253,245,230],
1381
+ olive: [128,128,0],
1382
+ olivedrab: [107,142,35],
1383
+ orange: [255,165,0],
1384
+ orangered: [255,69,0],
1385
+ orchid: [218,112,214],
1386
+ palegoldenrod: [238,232,170],
1387
+ palegreen: [152,251,152],
1388
+ paleturquoise: [175,238,238],
1389
+ palevioletred: [219,112,147],
1390
+ papayawhip: [255,239,213],
1391
+ peachpuff: [255,218,185],
1392
+ peru: [205,133,63],
1393
+ pink: [255,192,203],
1394
+ plum: [221,160,221],
1395
+ powderblue: [176,224,230],
1396
+ purple: [128,0,128],
1397
+ rebeccapurple: [102, 51, 153],
1398
+ red: [255,0,0],
1399
+ rosybrown: [188,143,143],
1400
+ royalblue: [65,105,225],
1401
+ saddlebrown: [139,69,19],
1402
+ salmon: [250,128,114],
1403
+ sandybrown: [244,164,96],
1404
+ seagreen: [46,139,87],
1405
+ seashell: [255,245,238],
1406
+ sienna: [160,82,45],
1407
+ silver: [192,192,192],
1408
+ skyblue: [135,206,235],
1409
+ slateblue: [106,90,205],
1410
+ slategray: [112,128,144],
1411
+ slategrey: [112,128,144],
1412
+ snow: [255,250,250],
1413
+ springgreen: [0,255,127],
1414
+ steelblue: [70,130,180],
1415
+ tan: [210,180,140],
1416
+ teal: [0,128,128],
1417
+ thistle: [216,191,216],
1418
+ tomato: [255,99,71],
1419
+ turquoise: [64,224,208],
1420
+ violet: [238,130,238],
1421
+ wheat: [245,222,179],
1422
+ white: [255,255,255],
1423
+ whitesmoke: [245,245,245],
1424
+ yellow: [255,255,0],
1425
+ yellowgreen: [154,205,50]
1426
+ };
1427
+
1428
+ var reverseKeywords = {};
1429
+ for (var key in cssKeywords) {
1430
+ reverseKeywords[JSON.stringify(cssKeywords[key])] = key;
1431
+ }
1432
+
1433
+ },{}],5:[function(require,module,exports){
1434
+ var conversions = require(4);
1435
+
1436
+ var convert = function() {
1437
+ return new Converter();
1438
+ }
1439
+
1440
+ for (var func in conversions) {
1441
+ // export Raw versions
1442
+ convert[func + "Raw"] = (function(func) {
1443
+ // accept array or plain args
1444
+ return function(arg) {
1445
+ if (typeof arg == "number")
1446
+ arg = Array.prototype.slice.call(arguments);
1447
+ return conversions[func](arg);
1448
+ }
1449
+ })(func);
1450
+
1451
+ var pair = /(\w+)2(\w+)/.exec(func),
1452
+ from = pair[1],
1453
+ to = pair[2];
1454
+
1455
+ // export rgb2hsl and ["rgb"]["hsl"]
1456
+ convert[from] = convert[from] || {};
1457
+
1458
+ convert[from][to] = convert[func] = (function(func) {
1459
+ return function(arg) {
1460
+ if (typeof arg == "number")
1461
+ arg = Array.prototype.slice.call(arguments);
1462
+
1463
+ var val = conversions[func](arg);
1464
+ if (typeof val == "string" || val === undefined)
1465
+ return val; // keyword
1466
+
1467
+ for (var i = 0; i < val.length; i++)
1468
+ val[i] = Math.round(val[i]);
1469
+ return val;
1470
+ }
1471
+ })(func);
1472
+ }
1473
+
1474
+
1475
+ /* Converter does lazy conversion and caching */
1476
+ var Converter = function() {
1477
+ this.convs = {};
1478
+ };
1479
+
1480
+ /* Either get the values for a space or
1481
+ set the values for a space, depending on args */
1482
+ Converter.prototype.routeSpace = function(space, args) {
1483
+ var values = args[0];
1484
+ if (values === undefined) {
1485
+ // color.rgb()
1486
+ return this.getValues(space);
1487
+ }
1488
+ // color.rgb(10, 10, 10)
1489
+ if (typeof values == "number") {
1490
+ values = Array.prototype.slice.call(args);
1491
+ }
1492
+
1493
+ return this.setValues(space, values);
1494
+ };
1495
+
1496
+ /* Set the values for a space, invalidating cache */
1497
+ Converter.prototype.setValues = function(space, values) {
1498
+ this.space = space;
1499
+ this.convs = {};
1500
+ this.convs[space] = values;
1501
+ return this;
1502
+ };
1503
+
1504
+ /* Get the values for a space. If there's already
1505
+ a conversion for the space, fetch it, otherwise
1506
+ compute it */
1507
+ Converter.prototype.getValues = function(space) {
1508
+ var vals = this.convs[space];
1509
+ if (!vals) {
1510
+ var fspace = this.space,
1511
+ from = this.convs[fspace];
1512
+ vals = convert[fspace][space](from);
1513
+
1514
+ this.convs[space] = vals;
1515
+ }
1516
+ return vals;
1517
+ };
1518
+
1519
+ ["rgb", "hsl", "hsv", "cmyk", "keyword"].forEach(function(space) {
1520
+ Converter.prototype[space] = function(vals) {
1521
+ return this.routeSpace(space, arguments);
1522
+ }
1523
+ });
1524
+
1525
+ module.exports = convert;
1526
+ },{"4":4}],6:[function(require,module,exports){
1527
+ 'use strict'
1528
+
1529
+ module.exports = {
1530
+ "aliceblue": [240, 248, 255],
1531
+ "antiquewhite": [250, 235, 215],
1532
+ "aqua": [0, 255, 255],
1533
+ "aquamarine": [127, 255, 212],
1534
+ "azure": [240, 255, 255],
1535
+ "beige": [245, 245, 220],
1536
+ "bisque": [255, 228, 196],
1537
+ "black": [0, 0, 0],
1538
+ "blanchedalmond": [255, 235, 205],
1539
+ "blue": [0, 0, 255],
1540
+ "blueviolet": [138, 43, 226],
1541
+ "brown": [165, 42, 42],
1542
+ "burlywood": [222, 184, 135],
1543
+ "cadetblue": [95, 158, 160],
1544
+ "chartreuse": [127, 255, 0],
1545
+ "chocolate": [210, 105, 30],
1546
+ "coral": [255, 127, 80],
1547
+ "cornflowerblue": [100, 149, 237],
1548
+ "cornsilk": [255, 248, 220],
1549
+ "crimson": [220, 20, 60],
1550
+ "cyan": [0, 255, 255],
1551
+ "darkblue": [0, 0, 139],
1552
+ "darkcyan": [0, 139, 139],
1553
+ "darkgoldenrod": [184, 134, 11],
1554
+ "darkgray": [169, 169, 169],
1555
+ "darkgreen": [0, 100, 0],
1556
+ "darkgrey": [169, 169, 169],
1557
+ "darkkhaki": [189, 183, 107],
1558
+ "darkmagenta": [139, 0, 139],
1559
+ "darkolivegreen": [85, 107, 47],
1560
+ "darkorange": [255, 140, 0],
1561
+ "darkorchid": [153, 50, 204],
1562
+ "darkred": [139, 0, 0],
1563
+ "darksalmon": [233, 150, 122],
1564
+ "darkseagreen": [143, 188, 143],
1565
+ "darkslateblue": [72, 61, 139],
1566
+ "darkslategray": [47, 79, 79],
1567
+ "darkslategrey": [47, 79, 79],
1568
+ "darkturquoise": [0, 206, 209],
1569
+ "darkviolet": [148, 0, 211],
1570
+ "deeppink": [255, 20, 147],
1571
+ "deepskyblue": [0, 191, 255],
1572
+ "dimgray": [105, 105, 105],
1573
+ "dimgrey": [105, 105, 105],
1574
+ "dodgerblue": [30, 144, 255],
1575
+ "firebrick": [178, 34, 34],
1576
+ "floralwhite": [255, 250, 240],
1577
+ "forestgreen": [34, 139, 34],
1578
+ "fuchsia": [255, 0, 255],
1579
+ "gainsboro": [220, 220, 220],
1580
+ "ghostwhite": [248, 248, 255],
1581
+ "gold": [255, 215, 0],
1582
+ "goldenrod": [218, 165, 32],
1583
+ "gray": [128, 128, 128],
1584
+ "green": [0, 128, 0],
1585
+ "greenyellow": [173, 255, 47],
1586
+ "grey": [128, 128, 128],
1587
+ "honeydew": [240, 255, 240],
1588
+ "hotpink": [255, 105, 180],
1589
+ "indianred": [205, 92, 92],
1590
+ "indigo": [75, 0, 130],
1591
+ "ivory": [255, 255, 240],
1592
+ "khaki": [240, 230, 140],
1593
+ "lavender": [230, 230, 250],
1594
+ "lavenderblush": [255, 240, 245],
1595
+ "lawngreen": [124, 252, 0],
1596
+ "lemonchiffon": [255, 250, 205],
1597
+ "lightblue": [173, 216, 230],
1598
+ "lightcoral": [240, 128, 128],
1599
+ "lightcyan": [224, 255, 255],
1600
+ "lightgoldenrodyellow": [250, 250, 210],
1601
+ "lightgray": [211, 211, 211],
1602
+ "lightgreen": [144, 238, 144],
1603
+ "lightgrey": [211, 211, 211],
1604
+ "lightpink": [255, 182, 193],
1605
+ "lightsalmon": [255, 160, 122],
1606
+ "lightseagreen": [32, 178, 170],
1607
+ "lightskyblue": [135, 206, 250],
1608
+ "lightslategray": [119, 136, 153],
1609
+ "lightslategrey": [119, 136, 153],
1610
+ "lightsteelblue": [176, 196, 222],
1611
+ "lightyellow": [255, 255, 224],
1612
+ "lime": [0, 255, 0],
1613
+ "limegreen": [50, 205, 50],
1614
+ "linen": [250, 240, 230],
1615
+ "magenta": [255, 0, 255],
1616
+ "maroon": [128, 0, 0],
1617
+ "mediumaquamarine": [102, 205, 170],
1618
+ "mediumblue": [0, 0, 205],
1619
+ "mediumorchid": [186, 85, 211],
1620
+ "mediumpurple": [147, 112, 219],
1621
+ "mediumseagreen": [60, 179, 113],
1622
+ "mediumslateblue": [123, 104, 238],
1623
+ "mediumspringgreen": [0, 250, 154],
1624
+ "mediumturquoise": [72, 209, 204],
1625
+ "mediumvioletred": [199, 21, 133],
1626
+ "midnightblue": [25, 25, 112],
1627
+ "mintcream": [245, 255, 250],
1628
+ "mistyrose": [255, 228, 225],
1629
+ "moccasin": [255, 228, 181],
1630
+ "navajowhite": [255, 222, 173],
1631
+ "navy": [0, 0, 128],
1632
+ "oldlace": [253, 245, 230],
1633
+ "olive": [128, 128, 0],
1634
+ "olivedrab": [107, 142, 35],
1635
+ "orange": [255, 165, 0],
1636
+ "orangered": [255, 69, 0],
1637
+ "orchid": [218, 112, 214],
1638
+ "palegoldenrod": [238, 232, 170],
1639
+ "palegreen": [152, 251, 152],
1640
+ "paleturquoise": [175, 238, 238],
1641
+ "palevioletred": [219, 112, 147],
1642
+ "papayawhip": [255, 239, 213],
1643
+ "peachpuff": [255, 218, 185],
1644
+ "peru": [205, 133, 63],
1645
+ "pink": [255, 192, 203],
1646
+ "plum": [221, 160, 221],
1647
+ "powderblue": [176, 224, 230],
1648
+ "purple": [128, 0, 128],
1649
+ "rebeccapurple": [102, 51, 153],
1650
+ "red": [255, 0, 0],
1651
+ "rosybrown": [188, 143, 143],
1652
+ "royalblue": [65, 105, 225],
1653
+ "saddlebrown": [139, 69, 19],
1654
+ "salmon": [250, 128, 114],
1655
+ "sandybrown": [244, 164, 96],
1656
+ "seagreen": [46, 139, 87],
1657
+ "seashell": [255, 245, 238],
1658
+ "sienna": [160, 82, 45],
1659
+ "silver": [192, 192, 192],
1660
+ "skyblue": [135, 206, 235],
1661
+ "slateblue": [106, 90, 205],
1662
+ "slategray": [112, 128, 144],
1663
+ "slategrey": [112, 128, 144],
1664
+ "snow": [255, 250, 250],
1665
+ "springgreen": [0, 255, 127],
1666
+ "steelblue": [70, 130, 180],
1667
+ "tan": [210, 180, 140],
1668
+ "teal": [0, 128, 128],
1669
+ "thistle": [216, 191, 216],
1670
+ "tomato": [255, 99, 71],
1671
+ "turquoise": [64, 224, 208],
1672
+ "violet": [238, 130, 238],
1673
+ "wheat": [245, 222, 179],
1674
+ "white": [255, 255, 255],
1675
+ "whitesmoke": [245, 245, 245],
1676
+ "yellow": [255, 255, 0],
1677
+ "yellowgreen": [154, 205, 50]
1678
+ };
1679
+
1680
+ },{}],7:[function(require,module,exports){
1681
+ /**
1682
+ * @namespace Chart
1683
+ */
1684
+ var Chart = require(29)();
1685
+
1686
+ Chart.helpers = require(45);
1687
+
1688
+ // @todo dispatch these helpers into appropriated helpers/helpers.* file and write unit tests!
1689
+ require(27)(Chart);
1690
+
1691
+ Chart.defaults = require(25);
1692
+ Chart.Element = require(26);
1693
+ Chart.elements = require(40);
1694
+ Chart.Interaction = require(28);
1695
+ Chart.platform = require(48);
1696
+
1697
+ require(31)(Chart);
1698
+ require(22)(Chart);
1699
+ require(23)(Chart);
1700
+ require(24)(Chart);
1701
+ require(30)(Chart);
1702
+ require(33)(Chart);
1703
+ require(32)(Chart);
1704
+ require(35)(Chart);
1705
+
1706
+ require(54)(Chart);
1707
+ require(52)(Chart);
1708
+ require(53)(Chart);
1709
+ require(55)(Chart);
1710
+ require(56)(Chart);
1711
+ require(57)(Chart);
1712
+
1713
+ // Controllers must be loaded after elements
1714
+ // See Chart.core.datasetController.dataElementType
1715
+ require(15)(Chart);
1716
+ require(16)(Chart);
1717
+ require(17)(Chart);
1718
+ require(18)(Chart);
1719
+ require(19)(Chart);
1720
+ require(20)(Chart);
1721
+ require(21)(Chart);
1722
+
1723
+ require(8)(Chart);
1724
+ require(9)(Chart);
1725
+ require(10)(Chart);
1726
+ require(11)(Chart);
1727
+ require(12)(Chart);
1728
+ require(13)(Chart);
1729
+ require(14)(Chart);
1730
+
1731
+ // Loading built-it plugins
1732
+ var plugins = [];
1733
+
1734
+ plugins.push(
1735
+ require(49)(Chart),
1736
+ require(50)(Chart),
1737
+ require(51)(Chart)
1738
+ );
1739
+
1740
+ Chart.plugins.register(plugins);
1741
+
1742
+ Chart.platform.initialize();
1743
+
1744
+ module.exports = Chart;
1745
+ if (typeof window !== 'undefined') {
1746
+ window.Chart = Chart;
1747
+ }
1748
+
1749
+ // DEPRECATIONS
1750
+
1751
+ /**
1752
+ * Provided for backward compatibility, use Chart.helpers.canvas instead.
1753
+ * @namespace Chart.canvasHelpers
1754
+ * @deprecated since version 2.6.0
1755
+ * @todo remove at version 3
1756
+ * @private
1757
+ */
1758
+ Chart.canvasHelpers = Chart.helpers.canvas;
1759
+
1760
+ },{"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,"35":35,"40":40,"45":45,"48":48,"49":49,"50":50,"51":51,"52":52,"53":53,"54":54,"55":55,"56":56,"57":57,"8":8,"9":9}],8:[function(require,module,exports){
1761
+ 'use strict';
1762
+
1763
+ module.exports = function(Chart) {
1764
+
1765
+ Chart.Bar = function(context, config) {
1766
+ config.type = 'bar';
1767
+
1768
+ return new Chart(context, config);
1769
+ };
1770
+
1771
+ };
1772
+
1773
+ },{}],9:[function(require,module,exports){
1774
+ 'use strict';
1775
+
1776
+ module.exports = function(Chart) {
1777
+
1778
+ Chart.Bubble = function(context, config) {
1779
+ config.type = 'bubble';
1780
+ return new Chart(context, config);
1781
+ };
1782
+
1783
+ };
1784
+
1785
+ },{}],10:[function(require,module,exports){
1786
+ 'use strict';
1787
+
1788
+ module.exports = function(Chart) {
1789
+
1790
+ Chart.Doughnut = function(context, config) {
1791
+ config.type = 'doughnut';
1792
+
1793
+ return new Chart(context, config);
1794
+ };
1795
+
1796
+ };
1797
+
1798
+ },{}],11:[function(require,module,exports){
1799
+ 'use strict';
1800
+
1801
+ module.exports = function(Chart) {
1802
+
1803
+ Chart.Line = function(context, config) {
1804
+ config.type = 'line';
1805
+
1806
+ return new Chart(context, config);
1807
+ };
1808
+
1809
+ };
1810
+
1811
+ },{}],12:[function(require,module,exports){
1812
+ 'use strict';
1813
+
1814
+ module.exports = function(Chart) {
1815
+
1816
+ Chart.PolarArea = function(context, config) {
1817
+ config.type = 'polarArea';
1818
+
1819
+ return new Chart(context, config);
1820
+ };
1821
+
1822
+ };
1823
+
1824
+ },{}],13:[function(require,module,exports){
1825
+ 'use strict';
1826
+
1827
+ module.exports = function(Chart) {
1828
+
1829
+ Chart.Radar = function(context, config) {
1830
+ config.type = 'radar';
1831
+
1832
+ return new Chart(context, config);
1833
+ };
1834
+
1835
+ };
1836
+
1837
+ },{}],14:[function(require,module,exports){
1838
+ 'use strict';
1839
+
1840
+ module.exports = function(Chart) {
1841
+ Chart.Scatter = function(context, config) {
1842
+ config.type = 'scatter';
1843
+ return new Chart(context, config);
1844
+ };
1845
+ };
1846
+
1847
+ },{}],15:[function(require,module,exports){
1848
+ 'use strict';
1849
+
1850
+ var defaults = require(25);
1851
+ var elements = require(40);
1852
+ var helpers = require(45);
1853
+
1854
+ defaults._set('bar', {
1855
+ hover: {
1856
+ mode: 'label'
1857
+ },
1858
+
1859
+ scales: {
1860
+ xAxes: [{
1861
+ type: 'category',
1862
+
1863
+ // Specific to Bar Controller
1864
+ categoryPercentage: 0.8,
1865
+ barPercentage: 0.9,
1866
+
1867
+ // offset settings
1868
+ offset: true,
1869
+
1870
+ // grid line settings
1871
+ gridLines: {
1872
+ offsetGridLines: true
1873
+ }
1874
+ }],
1875
+
1876
+ yAxes: [{
1877
+ type: 'linear'
1878
+ }]
1879
+ }
1880
+ });
1881
+
1882
+ defaults._set('horizontalBar', {
1883
+ hover: {
1884
+ mode: 'index',
1885
+ axis: 'y'
1886
+ },
1887
+
1888
+ scales: {
1889
+ xAxes: [{
1890
+ type: 'linear',
1891
+ position: 'bottom'
1892
+ }],
1893
+
1894
+ yAxes: [{
1895
+ position: 'left',
1896
+ type: 'category',
1897
+
1898
+ // Specific to Horizontal Bar Controller
1899
+ categoryPercentage: 0.8,
1900
+ barPercentage: 0.9,
1901
+
1902
+ // offset settings
1903
+ offset: true,
1904
+
1905
+ // grid line settings
1906
+ gridLines: {
1907
+ offsetGridLines: true
1908
+ }
1909
+ }]
1910
+ },
1911
+
1912
+ elements: {
1913
+ rectangle: {
1914
+ borderSkipped: 'left'
1915
+ }
1916
+ },
1917
+
1918
+ tooltips: {
1919
+ callbacks: {
1920
+ title: function(item, data) {
1921
+ // Pick first xLabel for now
1922
+ var title = '';
1923
+
1924
+ if (item.length > 0) {
1925
+ if (item[0].yLabel) {
1926
+ title = item[0].yLabel;
1927
+ } else if (data.labels.length > 0 && item[0].index < data.labels.length) {
1928
+ title = data.labels[item[0].index];
1929
+ }
1930
+ }
1931
+
1932
+ return title;
1933
+ },
1934
+
1935
+ label: function(item, data) {
1936
+ var datasetLabel = data.datasets[item.datasetIndex].label || '';
1937
+ return datasetLabel + ': ' + item.xLabel;
1938
+ }
1939
+ },
1940
+ mode: 'index',
1941
+ axis: 'y'
1942
+ }
1943
+ });
1944
+
1945
+ module.exports = function(Chart) {
1946
+
1947
+ Chart.controllers.bar = Chart.DatasetController.extend({
1948
+
1949
+ dataElementType: elements.Rectangle,
1950
+
1951
+ initialize: function() {
1952
+ var me = this;
1953
+ var meta;
1954
+
1955
+ Chart.DatasetController.prototype.initialize.apply(me, arguments);
1956
+
1957
+ meta = me.getMeta();
1958
+ meta.stack = me.getDataset().stack;
1959
+ meta.bar = true;
1960
+ },
1961
+
1962
+ update: function(reset) {
1963
+ var me = this;
1964
+ var rects = me.getMeta().data;
1965
+ var i, ilen;
1966
+
1967
+ me._ruler = me.getRuler();
1968
+
1969
+ for (i = 0, ilen = rects.length; i < ilen; ++i) {
1970
+ me.updateElement(rects[i], i, reset);
1971
+ }
1972
+ },
1973
+
1974
+ updateElement: function(rectangle, index, reset) {
1975
+ var me = this;
1976
+ var chart = me.chart;
1977
+ var meta = me.getMeta();
1978
+ var dataset = me.getDataset();
1979
+ var custom = rectangle.custom || {};
1980
+ var rectangleOptions = chart.options.elements.rectangle;
1981
+
1982
+ rectangle._xScale = me.getScaleForId(meta.xAxisID);
1983
+ rectangle._yScale = me.getScaleForId(meta.yAxisID);
1984
+ rectangle._datasetIndex = me.index;
1985
+ rectangle._index = index;
1986
+
1987
+ rectangle._model = {
1988
+ datasetLabel: dataset.label,
1989
+ label: chart.data.labels[index],
1990
+ borderSkipped: custom.borderSkipped ? custom.borderSkipped : rectangleOptions.borderSkipped,
1991
+ backgroundColor: custom.backgroundColor ? custom.backgroundColor : helpers.valueAtIndexOrDefault(dataset.backgroundColor, index, rectangleOptions.backgroundColor),
1992
+ borderColor: custom.borderColor ? custom.borderColor : helpers.valueAtIndexOrDefault(dataset.borderColor, index, rectangleOptions.borderColor),
1993
+ borderWidth: custom.borderWidth ? custom.borderWidth : helpers.valueAtIndexOrDefault(dataset.borderWidth, index, rectangleOptions.borderWidth)
1994
+ };
1995
+
1996
+ me.updateElementGeometry(rectangle, index, reset);
1997
+
1998
+ rectangle.pivot();
1999
+ },
2000
+
2001
+ /**
2002
+ * @private
2003
+ */
2004
+ updateElementGeometry: function(rectangle, index, reset) {
2005
+ var me = this;
2006
+ var model = rectangle._model;
2007
+ var vscale = me.getValueScale();
2008
+ var base = vscale.getBasePixel();
2009
+ var horizontal = vscale.isHorizontal();
2010
+ var ruler = me._ruler || me.getRuler();
2011
+ var vpixels = me.calculateBarValuePixels(me.index, index);
2012
+ var ipixels = me.calculateBarIndexPixels(me.index, index, ruler);
2013
+
2014
+ model.horizontal = horizontal;
2015
+ model.base = reset ? base : vpixels.base;
2016
+ model.x = horizontal ? reset ? base : vpixels.head : ipixels.center;
2017
+ model.y = horizontal ? ipixels.center : reset ? base : vpixels.head;
2018
+ model.height = horizontal ? ipixels.size : undefined;
2019
+ model.width = horizontal ? undefined : ipixels.size;
2020
+ },
2021
+
2022
+ /**
2023
+ * @private
2024
+ */
2025
+ getValueScaleId: function() {
2026
+ return this.getMeta().yAxisID;
2027
+ },
2028
+
2029
+ /**
2030
+ * @private
2031
+ */
2032
+ getIndexScaleId: function() {
2033
+ return this.getMeta().xAxisID;
2034
+ },
2035
+
2036
+ /**
2037
+ * @private
2038
+ */
2039
+ getValueScale: function() {
2040
+ return this.getScaleForId(this.getValueScaleId());
2041
+ },
2042
+
2043
+ /**
2044
+ * @private
2045
+ */
2046
+ getIndexScale: function() {
2047
+ return this.getScaleForId(this.getIndexScaleId());
2048
+ },
2049
+
2050
+ /**
2051
+ * Returns the effective number of stacks based on groups and bar visibility.
2052
+ * @private
2053
+ */
2054
+ getStackCount: function(last) {
2055
+ var me = this;
2056
+ var chart = me.chart;
2057
+ var scale = me.getIndexScale();
2058
+ var stacked = scale.options.stacked;
2059
+ var ilen = last === undefined ? chart.data.datasets.length : last + 1;
2060
+ var stacks = [];
2061
+ var i, meta;
2062
+
2063
+ for (i = 0; i < ilen; ++i) {
2064
+ meta = chart.getDatasetMeta(i);
2065
+ if (meta.bar && chart.isDatasetVisible(i) &&
2066
+ (stacked === false ||
2067
+ (stacked === true && stacks.indexOf(meta.stack) === -1) ||
2068
+ (stacked === undefined && (meta.stack === undefined || stacks.indexOf(meta.stack) === -1)))) {
2069
+ stacks.push(meta.stack);
2070
+ }
2071
+ }
2072
+
2073
+ return stacks.length;
2074
+ },
2075
+
2076
+ /**
2077
+ * Returns the stack index for the given dataset based on groups and bar visibility.
2078
+ * @private
2079
+ */
2080
+ getStackIndex: function(datasetIndex) {
2081
+ return this.getStackCount(datasetIndex) - 1;
2082
+ },
2083
+
2084
+ /**
2085
+ * @private
2086
+ */
2087
+ getRuler: function() {
2088
+ var me = this;
2089
+ var scale = me.getIndexScale();
2090
+ var stackCount = me.getStackCount();
2091
+ var datasetIndex = me.index;
2092
+ var pixels = [];
2093
+ var isHorizontal = scale.isHorizontal();
2094
+ var start = isHorizontal ? scale.left : scale.top;
2095
+ var end = start + (isHorizontal ? scale.width : scale.height);
2096
+ var i, ilen;
2097
+
2098
+ for (i = 0, ilen = me.getMeta().data.length; i < ilen; ++i) {
2099
+ pixels.push(scale.getPixelForValue(null, i, datasetIndex));
2100
+ }
2101
+
2102
+ return {
2103
+ pixels: pixels,
2104
+ start: start,
2105
+ end: end,
2106
+ stackCount: stackCount,
2107
+ scale: scale
2108
+ };
2109
+ },
2110
+
2111
+ /**
2112
+ * Note: pixel values are not clamped to the scale area.
2113
+ * @private
2114
+ */
2115
+ calculateBarValuePixels: function(datasetIndex, index) {
2116
+ var me = this;
2117
+ var chart = me.chart;
2118
+ var meta = me.getMeta();
2119
+ var scale = me.getValueScale();
2120
+ var datasets = chart.data.datasets;
2121
+ var value = scale.getRightValue(datasets[datasetIndex].data[index]);
2122
+ var stacked = scale.options.stacked;
2123
+ var stack = meta.stack;
2124
+ var start = 0;
2125
+ var i, imeta, ivalue, base, head, size;
2126
+
2127
+ if (stacked || (stacked === undefined && stack !== undefined)) {
2128
+ for (i = 0; i < datasetIndex; ++i) {
2129
+ imeta = chart.getDatasetMeta(i);
2130
+
2131
+ if (imeta.bar &&
2132
+ imeta.stack === stack &&
2133
+ imeta.controller.getValueScaleId() === scale.id &&
2134
+ chart.isDatasetVisible(i)) {
2135
+
2136
+ ivalue = scale.getRightValue(datasets[i].data[index]);
2137
+ if ((value < 0 && ivalue < 0) || (value >= 0 && ivalue > 0)) {
2138
+ start += ivalue;
2139
+ }
2140
+ }
2141
+ }
2142
+ }
2143
+
2144
+ base = scale.getPixelForValue(start);
2145
+ head = scale.getPixelForValue(start + value);
2146
+ size = (head - base) / 2;
2147
+
2148
+ return {
2149
+ size: size,
2150
+ base: base,
2151
+ head: head,
2152
+ center: head + size / 2
2153
+ };
2154
+ },
2155
+
2156
+ /**
2157
+ * @private
2158
+ */
2159
+ calculateBarIndexPixels: function(datasetIndex, index, ruler) {
2160
+ var me = this;
2161
+ var options = ruler.scale.options;
2162
+ var stackIndex = me.getStackIndex(datasetIndex);
2163
+ var pixels = ruler.pixels;
2164
+ var base = pixels[index];
2165
+ var length = pixels.length;
2166
+ var start = ruler.start;
2167
+ var end = ruler.end;
2168
+ var leftSampleSize, rightSampleSize, leftCategorySize, rightCategorySize, fullBarSize, size;
2169
+
2170
+ if (length === 1) {
2171
+ leftSampleSize = base > start ? base - start : end - base;
2172
+ rightSampleSize = base < end ? end - base : base - start;
2173
+ } else {
2174
+ if (index > 0) {
2175
+ leftSampleSize = (base - pixels[index - 1]) / 2;
2176
+ if (index === length - 1) {
2177
+ rightSampleSize = leftSampleSize;
2178
+ }
2179
+ }
2180
+ if (index < length - 1) {
2181
+ rightSampleSize = (pixels[index + 1] - base) / 2;
2182
+ if (index === 0) {
2183
+ leftSampleSize = rightSampleSize;
2184
+ }
2185
+ }
2186
+ }
2187
+
2188
+ leftCategorySize = leftSampleSize * options.categoryPercentage;
2189
+ rightCategorySize = rightSampleSize * options.categoryPercentage;
2190
+ fullBarSize = (leftCategorySize + rightCategorySize) / ruler.stackCount;
2191
+ size = fullBarSize * options.barPercentage;
2192
+
2193
+ size = Math.min(
2194
+ helpers.valueOrDefault(options.barThickness, size),
2195
+ helpers.valueOrDefault(options.maxBarThickness, Infinity));
2196
+
2197
+ base -= leftCategorySize;
2198
+ base += fullBarSize * stackIndex;
2199
+ base += (fullBarSize - size) / 2;
2200
+
2201
+ return {
2202
+ size: size,
2203
+ base: base,
2204
+ head: base + size,
2205
+ center: base + size / 2
2206
+ };
2207
+ },
2208
+
2209
+ draw: function() {
2210
+ var me = this;
2211
+ var chart = me.chart;
2212
+ var scale = me.getValueScale();
2213
+ var rects = me.getMeta().data;
2214
+ var dataset = me.getDataset();
2215
+ var ilen = rects.length;
2216
+ var i = 0;
2217
+
2218
+ helpers.canvas.clipArea(chart.ctx, chart.chartArea);
2219
+
2220
+ for (; i < ilen; ++i) {
2221
+ if (!isNaN(scale.getRightValue(dataset.data[i]))) {
2222
+ rects[i].draw();
2223
+ }
2224
+ }
2225
+
2226
+ helpers.canvas.unclipArea(chart.ctx);
2227
+ },
2228
+
2229
+ setHoverStyle: function(rectangle) {
2230
+ var dataset = this.chart.data.datasets[rectangle._datasetIndex];
2231
+ var index = rectangle._index;
2232
+ var custom = rectangle.custom || {};
2233
+ var model = rectangle._model;
2234
+
2235
+ model.backgroundColor = custom.hoverBackgroundColor ? custom.hoverBackgroundColor : helpers.valueAtIndexOrDefault(dataset.hoverBackgroundColor, index, helpers.getHoverColor(model.backgroundColor));
2236
+ model.borderColor = custom.hoverBorderColor ? custom.hoverBorderColor : helpers.valueAtIndexOrDefault(dataset.hoverBorderColor, index, helpers.getHoverColor(model.borderColor));
2237
+ model.borderWidth = custom.hoverBorderWidth ? custom.hoverBorderWidth : helpers.valueAtIndexOrDefault(dataset.hoverBorderWidth, index, model.borderWidth);
2238
+ },
2239
+
2240
+ removeHoverStyle: function(rectangle) {
2241
+ var dataset = this.chart.data.datasets[rectangle._datasetIndex];
2242
+ var index = rectangle._index;
2243
+ var custom = rectangle.custom || {};
2244
+ var model = rectangle._model;
2245
+ var rectangleElementOptions = this.chart.options.elements.rectangle;
2246
+
2247
+ model.backgroundColor = custom.backgroundColor ? custom.backgroundColor : helpers.valueAtIndexOrDefault(dataset.backgroundColor, index, rectangleElementOptions.backgroundColor);
2248
+ model.borderColor = custom.borderColor ? custom.borderColor : helpers.valueAtIndexOrDefault(dataset.borderColor, index, rectangleElementOptions.borderColor);
2249
+ model.borderWidth = custom.borderWidth ? custom.borderWidth : helpers.valueAtIndexOrDefault(dataset.borderWidth, index, rectangleElementOptions.borderWidth);
2250
+ }
2251
+ });
2252
+
2253
+ Chart.controllers.horizontalBar = Chart.controllers.bar.extend({
2254
+ /**
2255
+ * @private
2256
+ */
2257
+ getValueScaleId: function() {
2258
+ return this.getMeta().xAxisID;
2259
+ },
2260
+
2261
+ /**
2262
+ * @private
2263
+ */
2264
+ getIndexScaleId: function() {
2265
+ return this.getMeta().yAxisID;
2266
+ }
2267
+ });
2268
+ };
2269
+
2270
+ },{"25":25,"40":40,"45":45}],16:[function(require,module,exports){
2271
+ 'use strict';
2272
+
2273
+ var defaults = require(25);
2274
+ var elements = require(40);
2275
+ var helpers = require(45);
2276
+
2277
+ defaults._set('bubble', {
2278
+ hover: {
2279
+ mode: 'single'
2280
+ },
2281
+
2282
+ scales: {
2283
+ xAxes: [{
2284
+ type: 'linear', // bubble should probably use a linear scale by default
2285
+ position: 'bottom',
2286
+ id: 'x-axis-0' // need an ID so datasets can reference the scale
2287
+ }],
2288
+ yAxes: [{
2289
+ type: 'linear',
2290
+ position: 'left',
2291
+ id: 'y-axis-0'
2292
+ }]
2293
+ },
2294
+
2295
+ tooltips: {
2296
+ callbacks: {
2297
+ title: function() {
2298
+ // Title doesn't make sense for scatter since we format the data as a point
2299
+ return '';
2300
+ },
2301
+ label: function(item, data) {
2302
+ var datasetLabel = data.datasets[item.datasetIndex].label || '';
2303
+ var dataPoint = data.datasets[item.datasetIndex].data[item.index];
2304
+ return datasetLabel + ': (' + item.xLabel + ', ' + item.yLabel + ', ' + dataPoint.r + ')';
2305
+ }
2306
+ }
2307
+ }
2308
+ });
2309
+
2310
+
2311
+ module.exports = function(Chart) {
2312
+
2313
+ Chart.controllers.bubble = Chart.DatasetController.extend({
2314
+ /**
2315
+ * @protected
2316
+ */
2317
+ dataElementType: elements.Point,
2318
+
2319
+ /**
2320
+ * @protected
2321
+ */
2322
+ update: function(reset) {
2323
+ var me = this;
2324
+ var meta = me.getMeta();
2325
+ var points = meta.data;
2326
+
2327
+ // Update Points
2328
+ helpers.each(points, function(point, index) {
2329
+ me.updateElement(point, index, reset);
2330
+ });
2331
+ },
2332
+
2333
+ /**
2334
+ * @protected
2335
+ */
2336
+ updateElement: function(point, index, reset) {
2337
+ var me = this;
2338
+ var meta = me.getMeta();
2339
+ var custom = point.custom || {};
2340
+ var xScale = me.getScaleForId(meta.xAxisID);
2341
+ var yScale = me.getScaleForId(meta.yAxisID);
2342
+ var options = me._resolveElementOptions(point, index);
2343
+ var data = me.getDataset().data[index];
2344
+ var dsIndex = me.index;
2345
+
2346
+ var x = reset ? xScale.getPixelForDecimal(0.5) : xScale.getPixelForValue(typeof data === 'object' ? data : NaN, index, dsIndex);
2347
+ var y = reset ? yScale.getBasePixel() : yScale.getPixelForValue(data, index, dsIndex);
2348
+
2349
+ point._xScale = xScale;
2350
+ point._yScale = yScale;
2351
+ point._options = options;
2352
+ point._datasetIndex = dsIndex;
2353
+ point._index = index;
2354
+ point._model = {
2355
+ backgroundColor: options.backgroundColor,
2356
+ borderColor: options.borderColor,
2357
+ borderWidth: options.borderWidth,
2358
+ hitRadius: options.hitRadius,
2359
+ pointStyle: options.pointStyle,
2360
+ radius: reset ? 0 : options.radius,
2361
+ skip: custom.skip || isNaN(x) || isNaN(y),
2362
+ x: x,
2363
+ y: y,
2364
+ };
2365
+
2366
+ point.pivot();
2367
+ },
2368
+
2369
+ /**
2370
+ * @protected
2371
+ */
2372
+ setHoverStyle: function(point) {
2373
+ var model = point._model;
2374
+ var options = point._options;
2375
+
2376
+ model.backgroundColor = helpers.valueOrDefault(options.hoverBackgroundColor, helpers.getHoverColor(options.backgroundColor));
2377
+ model.borderColor = helpers.valueOrDefault(options.hoverBorderColor, helpers.getHoverColor(options.borderColor));
2378
+ model.borderWidth = helpers.valueOrDefault(options.hoverBorderWidth, options.borderWidth);
2379
+ model.radius = options.radius + options.hoverRadius;
2380
+ },
2381
+
2382
+ /**
2383
+ * @protected
2384
+ */
2385
+ removeHoverStyle: function(point) {
2386
+ var model = point._model;
2387
+ var options = point._options;
2388
+
2389
+ model.backgroundColor = options.backgroundColor;
2390
+ model.borderColor = options.borderColor;
2391
+ model.borderWidth = options.borderWidth;
2392
+ model.radius = options.radius;
2393
+ },
2394
+
2395
+ /**
2396
+ * @private
2397
+ */
2398
+ _resolveElementOptions: function(point, index) {
2399
+ var me = this;
2400
+ var chart = me.chart;
2401
+ var datasets = chart.data.datasets;
2402
+ var dataset = datasets[me.index];
2403
+ var custom = point.custom || {};
2404
+ var options = chart.options.elements.point;
2405
+ var resolve = helpers.options.resolve;
2406
+ var data = dataset.data[index];
2407
+ var values = {};
2408
+ var i, ilen, key;
2409
+
2410
+ // Scriptable options
2411
+ var context = {
2412
+ chart: chart,
2413
+ dataIndex: index,
2414
+ dataset: dataset,
2415
+ datasetIndex: me.index
2416
+ };
2417
+
2418
+ var keys = [
2419
+ 'backgroundColor',
2420
+ 'borderColor',
2421
+ 'borderWidth',
2422
+ 'hoverBackgroundColor',
2423
+ 'hoverBorderColor',
2424
+ 'hoverBorderWidth',
2425
+ 'hoverRadius',
2426
+ 'hitRadius',
2427
+ 'pointStyle'
2428
+ ];
2429
+
2430
+ for (i = 0, ilen = keys.length; i < ilen; ++i) {
2431
+ key = keys[i];
2432
+ values[key] = resolve([
2433
+ custom[key],
2434
+ dataset[key],
2435
+ options[key]
2436
+ ], context, index);
2437
+ }
2438
+
2439
+ // Custom radius resolution
2440
+ values.radius = resolve([
2441
+ custom.radius,
2442
+ data ? data.r : undefined,
2443
+ dataset.radius,
2444
+ options.radius
2445
+ ], context, index);
2446
+
2447
+ return values;
2448
+ }
2449
+ });
2450
+ };
2451
+
2452
+ },{"25":25,"40":40,"45":45}],17:[function(require,module,exports){
2453
+ 'use strict';
2454
+
2455
+ var defaults = require(25);
2456
+ var elements = require(40);
2457
+ var helpers = require(45);
2458
+
2459
+ defaults._set('doughnut', {
2460
+ animation: {
2461
+ // Boolean - Whether we animate the rotation of the Doughnut
2462
+ animateRotate: true,
2463
+ // Boolean - Whether we animate scaling the Doughnut from the centre
2464
+ animateScale: false
2465
+ },
2466
+ hover: {
2467
+ mode: 'single'
2468
+ },
2469
+ legendCallback: function(chart) {
2470
+ var text = [];
2471
+ text.push('<ul class="' + chart.id + '-legend">');
2472
+
2473
+ var data = chart.data;
2474
+ var datasets = data.datasets;
2475
+ var labels = data.labels;
2476
+
2477
+ if (datasets.length) {
2478
+ for (var i = 0; i < datasets[0].data.length; ++i) {
2479
+ text.push('<li><span style="background-color:' + datasets[0].backgroundColor[i] + '"></span>');
2480
+ if (labels[i]) {
2481
+ text.push(labels[i]);
2482
+ }
2483
+ text.push('</li>');
2484
+ }
2485
+ }
2486
+
2487
+ text.push('</ul>');
2488
+ return text.join('');
2489
+ },
2490
+ legend: {
2491
+ labels: {
2492
+ generateLabels: function(chart) {
2493
+ var data = chart.data;
2494
+ if (data.labels.length && data.datasets.length) {
2495
+ return data.labels.map(function(label, i) {
2496
+ var meta = chart.getDatasetMeta(0);
2497
+ var ds = data.datasets[0];
2498
+ var arc = meta.data[i];
2499
+ var custom = arc && arc.custom || {};
2500
+ var valueAtIndexOrDefault = helpers.valueAtIndexOrDefault;
2501
+ var arcOpts = chart.options.elements.arc;
2502
+ var fill = custom.backgroundColor ? custom.backgroundColor : valueAtIndexOrDefault(ds.backgroundColor, i, arcOpts.backgroundColor);
2503
+ var stroke = custom.borderColor ? custom.borderColor : valueAtIndexOrDefault(ds.borderColor, i, arcOpts.borderColor);
2504
+ var bw = custom.borderWidth ? custom.borderWidth : valueAtIndexOrDefault(ds.borderWidth, i, arcOpts.borderWidth);
2505
+
2506
+ return {
2507
+ text: label,
2508
+ fillStyle: fill,
2509
+ strokeStyle: stroke,
2510
+ lineWidth: bw,
2511
+ hidden: isNaN(ds.data[i]) || meta.data[i].hidden,
2512
+
2513
+ // Extra data used for toggling the correct item
2514
+ index: i
2515
+ };
2516
+ });
2517
+ }
2518
+ return [];
2519
+ }
2520
+ },
2521
+
2522
+ onClick: function(e, legendItem) {
2523
+ var index = legendItem.index;
2524
+ var chart = this.chart;
2525
+ var i, ilen, meta;
2526
+
2527
+ for (i = 0, ilen = (chart.data.datasets || []).length; i < ilen; ++i) {
2528
+ meta = chart.getDatasetMeta(i);
2529
+ // toggle visibility of index if exists
2530
+ if (meta.data[index]) {
2531
+ meta.data[index].hidden = !meta.data[index].hidden;
2532
+ }
2533
+ }
2534
+
2535
+ chart.update();
2536
+ }
2537
+ },
2538
+
2539
+ // The percentage of the chart that we cut out of the middle.
2540
+ cutoutPercentage: 50,
2541
+
2542
+ // The rotation of the chart, where the first data arc begins.
2543
+ rotation: Math.PI * -0.5,
2544
+
2545
+ // The total circumference of the chart.
2546
+ circumference: Math.PI * 2.0,
2547
+
2548
+ // Need to override these to give a nice default
2549
+ tooltips: {
2550
+ callbacks: {
2551
+ title: function() {
2552
+ return '';
2553
+ },
2554
+ label: function(tooltipItem, data) {
2555
+ var dataLabel = data.labels[tooltipItem.index];
2556
+ var value = ': ' + data.datasets[tooltipItem.datasetIndex].data[tooltipItem.index];
2557
+
2558
+ if (helpers.isArray(dataLabel)) {
2559
+ // show value on first line of multiline label
2560
+ // need to clone because we are changing the value
2561
+ dataLabel = dataLabel.slice();
2562
+ dataLabel[0] += value;
2563
+ } else {
2564
+ dataLabel += value;
2565
+ }
2566
+
2567
+ return dataLabel;
2568
+ }
2569
+ }
2570
+ }
2571
+ });
2572
+
2573
+ defaults._set('pie', helpers.clone(defaults.doughnut));
2574
+ defaults._set('pie', {
2575
+ cutoutPercentage: 0
2576
+ });
2577
+
2578
+ module.exports = function(Chart) {
2579
+
2580
+ Chart.controllers.doughnut = Chart.controllers.pie = Chart.DatasetController.extend({
2581
+
2582
+ dataElementType: elements.Arc,
2583
+
2584
+ linkScales: helpers.noop,
2585
+
2586
+ // Get index of the dataset in relation to the visible datasets. This allows determining the inner and outer radius correctly
2587
+ getRingIndex: function(datasetIndex) {
2588
+ var ringIndex = 0;
2589
+
2590
+ for (var j = 0; j < datasetIndex; ++j) {
2591
+ if (this.chart.isDatasetVisible(j)) {
2592
+ ++ringIndex;
2593
+ }
2594
+ }
2595
+
2596
+ return ringIndex;
2597
+ },
2598
+
2599
+ update: function(reset) {
2600
+ var me = this;
2601
+ var chart = me.chart;
2602
+ var chartArea = chart.chartArea;
2603
+ var opts = chart.options;
2604
+ var arcOpts = opts.elements.arc;
2605
+ var availableWidth = chartArea.right - chartArea.left - arcOpts.borderWidth;
2606
+ var availableHeight = chartArea.bottom - chartArea.top - arcOpts.borderWidth;
2607
+ var minSize = Math.min(availableWidth, availableHeight);
2608
+ var offset = {x: 0, y: 0};
2609
+ var meta = me.getMeta();
2610
+ var cutoutPercentage = opts.cutoutPercentage;
2611
+ var circumference = opts.circumference;
2612
+
2613
+ // If the chart's circumference isn't a full circle, calculate minSize as a ratio of the width/height of the arc
2614
+ if (circumference < Math.PI * 2.0) {
2615
+ var startAngle = opts.rotation % (Math.PI * 2.0);
2616
+ startAngle += Math.PI * 2.0 * (startAngle >= Math.PI ? -1 : startAngle < -Math.PI ? 1 : 0);
2617
+ var endAngle = startAngle + circumference;
2618
+ var start = {x: Math.cos(startAngle), y: Math.sin(startAngle)};
2619
+ var end = {x: Math.cos(endAngle), y: Math.sin(endAngle)};
2620
+ var contains0 = (startAngle <= 0 && endAngle >= 0) || (startAngle <= Math.PI * 2.0 && Math.PI * 2.0 <= endAngle);
2621
+ var contains90 = (startAngle <= Math.PI * 0.5 && Math.PI * 0.5 <= endAngle) || (startAngle <= Math.PI * 2.5 && Math.PI * 2.5 <= endAngle);
2622
+ var contains180 = (startAngle <= -Math.PI && -Math.PI <= endAngle) || (startAngle <= Math.PI && Math.PI <= endAngle);
2623
+ var contains270 = (startAngle <= -Math.PI * 0.5 && -Math.PI * 0.5 <= endAngle) || (startAngle <= Math.PI * 1.5 && Math.PI * 1.5 <= endAngle);
2624
+ var cutout = cutoutPercentage / 100.0;
2625
+ 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))};
2626
+ 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))};
2627
+ var size = {width: (max.x - min.x) * 0.5, height: (max.y - min.y) * 0.5};
2628
+ minSize = Math.min(availableWidth / size.width, availableHeight / size.height);
2629
+ offset = {x: (max.x + min.x) * -0.5, y: (max.y + min.y) * -0.5};
2630
+ }
2631
+
2632
+ chart.borderWidth = me.getMaxBorderWidth(meta.data);
2633
+ chart.outerRadius = Math.max((minSize - chart.borderWidth) / 2, 0);
2634
+ chart.innerRadius = Math.max(cutoutPercentage ? (chart.outerRadius / 100) * (cutoutPercentage) : 0, 0);
2635
+ chart.radiusLength = (chart.outerRadius - chart.innerRadius) / chart.getVisibleDatasetCount();
2636
+ chart.offsetX = offset.x * chart.outerRadius;
2637
+ chart.offsetY = offset.y * chart.outerRadius;
2638
+
2639
+ meta.total = me.calculateTotal();
2640
+
2641
+ me.outerRadius = chart.outerRadius - (chart.radiusLength * me.getRingIndex(me.index));
2642
+ me.innerRadius = Math.max(me.outerRadius - chart.radiusLength, 0);
2643
+
2644
+ helpers.each(meta.data, function(arc, index) {
2645
+ me.updateElement(arc, index, reset);
2646
+ });
2647
+ },
2648
+
2649
+ updateElement: function(arc, index, reset) {
2650
+ var me = this;
2651
+ var chart = me.chart;
2652
+ var chartArea = chart.chartArea;
2653
+ var opts = chart.options;
2654
+ var animationOpts = opts.animation;
2655
+ var centerX = (chartArea.left + chartArea.right) / 2;
2656
+ var centerY = (chartArea.top + chartArea.bottom) / 2;
2657
+ var startAngle = opts.rotation; // non reset case handled later
2658
+ var endAngle = opts.rotation; // non reset case handled later
2659
+ var dataset = me.getDataset();
2660
+ var circumference = reset && animationOpts.animateRotate ? 0 : arc.hidden ? 0 : me.calculateCircumference(dataset.data[index]) * (opts.circumference / (2.0 * Math.PI));
2661
+ var innerRadius = reset && animationOpts.animateScale ? 0 : me.innerRadius;
2662
+ var outerRadius = reset && animationOpts.animateScale ? 0 : me.outerRadius;
2663
+ var valueAtIndexOrDefault = helpers.valueAtIndexOrDefault;
2664
+
2665
+ helpers.extend(arc, {
2666
+ // Utility
2667
+ _datasetIndex: me.index,
2668
+ _index: index,
2669
+
2670
+ // Desired view properties
2671
+ _model: {
2672
+ x: centerX + chart.offsetX,
2673
+ y: centerY + chart.offsetY,
2674
+ startAngle: startAngle,
2675
+ endAngle: endAngle,
2676
+ circumference: circumference,
2677
+ outerRadius: outerRadius,
2678
+ innerRadius: innerRadius,
2679
+ label: valueAtIndexOrDefault(dataset.label, index, chart.data.labels[index])
2680
+ }
2681
+ });
2682
+
2683
+ var model = arc._model;
2684
+ // Resets the visual styles
2685
+ this.removeHoverStyle(arc);
2686
+
2687
+ // Set correct angles if not resetting
2688
+ if (!reset || !animationOpts.animateRotate) {
2689
+ if (index === 0) {
2690
+ model.startAngle = opts.rotation;
2691
+ } else {
2692
+ model.startAngle = me.getMeta().data[index - 1]._model.endAngle;
2693
+ }
2694
+
2695
+ model.endAngle = model.startAngle + model.circumference;
2696
+ }
2697
+
2698
+ arc.pivot();
2699
+ },
2700
+
2701
+ removeHoverStyle: function(arc) {
2702
+ Chart.DatasetController.prototype.removeHoverStyle.call(this, arc, this.chart.options.elements.arc);
2703
+ },
2704
+
2705
+ calculateTotal: function() {
2706
+ var dataset = this.getDataset();
2707
+ var meta = this.getMeta();
2708
+ var total = 0;
2709
+ var value;
2710
+
2711
+ helpers.each(meta.data, function(element, index) {
2712
+ value = dataset.data[index];
2713
+ if (!isNaN(value) && !element.hidden) {
2714
+ total += Math.abs(value);
2715
+ }
2716
+ });
2717
+
2718
+ /* if (total === 0) {
2719
+ total = NaN;
2720
+ }*/
2721
+
2722
+ return total;
2723
+ },
2724
+
2725
+ calculateCircumference: function(value) {
2726
+ var total = this.getMeta().total;
2727
+ if (total > 0 && !isNaN(value)) {
2728
+ return (Math.PI * 2.0) * (value / total);
2729
+ }
2730
+ return 0;
2731
+ },
2732
+
2733
+ // gets the max border or hover width to properly scale pie charts
2734
+ getMaxBorderWidth: function(arcs) {
2735
+ var max = 0;
2736
+ var index = this.index;
2737
+ var length = arcs.length;
2738
+ var borderWidth;
2739
+ var hoverWidth;
2740
+
2741
+ for (var i = 0; i < length; i++) {
2742
+ borderWidth = arcs[i]._model ? arcs[i]._model.borderWidth : 0;
2743
+ hoverWidth = arcs[i]._chart ? arcs[i]._chart.config.data.datasets[index].hoverBorderWidth : 0;
2744
+
2745
+ max = borderWidth > max ? borderWidth : max;
2746
+ max = hoverWidth > max ? hoverWidth : max;
2747
+ }
2748
+ return max;
2749
+ }
2750
+ });
2751
+ };
2752
+
2753
+ },{"25":25,"40":40,"45":45}],18:[function(require,module,exports){
2754
+ 'use strict';
2755
+
2756
+ var defaults = require(25);
2757
+ var elements = require(40);
2758
+ var helpers = require(45);
2759
+
2760
+ defaults._set('line', {
2761
+ showLines: true,
2762
+ spanGaps: false,
2763
+
2764
+ hover: {
2765
+ mode: 'label'
2766
+ },
2767
+
2768
+ scales: {
2769
+ xAxes: [{
2770
+ type: 'category',
2771
+ id: 'x-axis-0'
2772
+ }],
2773
+ yAxes: [{
2774
+ type: 'linear',
2775
+ id: 'y-axis-0'
2776
+ }]
2777
+ }
2778
+ });
2779
+
2780
+ module.exports = function(Chart) {
2781
+
2782
+ function lineEnabled(dataset, options) {
2783
+ return helpers.valueOrDefault(dataset.showLine, options.showLines);
2784
+ }
2785
+
2786
+ Chart.controllers.line = Chart.DatasetController.extend({
2787
+
2788
+ datasetElementType: elements.Line,
2789
+
2790
+ dataElementType: elements.Point,
2791
+
2792
+ update: function(reset) {
2793
+ var me = this;
2794
+ var meta = me.getMeta();
2795
+ var line = meta.dataset;
2796
+ var points = meta.data || [];
2797
+ var options = me.chart.options;
2798
+ var lineElementOptions = options.elements.line;
2799
+ var scale = me.getScaleForId(meta.yAxisID);
2800
+ var i, ilen, custom;
2801
+ var dataset = me.getDataset();
2802
+ var showLine = lineEnabled(dataset, options);
2803
+
2804
+ // Update Line
2805
+ if (showLine) {
2806
+ custom = line.custom || {};
2807
+
2808
+ // Compatibility: If the properties are defined with only the old name, use those values
2809
+ if ((dataset.tension !== undefined) && (dataset.lineTension === undefined)) {
2810
+ dataset.lineTension = dataset.tension;
2811
+ }
2812
+
2813
+ // Utility
2814
+ line._scale = scale;
2815
+ line._datasetIndex = me.index;
2816
+ // Data
2817
+ line._children = points;
2818
+ // Model
2819
+ line._model = {
2820
+ // Appearance
2821
+ // The default behavior of lines is to break at null values, according
2822
+ // to https://github.com/chartjs/Chart.js/issues/2435#issuecomment-216718158
2823
+ // This option gives lines the ability to span gaps
2824
+ spanGaps: dataset.spanGaps ? dataset.spanGaps : options.spanGaps,
2825
+ tension: custom.tension ? custom.tension : helpers.valueOrDefault(dataset.lineTension, lineElementOptions.tension),
2826
+ backgroundColor: custom.backgroundColor ? custom.backgroundColor : (dataset.backgroundColor || lineElementOptions.backgroundColor),
2827
+ borderWidth: custom.borderWidth ? custom.borderWidth : (dataset.borderWidth || lineElementOptions.borderWidth),
2828
+ borderColor: custom.borderColor ? custom.borderColor : (dataset.borderColor || lineElementOptions.borderColor),
2829
+ borderCapStyle: custom.borderCapStyle ? custom.borderCapStyle : (dataset.borderCapStyle || lineElementOptions.borderCapStyle),
2830
+ borderDash: custom.borderDash ? custom.borderDash : (dataset.borderDash || lineElementOptions.borderDash),
2831
+ borderDashOffset: custom.borderDashOffset ? custom.borderDashOffset : (dataset.borderDashOffset || lineElementOptions.borderDashOffset),
2832
+ borderJoinStyle: custom.borderJoinStyle ? custom.borderJoinStyle : (dataset.borderJoinStyle || lineElementOptions.borderJoinStyle),
2833
+ fill: custom.fill ? custom.fill : (dataset.fill !== undefined ? dataset.fill : lineElementOptions.fill),
2834
+ steppedLine: custom.steppedLine ? custom.steppedLine : helpers.valueOrDefault(dataset.steppedLine, lineElementOptions.stepped),
2835
+ cubicInterpolationMode: custom.cubicInterpolationMode ? custom.cubicInterpolationMode : helpers.valueOrDefault(dataset.cubicInterpolationMode, lineElementOptions.cubicInterpolationMode),
2836
+ };
2837
+
2838
+ line.pivot();
2839
+ }
2840
+
2841
+ // Update Points
2842
+ for (i = 0, ilen = points.length; i < ilen; ++i) {
2843
+ me.updateElement(points[i], i, reset);
2844
+ }
2845
+
2846
+ if (showLine && line._model.tension !== 0) {
2847
+ me.updateBezierControlPoints();
2848
+ }
2849
+
2850
+ // Now pivot the point for animation
2851
+ for (i = 0, ilen = points.length; i < ilen; ++i) {
2852
+ points[i].pivot();
2853
+ }
2854
+ },
2855
+
2856
+ getPointBackgroundColor: function(point, index) {
2857
+ var backgroundColor = this.chart.options.elements.point.backgroundColor;
2858
+ var dataset = this.getDataset();
2859
+ var custom = point.custom || {};
2860
+
2861
+ if (custom.backgroundColor) {
2862
+ backgroundColor = custom.backgroundColor;
2863
+ } else if (dataset.pointBackgroundColor) {
2864
+ backgroundColor = helpers.valueAtIndexOrDefault(dataset.pointBackgroundColor, index, backgroundColor);
2865
+ } else if (dataset.backgroundColor) {
2866
+ backgroundColor = dataset.backgroundColor;
2867
+ }
2868
+
2869
+ return backgroundColor;
2870
+ },
2871
+
2872
+ getPointBorderColor: function(point, index) {
2873
+ var borderColor = this.chart.options.elements.point.borderColor;
2874
+ var dataset = this.getDataset();
2875
+ var custom = point.custom || {};
2876
+
2877
+ if (custom.borderColor) {
2878
+ borderColor = custom.borderColor;
2879
+ } else if (dataset.pointBorderColor) {
2880
+ borderColor = helpers.valueAtIndexOrDefault(dataset.pointBorderColor, index, borderColor);
2881
+ } else if (dataset.borderColor) {
2882
+ borderColor = dataset.borderColor;
2883
+ }
2884
+
2885
+ return borderColor;
2886
+ },
2887
+
2888
+ getPointBorderWidth: function(point, index) {
2889
+ var borderWidth = this.chart.options.elements.point.borderWidth;
2890
+ var dataset = this.getDataset();
2891
+ var custom = point.custom || {};
2892
+
2893
+ if (!isNaN(custom.borderWidth)) {
2894
+ borderWidth = custom.borderWidth;
2895
+ } else if (!isNaN(dataset.pointBorderWidth) || helpers.isArray(dataset.pointBorderWidth)) {
2896
+ borderWidth = helpers.valueAtIndexOrDefault(dataset.pointBorderWidth, index, borderWidth);
2897
+ } else if (!isNaN(dataset.borderWidth)) {
2898
+ borderWidth = dataset.borderWidth;
2899
+ }
2900
+
2901
+ return borderWidth;
2902
+ },
2903
+
2904
+ updateElement: function(point, index, reset) {
2905
+ var me = this;
2906
+ var meta = me.getMeta();
2907
+ var custom = point.custom || {};
2908
+ var dataset = me.getDataset();
2909
+ var datasetIndex = me.index;
2910
+ var value = dataset.data[index];
2911
+ var yScale = me.getScaleForId(meta.yAxisID);
2912
+ var xScale = me.getScaleForId(meta.xAxisID);
2913
+ var pointOptions = me.chart.options.elements.point;
2914
+ var x, y;
2915
+
2916
+ // Compatibility: If the properties are defined with only the old name, use those values
2917
+ if ((dataset.radius !== undefined) && (dataset.pointRadius === undefined)) {
2918
+ dataset.pointRadius = dataset.radius;
2919
+ }
2920
+ if ((dataset.hitRadius !== undefined) && (dataset.pointHitRadius === undefined)) {
2921
+ dataset.pointHitRadius = dataset.hitRadius;
2922
+ }
2923
+
2924
+ x = xScale.getPixelForValue(typeof value === 'object' ? value : NaN, index, datasetIndex);
2925
+ y = reset ? yScale.getBasePixel() : me.calculatePointY(value, index, datasetIndex);
2926
+
2927
+ // Utility
2928
+ point._xScale = xScale;
2929
+ point._yScale = yScale;
2930
+ point._datasetIndex = datasetIndex;
2931
+ point._index = index;
2932
+
2933
+ // Desired view properties
2934
+ point._model = {
2935
+ x: x,
2936
+ y: y,
2937
+ skip: custom.skip || isNaN(x) || isNaN(y),
2938
+ // Appearance
2939
+ radius: custom.radius || helpers.valueAtIndexOrDefault(dataset.pointRadius, index, pointOptions.radius),
2940
+ pointStyle: custom.pointStyle || helpers.valueAtIndexOrDefault(dataset.pointStyle, index, pointOptions.pointStyle),
2941
+ backgroundColor: me.getPointBackgroundColor(point, index),
2942
+ borderColor: me.getPointBorderColor(point, index),
2943
+ borderWidth: me.getPointBorderWidth(point, index),
2944
+ tension: meta.dataset._model ? meta.dataset._model.tension : 0,
2945
+ steppedLine: meta.dataset._model ? meta.dataset._model.steppedLine : false,
2946
+ // Tooltip
2947
+ hitRadius: custom.hitRadius || helpers.valueAtIndexOrDefault(dataset.pointHitRadius, index, pointOptions.hitRadius)
2948
+ };
2949
+ },
2950
+
2951
+ calculatePointY: function(value, index, datasetIndex) {
2952
+ var me = this;
2953
+ var chart = me.chart;
2954
+ var meta = me.getMeta();
2955
+ var yScale = me.getScaleForId(meta.yAxisID);
2956
+ var sumPos = 0;
2957
+ var sumNeg = 0;
2958
+ var i, ds, dsMeta;
2959
+
2960
+ if (yScale.options.stacked) {
2961
+ for (i = 0; i < datasetIndex; i++) {
2962
+ ds = chart.data.datasets[i];
2963
+ dsMeta = chart.getDatasetMeta(i);
2964
+ if (dsMeta.type === 'line' && dsMeta.yAxisID === yScale.id && chart.isDatasetVisible(i)) {
2965
+ var stackedRightValue = Number(yScale.getRightValue(ds.data[index]));
2966
+ if (stackedRightValue < 0) {
2967
+ sumNeg += stackedRightValue || 0;
2968
+ } else {
2969
+ sumPos += stackedRightValue || 0;
2970
+ }
2971
+ }
2972
+ }
2973
+
2974
+ var rightValue = Number(yScale.getRightValue(value));
2975
+ if (rightValue < 0) {
2976
+ return yScale.getPixelForValue(sumNeg + rightValue);
2977
+ }
2978
+ return yScale.getPixelForValue(sumPos + rightValue);
2979
+ }
2980
+
2981
+ return yScale.getPixelForValue(value);
2982
+ },
2983
+
2984
+ updateBezierControlPoints: function() {
2985
+ var me = this;
2986
+ var meta = me.getMeta();
2987
+ var area = me.chart.chartArea;
2988
+ var points = (meta.data || []);
2989
+ var i, ilen, point, model, controlPoints;
2990
+
2991
+ // Only consider points that are drawn in case the spanGaps option is used
2992
+ if (meta.dataset._model.spanGaps) {
2993
+ points = points.filter(function(pt) {
2994
+ return !pt._model.skip;
2995
+ });
2996
+ }
2997
+
2998
+ function capControlPoint(pt, min, max) {
2999
+ return Math.max(Math.min(pt, max), min);
3000
+ }
3001
+
3002
+ if (meta.dataset._model.cubicInterpolationMode === 'monotone') {
3003
+ helpers.splineCurveMonotone(points);
3004
+ } else {
3005
+ for (i = 0, ilen = points.length; i < ilen; ++i) {
3006
+ point = points[i];
3007
+ model = point._model;
3008
+ controlPoints = helpers.splineCurve(
3009
+ helpers.previousItem(points, i)._model,
3010
+ model,
3011
+ helpers.nextItem(points, i)._model,
3012
+ meta.dataset._model.tension
3013
+ );
3014
+ model.controlPointPreviousX = controlPoints.previous.x;
3015
+ model.controlPointPreviousY = controlPoints.previous.y;
3016
+ model.controlPointNextX = controlPoints.next.x;
3017
+ model.controlPointNextY = controlPoints.next.y;
3018
+ }
3019
+ }
3020
+
3021
+ if (me.chart.options.elements.line.capBezierPoints) {
3022
+ for (i = 0, ilen = points.length; i < ilen; ++i) {
3023
+ model = points[i]._model;
3024
+ model.controlPointPreviousX = capControlPoint(model.controlPointPreviousX, area.left, area.right);
3025
+ model.controlPointPreviousY = capControlPoint(model.controlPointPreviousY, area.top, area.bottom);
3026
+ model.controlPointNextX = capControlPoint(model.controlPointNextX, area.left, area.right);
3027
+ model.controlPointNextY = capControlPoint(model.controlPointNextY, area.top, area.bottom);
3028
+ }
3029
+ }
3030
+ },
3031
+
3032
+ draw: function() {
3033
+ var me = this;
3034
+ var chart = me.chart;
3035
+ var meta = me.getMeta();
3036
+ var points = meta.data || [];
3037
+ var area = chart.chartArea;
3038
+ var ilen = points.length;
3039
+ var i = 0;
3040
+
3041
+ helpers.canvas.clipArea(chart.ctx, area);
3042
+
3043
+ if (lineEnabled(me.getDataset(), chart.options)) {
3044
+ meta.dataset.draw();
3045
+ }
3046
+
3047
+ helpers.canvas.unclipArea(chart.ctx);
3048
+
3049
+ // Draw the points
3050
+ for (; i < ilen; ++i) {
3051
+ points[i].draw(area);
3052
+ }
3053
+ },
3054
+
3055
+ setHoverStyle: function(point) {
3056
+ // Point
3057
+ var dataset = this.chart.data.datasets[point._datasetIndex];
3058
+ var index = point._index;
3059
+ var custom = point.custom || {};
3060
+ var model = point._model;
3061
+
3062
+ model.radius = custom.hoverRadius || helpers.valueAtIndexOrDefault(dataset.pointHoverRadius, index, this.chart.options.elements.point.hoverRadius);
3063
+ model.backgroundColor = custom.hoverBackgroundColor || helpers.valueAtIndexOrDefault(dataset.pointHoverBackgroundColor, index, helpers.getHoverColor(model.backgroundColor));
3064
+ model.borderColor = custom.hoverBorderColor || helpers.valueAtIndexOrDefault(dataset.pointHoverBorderColor, index, helpers.getHoverColor(model.borderColor));
3065
+ model.borderWidth = custom.hoverBorderWidth || helpers.valueAtIndexOrDefault(dataset.pointHoverBorderWidth, index, model.borderWidth);
3066
+ },
3067
+
3068
+ removeHoverStyle: function(point) {
3069
+ var me = this;
3070
+ var dataset = me.chart.data.datasets[point._datasetIndex];
3071
+ var index = point._index;
3072
+ var custom = point.custom || {};
3073
+ var model = point._model;
3074
+
3075
+ // Compatibility: If the properties are defined with only the old name, use those values
3076
+ if ((dataset.radius !== undefined) && (dataset.pointRadius === undefined)) {
3077
+ dataset.pointRadius = dataset.radius;
3078
+ }
3079
+
3080
+ model.radius = custom.radius || helpers.valueAtIndexOrDefault(dataset.pointRadius, index, me.chart.options.elements.point.radius);
3081
+ model.backgroundColor = me.getPointBackgroundColor(point, index);
3082
+ model.borderColor = me.getPointBorderColor(point, index);
3083
+ model.borderWidth = me.getPointBorderWidth(point, index);
3084
+ }
3085
+ });
3086
+ };
3087
+
3088
+ },{"25":25,"40":40,"45":45}],19:[function(require,module,exports){
3089
+ 'use strict';
3090
+
3091
+ var defaults = require(25);
3092
+ var elements = require(40);
3093
+ var helpers = require(45);
3094
+
3095
+ defaults._set('polarArea', {
3096
+ scale: {
3097
+ type: 'radialLinear',
3098
+ angleLines: {
3099
+ display: false
3100
+ },
3101
+ gridLines: {
3102
+ circular: true
3103
+ },
3104
+ pointLabels: {
3105
+ display: false
3106
+ },
3107
+ ticks: {
3108
+ beginAtZero: true
3109
+ }
3110
+ },
3111
+
3112
+ // Boolean - Whether to animate the rotation of the chart
3113
+ animation: {
3114
+ animateRotate: true,
3115
+ animateScale: true
3116
+ },
3117
+
3118
+ startAngle: -0.5 * Math.PI,
3119
+ legendCallback: function(chart) {
3120
+ var text = [];
3121
+ text.push('<ul class="' + chart.id + '-legend">');
3122
+
3123
+ var data = chart.data;
3124
+ var datasets = data.datasets;
3125
+ var labels = data.labels;
3126
+
3127
+ if (datasets.length) {
3128
+ for (var i = 0; i < datasets[0].data.length; ++i) {
3129
+ text.push('<li><span style="background-color:' + datasets[0].backgroundColor[i] + '"></span>');
3130
+ if (labels[i]) {
3131
+ text.push(labels[i]);
3132
+ }
3133
+ text.push('</li>');
3134
+ }
3135
+ }
3136
+
3137
+ text.push('</ul>');
3138
+ return text.join('');
3139
+ },
3140
+ legend: {
3141
+ labels: {
3142
+ generateLabels: function(chart) {
3143
+ var data = chart.data;
3144
+ if (data.labels.length && data.datasets.length) {
3145
+ return data.labels.map(function(label, i) {
3146
+ var meta = chart.getDatasetMeta(0);
3147
+ var ds = data.datasets[0];
3148
+ var arc = meta.data[i];
3149
+ var custom = arc.custom || {};
3150
+ var valueAtIndexOrDefault = helpers.valueAtIndexOrDefault;
3151
+ var arcOpts = chart.options.elements.arc;
3152
+ var fill = custom.backgroundColor ? custom.backgroundColor : valueAtIndexOrDefault(ds.backgroundColor, i, arcOpts.backgroundColor);
3153
+ var stroke = custom.borderColor ? custom.borderColor : valueAtIndexOrDefault(ds.borderColor, i, arcOpts.borderColor);
3154
+ var bw = custom.borderWidth ? custom.borderWidth : valueAtIndexOrDefault(ds.borderWidth, i, arcOpts.borderWidth);
3155
+
3156
+ return {
3157
+ text: label,
3158
+ fillStyle: fill,
3159
+ strokeStyle: stroke,
3160
+ lineWidth: bw,
3161
+ hidden: isNaN(ds.data[i]) || meta.data[i].hidden,
3162
+
3163
+ // Extra data used for toggling the correct item
3164
+ index: i
3165
+ };
3166
+ });
3167
+ }
3168
+ return [];
3169
+ }
3170
+ },
3171
+
3172
+ onClick: function(e, legendItem) {
3173
+ var index = legendItem.index;
3174
+ var chart = this.chart;
3175
+ var i, ilen, meta;
3176
+
3177
+ for (i = 0, ilen = (chart.data.datasets || []).length; i < ilen; ++i) {
3178
+ meta = chart.getDatasetMeta(i);
3179
+ meta.data[index].hidden = !meta.data[index].hidden;
3180
+ }
3181
+
3182
+ chart.update();
3183
+ }
3184
+ },
3185
+
3186
+ // Need to override these to give a nice default
3187
+ tooltips: {
3188
+ callbacks: {
3189
+ title: function() {
3190
+ return '';
3191
+ },
3192
+ label: function(item, data) {
3193
+ return data.labels[item.index] + ': ' + item.yLabel;
3194
+ }
3195
+ }
3196
+ }
3197
+ });
3198
+
3199
+ module.exports = function(Chart) {
3200
+
3201
+ Chart.controllers.polarArea = Chart.DatasetController.extend({
3202
+
3203
+ dataElementType: elements.Arc,
3204
+
3205
+ linkScales: helpers.noop,
3206
+
3207
+ update: function(reset) {
3208
+ var me = this;
3209
+ var chart = me.chart;
3210
+ var chartArea = chart.chartArea;
3211
+ var meta = me.getMeta();
3212
+ var opts = chart.options;
3213
+ var arcOpts = opts.elements.arc;
3214
+ var minSize = Math.min(chartArea.right - chartArea.left, chartArea.bottom - chartArea.top);
3215
+ chart.outerRadius = Math.max((minSize - arcOpts.borderWidth / 2) / 2, 0);
3216
+ chart.innerRadius = Math.max(opts.cutoutPercentage ? (chart.outerRadius / 100) * (opts.cutoutPercentage) : 1, 0);
3217
+ chart.radiusLength = (chart.outerRadius - chart.innerRadius) / chart.getVisibleDatasetCount();
3218
+
3219
+ me.outerRadius = chart.outerRadius - (chart.radiusLength * me.index);
3220
+ me.innerRadius = me.outerRadius - chart.radiusLength;
3221
+
3222
+ meta.count = me.countVisibleElements();
3223
+
3224
+ helpers.each(meta.data, function(arc, index) {
3225
+ me.updateElement(arc, index, reset);
3226
+ });
3227
+ },
3228
+
3229
+ updateElement: function(arc, index, reset) {
3230
+ var me = this;
3231
+ var chart = me.chart;
3232
+ var dataset = me.getDataset();
3233
+ var opts = chart.options;
3234
+ var animationOpts = opts.animation;
3235
+ var scale = chart.scale;
3236
+ var labels = chart.data.labels;
3237
+
3238
+ var circumference = me.calculateCircumference(dataset.data[index]);
3239
+ var centerX = scale.xCenter;
3240
+ var centerY = scale.yCenter;
3241
+
3242
+ // If there is NaN data before us, we need to calculate the starting angle correctly.
3243
+ // We could be way more efficient here, but its unlikely that the polar area chart will have a lot of data
3244
+ var visibleCount = 0;
3245
+ var meta = me.getMeta();
3246
+ for (var i = 0; i < index; ++i) {
3247
+ if (!isNaN(dataset.data[i]) && !meta.data[i].hidden) {
3248
+ ++visibleCount;
3249
+ }
3250
+ }
3251
+
3252
+ // var negHalfPI = -0.5 * Math.PI;
3253
+ var datasetStartAngle = opts.startAngle;
3254
+ var distance = arc.hidden ? 0 : scale.getDistanceFromCenterForValue(dataset.data[index]);
3255
+ var startAngle = datasetStartAngle + (circumference * visibleCount);
3256
+ var endAngle = startAngle + (arc.hidden ? 0 : circumference);
3257
+
3258
+ var resetRadius = animationOpts.animateScale ? 0 : scale.getDistanceFromCenterForValue(dataset.data[index]);
3259
+
3260
+ helpers.extend(arc, {
3261
+ // Utility
3262
+ _datasetIndex: me.index,
3263
+ _index: index,
3264
+ _scale: scale,
3265
+
3266
+ // Desired view properties
3267
+ _model: {
3268
+ x: centerX,
3269
+ y: centerY,
3270
+ innerRadius: 0,
3271
+ outerRadius: reset ? resetRadius : distance,
3272
+ startAngle: reset && animationOpts.animateRotate ? datasetStartAngle : startAngle,
3273
+ endAngle: reset && animationOpts.animateRotate ? datasetStartAngle : endAngle,
3274
+ label: helpers.valueAtIndexOrDefault(labels, index, labels[index])
3275
+ }
3276
+ });
3277
+
3278
+ // Apply border and fill style
3279
+ me.removeHoverStyle(arc);
3280
+
3281
+ arc.pivot();
3282
+ },
3283
+
3284
+ removeHoverStyle: function(arc) {
3285
+ Chart.DatasetController.prototype.removeHoverStyle.call(this, arc, this.chart.options.elements.arc);
3286
+ },
3287
+
3288
+ countVisibleElements: function() {
3289
+ var dataset = this.getDataset();
3290
+ var meta = this.getMeta();
3291
+ var count = 0;
3292
+
3293
+ helpers.each(meta.data, function(element, index) {
3294
+ if (!isNaN(dataset.data[index]) && !element.hidden) {
3295
+ count++;
3296
+ }
3297
+ });
3298
+
3299
+ return count;
3300
+ },
3301
+
3302
+ calculateCircumference: function(value) {
3303
+ var count = this.getMeta().count;
3304
+ if (count > 0 && !isNaN(value)) {
3305
+ return (2 * Math.PI) / count;
3306
+ }
3307
+ return 0;
3308
+ }
3309
+ });
3310
+ };
3311
+
3312
+ },{"25":25,"40":40,"45":45}],20:[function(require,module,exports){
3313
+ 'use strict';
3314
+
3315
+ var defaults = require(25);
3316
+ var elements = require(40);
3317
+ var helpers = require(45);
3318
+
3319
+ defaults._set('radar', {
3320
+ scale: {
3321
+ type: 'radialLinear'
3322
+ },
3323
+ elements: {
3324
+ line: {
3325
+ tension: 0 // no bezier in radar
3326
+ }
3327
+ }
3328
+ });
3329
+
3330
+ module.exports = function(Chart) {
3331
+
3332
+ Chart.controllers.radar = Chart.DatasetController.extend({
3333
+
3334
+ datasetElementType: elements.Line,
3335
+
3336
+ dataElementType: elements.Point,
3337
+
3338
+ linkScales: helpers.noop,
3339
+
3340
+ update: function(reset) {
3341
+ var me = this;
3342
+ var meta = me.getMeta();
3343
+ var line = meta.dataset;
3344
+ var points = meta.data;
3345
+ var custom = line.custom || {};
3346
+ var dataset = me.getDataset();
3347
+ var lineElementOptions = me.chart.options.elements.line;
3348
+ var scale = me.chart.scale;
3349
+
3350
+ // Compatibility: If the properties are defined with only the old name, use those values
3351
+ if ((dataset.tension !== undefined) && (dataset.lineTension === undefined)) {
3352
+ dataset.lineTension = dataset.tension;
3353
+ }
3354
+
3355
+ helpers.extend(meta.dataset, {
3356
+ // Utility
3357
+ _datasetIndex: me.index,
3358
+ _scale: scale,
3359
+ // Data
3360
+ _children: points,
3361
+ _loop: true,
3362
+ // Model
3363
+ _model: {
3364
+ // Appearance
3365
+ tension: custom.tension ? custom.tension : helpers.valueOrDefault(dataset.lineTension, lineElementOptions.tension),
3366
+ backgroundColor: custom.backgroundColor ? custom.backgroundColor : (dataset.backgroundColor || lineElementOptions.backgroundColor),
3367
+ borderWidth: custom.borderWidth ? custom.borderWidth : (dataset.borderWidth || lineElementOptions.borderWidth),
3368
+ borderColor: custom.borderColor ? custom.borderColor : (dataset.borderColor || lineElementOptions.borderColor),
3369
+ fill: custom.fill ? custom.fill : (dataset.fill !== undefined ? dataset.fill : lineElementOptions.fill),
3370
+ borderCapStyle: custom.borderCapStyle ? custom.borderCapStyle : (dataset.borderCapStyle || lineElementOptions.borderCapStyle),
3371
+ borderDash: custom.borderDash ? custom.borderDash : (dataset.borderDash || lineElementOptions.borderDash),
3372
+ borderDashOffset: custom.borderDashOffset ? custom.borderDashOffset : (dataset.borderDashOffset || lineElementOptions.borderDashOffset),
3373
+ borderJoinStyle: custom.borderJoinStyle ? custom.borderJoinStyle : (dataset.borderJoinStyle || lineElementOptions.borderJoinStyle),
3374
+ }
3375
+ });
3376
+
3377
+ meta.dataset.pivot();
3378
+
3379
+ // Update Points
3380
+ helpers.each(points, function(point, index) {
3381
+ me.updateElement(point, index, reset);
3382
+ }, me);
3383
+
3384
+ // Update bezier control points
3385
+ me.updateBezierControlPoints();
3386
+ },
3387
+ updateElement: function(point, index, reset) {
3388
+ var me = this;
3389
+ var custom = point.custom || {};
3390
+ var dataset = me.getDataset();
3391
+ var scale = me.chart.scale;
3392
+ var pointElementOptions = me.chart.options.elements.point;
3393
+ var pointPosition = scale.getPointPositionForValue(index, dataset.data[index]);
3394
+
3395
+ // Compatibility: If the properties are defined with only the old name, use those values
3396
+ if ((dataset.radius !== undefined) && (dataset.pointRadius === undefined)) {
3397
+ dataset.pointRadius = dataset.radius;
3398
+ }
3399
+ if ((dataset.hitRadius !== undefined) && (dataset.pointHitRadius === undefined)) {
3400
+ dataset.pointHitRadius = dataset.hitRadius;
3401
+ }
3402
+
3403
+ helpers.extend(point, {
3404
+ // Utility
3405
+ _datasetIndex: me.index,
3406
+ _index: index,
3407
+ _scale: scale,
3408
+
3409
+ // Desired view properties
3410
+ _model: {
3411
+ x: reset ? scale.xCenter : pointPosition.x, // value not used in dataset scale, but we want a consistent API between scales
3412
+ y: reset ? scale.yCenter : pointPosition.y,
3413
+
3414
+ // Appearance
3415
+ tension: custom.tension ? custom.tension : helpers.valueOrDefault(dataset.lineTension, me.chart.options.elements.line.tension),
3416
+ radius: custom.radius ? custom.radius : helpers.valueAtIndexOrDefault(dataset.pointRadius, index, pointElementOptions.radius),
3417
+ backgroundColor: custom.backgroundColor ? custom.backgroundColor : helpers.valueAtIndexOrDefault(dataset.pointBackgroundColor, index, pointElementOptions.backgroundColor),
3418
+ borderColor: custom.borderColor ? custom.borderColor : helpers.valueAtIndexOrDefault(dataset.pointBorderColor, index, pointElementOptions.borderColor),
3419
+ borderWidth: custom.borderWidth ? custom.borderWidth : helpers.valueAtIndexOrDefault(dataset.pointBorderWidth, index, pointElementOptions.borderWidth),
3420
+ pointStyle: custom.pointStyle ? custom.pointStyle : helpers.valueAtIndexOrDefault(dataset.pointStyle, index, pointElementOptions.pointStyle),
3421
+
3422
+ // Tooltip
3423
+ hitRadius: custom.hitRadius ? custom.hitRadius : helpers.valueAtIndexOrDefault(dataset.pointHitRadius, index, pointElementOptions.hitRadius)
3424
+ }
3425
+ });
3426
+
3427
+ point._model.skip = custom.skip ? custom.skip : (isNaN(point._model.x) || isNaN(point._model.y));
3428
+ },
3429
+ updateBezierControlPoints: function() {
3430
+ var chartArea = this.chart.chartArea;
3431
+ var meta = this.getMeta();
3432
+
3433
+ helpers.each(meta.data, function(point, index) {
3434
+ var model = point._model;
3435
+ var controlPoints = helpers.splineCurve(
3436
+ helpers.previousItem(meta.data, index, true)._model,
3437
+ model,
3438
+ helpers.nextItem(meta.data, index, true)._model,
3439
+ model.tension
3440
+ );
3441
+
3442
+ // Prevent the bezier going outside of the bounds of the graph
3443
+ model.controlPointPreviousX = Math.max(Math.min(controlPoints.previous.x, chartArea.right), chartArea.left);
3444
+ model.controlPointPreviousY = Math.max(Math.min(controlPoints.previous.y, chartArea.bottom), chartArea.top);
3445
+
3446
+ model.controlPointNextX = Math.max(Math.min(controlPoints.next.x, chartArea.right), chartArea.left);
3447
+ model.controlPointNextY = Math.max(Math.min(controlPoints.next.y, chartArea.bottom), chartArea.top);
3448
+
3449
+ // Now pivot the point for animation
3450
+ point.pivot();
3451
+ });
3452
+ },
3453
+
3454
+ setHoverStyle: function(point) {
3455
+ // Point
3456
+ var dataset = this.chart.data.datasets[point._datasetIndex];
3457
+ var custom = point.custom || {};
3458
+ var index = point._index;
3459
+ var model = point._model;
3460
+
3461
+ model.radius = custom.hoverRadius ? custom.hoverRadius : helpers.valueAtIndexOrDefault(dataset.pointHoverRadius, index, this.chart.options.elements.point.hoverRadius);
3462
+ model.backgroundColor = custom.hoverBackgroundColor ? custom.hoverBackgroundColor : helpers.valueAtIndexOrDefault(dataset.pointHoverBackgroundColor, index, helpers.getHoverColor(model.backgroundColor));
3463
+ model.borderColor = custom.hoverBorderColor ? custom.hoverBorderColor : helpers.valueAtIndexOrDefault(dataset.pointHoverBorderColor, index, helpers.getHoverColor(model.borderColor));
3464
+ model.borderWidth = custom.hoverBorderWidth ? custom.hoverBorderWidth : helpers.valueAtIndexOrDefault(dataset.pointHoverBorderWidth, index, model.borderWidth);
3465
+ },
3466
+
3467
+ removeHoverStyle: function(point) {
3468
+ var dataset = this.chart.data.datasets[point._datasetIndex];
3469
+ var custom = point.custom || {};
3470
+ var index = point._index;
3471
+ var model = point._model;
3472
+ var pointElementOptions = this.chart.options.elements.point;
3473
+
3474
+ model.radius = custom.radius ? custom.radius : helpers.valueAtIndexOrDefault(dataset.pointRadius, index, pointElementOptions.radius);
3475
+ model.backgroundColor = custom.backgroundColor ? custom.backgroundColor : helpers.valueAtIndexOrDefault(dataset.pointBackgroundColor, index, pointElementOptions.backgroundColor);
3476
+ model.borderColor = custom.borderColor ? custom.borderColor : helpers.valueAtIndexOrDefault(dataset.pointBorderColor, index, pointElementOptions.borderColor);
3477
+ model.borderWidth = custom.borderWidth ? custom.borderWidth : helpers.valueAtIndexOrDefault(dataset.pointBorderWidth, index, pointElementOptions.borderWidth);
3478
+ }
3479
+ });
3480
+ };
3481
+
3482
+ },{"25":25,"40":40,"45":45}],21:[function(require,module,exports){
3483
+ 'use strict';
3484
+
3485
+ var defaults = require(25);
3486
+
3487
+ defaults._set('scatter', {
3488
+ hover: {
3489
+ mode: 'single'
3490
+ },
3491
+
3492
+ scales: {
3493
+ xAxes: [{
3494
+ id: 'x-axis-1', // need an ID so datasets can reference the scale
3495
+ type: 'linear', // scatter should not use a category axis
3496
+ position: 'bottom'
3497
+ }],
3498
+ yAxes: [{
3499
+ id: 'y-axis-1',
3500
+ type: 'linear',
3501
+ position: 'left'
3502
+ }]
3503
+ },
3504
+
3505
+ showLines: false,
3506
+
3507
+ tooltips: {
3508
+ callbacks: {
3509
+ title: function() {
3510
+ return ''; // doesn't make sense for scatter since data are formatted as a point
3511
+ },
3512
+ label: function(item) {
3513
+ return '(' + item.xLabel + ', ' + item.yLabel + ')';
3514
+ }
3515
+ }
3516
+ }
3517
+ });
3518
+
3519
+ module.exports = function(Chart) {
3520
+
3521
+ // Scatter charts use line controllers
3522
+ Chart.controllers.scatter = Chart.controllers.line;
3523
+
3524
+ };
3525
+
3526
+ },{"25":25}],22:[function(require,module,exports){
3527
+ /* global window: false */
3528
+ 'use strict';
3529
+
3530
+ var defaults = require(25);
3531
+ var Element = require(26);
3532
+ var helpers = require(45);
3533
+
3534
+ defaults._set('global', {
3535
+ animation: {
3536
+ duration: 1000,
3537
+ easing: 'easeOutQuart',
3538
+ onProgress: helpers.noop,
3539
+ onComplete: helpers.noop
3540
+ }
3541
+ });
3542
+
3543
+ module.exports = function(Chart) {
3544
+
3545
+ Chart.Animation = Element.extend({
3546
+ chart: null, // the animation associated chart instance
3547
+ currentStep: 0, // the current animation step
3548
+ numSteps: 60, // default number of steps
3549
+ easing: '', // the easing to use for this animation
3550
+ render: null, // render function used by the animation service
3551
+
3552
+ onAnimationProgress: null, // user specified callback to fire on each step of the animation
3553
+ onAnimationComplete: null, // user specified callback to fire when the animation finishes
3554
+ });
3555
+
3556
+ Chart.animationService = {
3557
+ frameDuration: 17,
3558
+ animations: [],
3559
+ dropFrames: 0,
3560
+ request: null,
3561
+
3562
+ /**
3563
+ * @param {Chart} chart - The chart to animate.
3564
+ * @param {Chart.Animation} animation - The animation that we will animate.
3565
+ * @param {Number} duration - The animation duration in ms.
3566
+ * @param {Boolean} lazy - if true, the chart is not marked as animating to enable more responsive interactions
3567
+ */
3568
+ addAnimation: function(chart, animation, duration, lazy) {
3569
+ var animations = this.animations;
3570
+ var i, ilen;
3571
+
3572
+ animation.chart = chart;
3573
+
3574
+ if (!lazy) {
3575
+ chart.animating = true;
3576
+ }
3577
+
3578
+ for (i = 0, ilen = animations.length; i < ilen; ++i) {
3579
+ if (animations[i].chart === chart) {
3580
+ animations[i] = animation;
3581
+ return;
3582
+ }
3583
+ }
3584
+
3585
+ animations.push(animation);
3586
+
3587
+ // If there are no animations queued, manually kickstart a digest, for lack of a better word
3588
+ if (animations.length === 1) {
3589
+ this.requestAnimationFrame();
3590
+ }
3591
+ },
3592
+
3593
+ cancelAnimation: function(chart) {
3594
+ var index = helpers.findIndex(this.animations, function(animation) {
3595
+ return animation.chart === chart;
3596
+ });
3597
+
3598
+ if (index !== -1) {
3599
+ this.animations.splice(index, 1);
3600
+ chart.animating = false;
3601
+ }
3602
+ },
3603
+
3604
+ requestAnimationFrame: function() {
3605
+ var me = this;
3606
+ if (me.request === null) {
3607
+ // Skip animation frame requests until the active one is executed.
3608
+ // This can happen when processing mouse events, e.g. 'mousemove'
3609
+ // and 'mouseout' events will trigger multiple renders.
3610
+ me.request = helpers.requestAnimFrame.call(window, function() {
3611
+ me.request = null;
3612
+ me.startDigest();
3613
+ });
3614
+ }
3615
+ },
3616
+
3617
+ /**
3618
+ * @private
3619
+ */
3620
+ startDigest: function() {
3621
+ var me = this;
3622
+ var startTime = Date.now();
3623
+ var framesToDrop = 0;
3624
+
3625
+ if (me.dropFrames > 1) {
3626
+ framesToDrop = Math.floor(me.dropFrames);
3627
+ me.dropFrames = me.dropFrames % 1;
3628
+ }
3629
+
3630
+ me.advance(1 + framesToDrop);
3631
+
3632
+ var endTime = Date.now();
3633
+
3634
+ me.dropFrames += (endTime - startTime) / me.frameDuration;
3635
+
3636
+ // Do we have more stuff to animate?
3637
+ if (me.animations.length > 0) {
3638
+ me.requestAnimationFrame();
3639
+ }
3640
+ },
3641
+
3642
+ /**
3643
+ * @private
3644
+ */
3645
+ advance: function(count) {
3646
+ var animations = this.animations;
3647
+ var animation, chart;
3648
+ var i = 0;
3649
+
3650
+ while (i < animations.length) {
3651
+ animation = animations[i];
3652
+ chart = animation.chart;
3653
+
3654
+ animation.currentStep = (animation.currentStep || 0) + count;
3655
+ animation.currentStep = Math.min(animation.currentStep, animation.numSteps);
3656
+
3657
+ helpers.callback(animation.render, [chart, animation], chart);
3658
+ helpers.callback(animation.onAnimationProgress, [animation], chart);
3659
+
3660
+ if (animation.currentStep >= animation.numSteps) {
3661
+ helpers.callback(animation.onAnimationComplete, [animation], chart);
3662
+ chart.animating = false;
3663
+ animations.splice(i, 1);
3664
+ } else {
3665
+ ++i;
3666
+ }
3667
+ }
3668
+ }
3669
+ };
3670
+
3671
+ /**
3672
+ * Provided for backward compatibility, use Chart.Animation instead
3673
+ * @prop Chart.Animation#animationObject
3674
+ * @deprecated since version 2.6.0
3675
+ * @todo remove at version 3
3676
+ */
3677
+ Object.defineProperty(Chart.Animation.prototype, 'animationObject', {
3678
+ get: function() {
3679
+ return this;
3680
+ }
3681
+ });
3682
+
3683
+ /**
3684
+ * Provided for backward compatibility, use Chart.Animation#chart instead
3685
+ * @prop Chart.Animation#chartInstance
3686
+ * @deprecated since version 2.6.0
3687
+ * @todo remove at version 3
3688
+ */
3689
+ Object.defineProperty(Chart.Animation.prototype, 'chartInstance', {
3690
+ get: function() {
3691
+ return this.chart;
3692
+ },
3693
+ set: function(value) {
3694
+ this.chart = value;
3695
+ }
3696
+ });
3697
+
3698
+ };
3699
+
3700
+ },{"25":25,"26":26,"45":45}],23:[function(require,module,exports){
3701
+ 'use strict';
3702
+
3703
+ var defaults = require(25);
3704
+ var helpers = require(45);
3705
+ var Interaction = require(28);
3706
+ var platform = require(48);
3707
+
3708
+ module.exports = function(Chart) {
3709
+ var plugins = Chart.plugins;
3710
+
3711
+ // Create a dictionary of chart types, to allow for extension of existing types
3712
+ Chart.types = {};
3713
+
3714
+ // Store a reference to each instance - allowing us to globally resize chart instances on window resize.
3715
+ // Destroy method on the chart will remove the instance of the chart from this reference.
3716
+ Chart.instances = {};
3717
+
3718
+ // Controllers available for dataset visualization eg. bar, line, slice, etc.
3719
+ Chart.controllers = {};
3720
+
3721
+ /**
3722
+ * Initializes the given config with global and chart default values.
3723
+ */
3724
+ function initConfig(config) {
3725
+ config = config || {};
3726
+
3727
+ // Do NOT use configMerge() for the data object because this method merges arrays
3728
+ // and so would change references to labels and datasets, preventing data updates.
3729
+ var data = config.data = config.data || {};
3730
+ data.datasets = data.datasets || [];
3731
+ data.labels = data.labels || [];
3732
+
3733
+ config.options = helpers.configMerge(
3734
+ defaults.global,
3735
+ defaults[config.type],
3736
+ config.options || {});
3737
+
3738
+ return config;
3739
+ }
3740
+
3741
+ /**
3742
+ * Updates the config of the chart
3743
+ * @param chart {Chart} chart to update the options for
3744
+ */
3745
+ function updateConfig(chart) {
3746
+ var newOptions = chart.options;
3747
+
3748
+ // Update Scale(s) with options
3749
+ if (newOptions.scale) {
3750
+ chart.scale.options = newOptions.scale;
3751
+ } else if (newOptions.scales) {
3752
+ newOptions.scales.xAxes.concat(newOptions.scales.yAxes).forEach(function(scaleOptions) {
3753
+ chart.scales[scaleOptions.id].options = scaleOptions;
3754
+ });
3755
+ }
3756
+
3757
+ // Tooltip
3758
+ chart.tooltip._options = newOptions.tooltips;
3759
+ }
3760
+
3761
+ function positionIsHorizontal(position) {
3762
+ return position === 'top' || position === 'bottom';
3763
+ }
3764
+
3765
+ helpers.extend(Chart.prototype, /** @lends Chart */ {
3766
+ /**
3767
+ * @private
3768
+ */
3769
+ construct: function(item, config) {
3770
+ var me = this;
3771
+
3772
+ config = initConfig(config);
3773
+
3774
+ var context = platform.acquireContext(item, config);
3775
+ var canvas = context && context.canvas;
3776
+ var height = canvas && canvas.height;
3777
+ var width = canvas && canvas.width;
3778
+
3779
+ me.id = helpers.uid();
3780
+ me.ctx = context;
3781
+ me.canvas = canvas;
3782
+ me.config = config;
3783
+ me.width = width;
3784
+ me.height = height;
3785
+ me.aspectRatio = height ? width / height : null;
3786
+ me.options = config.options;
3787
+ me._bufferedRender = false;
3788
+
3789
+ /**
3790
+ * Provided for backward compatibility, Chart and Chart.Controller have been merged,
3791
+ * the "instance" still need to be defined since it might be called from plugins.
3792
+ * @prop Chart#chart
3793
+ * @deprecated since version 2.6.0
3794
+ * @todo remove at version 3
3795
+ * @private
3796
+ */
3797
+ me.chart = me;
3798
+ me.controller = me; // chart.chart.controller #inception
3799
+
3800
+ // Add the chart instance to the global namespace
3801
+ Chart.instances[me.id] = me;
3802
+
3803
+ // Define alias to the config data: `chart.data === chart.config.data`
3804
+ Object.defineProperty(me, 'data', {
3805
+ get: function() {
3806
+ return me.config.data;
3807
+ },
3808
+ set: function(value) {
3809
+ me.config.data = value;
3810
+ }
3811
+ });
3812
+
3813
+ if (!context || !canvas) {
3814
+ // The given item is not a compatible context2d element, let's return before finalizing
3815
+ // the chart initialization but after setting basic chart / controller properties that
3816
+ // can help to figure out that the chart is not valid (e.g chart.canvas !== null);
3817
+ // https://github.com/chartjs/Chart.js/issues/2807
3818
+ console.error("Failed to create chart: can't acquire context from the given item");
3819
+ return;
3820
+ }
3821
+
3822
+ me.initialize();
3823
+ me.update();
3824
+ },
3825
+
3826
+ /**
3827
+ * @private
3828
+ */
3829
+ initialize: function() {
3830
+ var me = this;
3831
+
3832
+ // Before init plugin notification
3833
+ plugins.notify(me, 'beforeInit');
3834
+
3835
+ helpers.retinaScale(me, me.options.devicePixelRatio);
3836
+
3837
+ me.bindEvents();
3838
+
3839
+ if (me.options.responsive) {
3840
+ // Initial resize before chart draws (must be silent to preserve initial animations).
3841
+ me.resize(true);
3842
+ }
3843
+
3844
+ // Make sure scales have IDs and are built before we build any controllers.
3845
+ me.ensureScalesHaveIDs();
3846
+ me.buildScales();
3847
+ me.initToolTip();
3848
+
3849
+ // After init plugin notification
3850
+ plugins.notify(me, 'afterInit');
3851
+
3852
+ return me;
3853
+ },
3854
+
3855
+ clear: function() {
3856
+ helpers.canvas.clear(this);
3857
+ return this;
3858
+ },
3859
+
3860
+ stop: function() {
3861
+ // Stops any current animation loop occurring
3862
+ Chart.animationService.cancelAnimation(this);
3863
+ return this;
3864
+ },
3865
+
3866
+ resize: function(silent) {
3867
+ var me = this;
3868
+ var options = me.options;
3869
+ var canvas = me.canvas;
3870
+ var aspectRatio = (options.maintainAspectRatio && me.aspectRatio) || null;
3871
+
3872
+ // the canvas render width and height will be casted to integers so make sure that
3873
+ // the canvas display style uses the same integer values to avoid blurring effect.
3874
+
3875
+ // Set to 0 instead of canvas.size because the size defaults to 300x150 if the element is collased
3876
+ var newWidth = Math.max(0, Math.floor(helpers.getMaximumWidth(canvas)));
3877
+ var newHeight = Math.max(0, Math.floor(aspectRatio ? newWidth / aspectRatio : helpers.getMaximumHeight(canvas)));
3878
+
3879
+ if (me.width === newWidth && me.height === newHeight) {
3880
+ return;
3881
+ }
3882
+
3883
+ canvas.width = me.width = newWidth;
3884
+ canvas.height = me.height = newHeight;
3885
+ canvas.style.width = newWidth + 'px';
3886
+ canvas.style.height = newHeight + 'px';
3887
+
3888
+ helpers.retinaScale(me, options.devicePixelRatio);
3889
+
3890
+ if (!silent) {
3891
+ // Notify any plugins about the resize
3892
+ var newSize = {width: newWidth, height: newHeight};
3893
+ plugins.notify(me, 'resize', [newSize]);
3894
+
3895
+ // Notify of resize
3896
+ if (me.options.onResize) {
3897
+ me.options.onResize(me, newSize);
3898
+ }
3899
+
3900
+ me.stop();
3901
+ me.update(me.options.responsiveAnimationDuration);
3902
+ }
3903
+ },
3904
+
3905
+ ensureScalesHaveIDs: function() {
3906
+ var options = this.options;
3907
+ var scalesOptions = options.scales || {};
3908
+ var scaleOptions = options.scale;
3909
+
3910
+ helpers.each(scalesOptions.xAxes, function(xAxisOptions, index) {
3911
+ xAxisOptions.id = xAxisOptions.id || ('x-axis-' + index);
3912
+ });
3913
+
3914
+ helpers.each(scalesOptions.yAxes, function(yAxisOptions, index) {
3915
+ yAxisOptions.id = yAxisOptions.id || ('y-axis-' + index);
3916
+ });
3917
+
3918
+ if (scaleOptions) {
3919
+ scaleOptions.id = scaleOptions.id || 'scale';
3920
+ }
3921
+ },
3922
+
3923
+ /**
3924
+ * Builds a map of scale ID to scale object for future lookup.
3925
+ */
3926
+ buildScales: function() {
3927
+ var me = this;
3928
+ var options = me.options;
3929
+ var scales = me.scales = {};
3930
+ var items = [];
3931
+
3932
+ if (options.scales) {
3933
+ items = items.concat(
3934
+ (options.scales.xAxes || []).map(function(xAxisOptions) {
3935
+ return {options: xAxisOptions, dtype: 'category', dposition: 'bottom'};
3936
+ }),
3937
+ (options.scales.yAxes || []).map(function(yAxisOptions) {
3938
+ return {options: yAxisOptions, dtype: 'linear', dposition: 'left'};
3939
+ })
3940
+ );
3941
+ }
3942
+
3943
+ if (options.scale) {
3944
+ items.push({
3945
+ options: options.scale,
3946
+ dtype: 'radialLinear',
3947
+ isDefault: true,
3948
+ dposition: 'chartArea'
3949
+ });
3950
+ }
3951
+
3952
+ helpers.each(items, function(item) {
3953
+ var scaleOptions = item.options;
3954
+ var scaleType = helpers.valueOrDefault(scaleOptions.type, item.dtype);
3955
+ var scaleClass = Chart.scaleService.getScaleConstructor(scaleType);
3956
+ if (!scaleClass) {
3957
+ return;
3958
+ }
3959
+
3960
+ if (positionIsHorizontal(scaleOptions.position) !== positionIsHorizontal(item.dposition)) {
3961
+ scaleOptions.position = item.dposition;
3962
+ }
3963
+
3964
+ var scale = new scaleClass({
3965
+ id: scaleOptions.id,
3966
+ options: scaleOptions,
3967
+ ctx: me.ctx,
3968
+ chart: me
3969
+ });
3970
+
3971
+ scales[scale.id] = scale;
3972
+ scale.mergeTicksOptions();
3973
+
3974
+ // TODO(SB): I think we should be able to remove this custom case (options.scale)
3975
+ // and consider it as a regular scale part of the "scales"" map only! This would
3976
+ // make the logic easier and remove some useless? custom code.
3977
+ if (item.isDefault) {
3978
+ me.scale = scale;
3979
+ }
3980
+ });
3981
+
3982
+ Chart.scaleService.addScalesToLayout(this);
3983
+ },
3984
+
3985
+ buildOrUpdateControllers: function() {
3986
+ var me = this;
3987
+ var types = [];
3988
+ var newControllers = [];
3989
+
3990
+ helpers.each(me.data.datasets, function(dataset, datasetIndex) {
3991
+ var meta = me.getDatasetMeta(datasetIndex);
3992
+ var type = dataset.type || me.config.type;
3993
+
3994
+ if (meta.type && meta.type !== type) {
3995
+ me.destroyDatasetMeta(datasetIndex);
3996
+ meta = me.getDatasetMeta(datasetIndex);
3997
+ }
3998
+ meta.type = type;
3999
+
4000
+ types.push(meta.type);
4001
+
4002
+ if (meta.controller) {
4003
+ meta.controller.updateIndex(datasetIndex);
4004
+ } else {
4005
+ var ControllerClass = Chart.controllers[meta.type];
4006
+ if (ControllerClass === undefined) {
4007
+ throw new Error('"' + meta.type + '" is not a chart type.');
4008
+ }
4009
+
4010
+ meta.controller = new ControllerClass(me, datasetIndex);
4011
+ newControllers.push(meta.controller);
4012
+ }
4013
+ }, me);
4014
+
4015
+ return newControllers;
4016
+ },
4017
+
4018
+ /**
4019
+ * Reset the elements of all datasets
4020
+ * @private
4021
+ */
4022
+ resetElements: function() {
4023
+ var me = this;
4024
+ helpers.each(me.data.datasets, function(dataset, datasetIndex) {
4025
+ me.getDatasetMeta(datasetIndex).controller.reset();
4026
+ }, me);
4027
+ },
4028
+
4029
+ /**
4030
+ * Resets the chart back to it's state before the initial animation
4031
+ */
4032
+ reset: function() {
4033
+ this.resetElements();
4034
+ this.tooltip.initialize();
4035
+ },
4036
+
4037
+ update: function(config) {
4038
+ var me = this;
4039
+
4040
+ if (!config || typeof config !== 'object') {
4041
+ // backwards compatibility
4042
+ config = {
4043
+ duration: config,
4044
+ lazy: arguments[1]
4045
+ };
4046
+ }
4047
+
4048
+ updateConfig(me);
4049
+
4050
+ if (plugins.notify(me, 'beforeUpdate') === false) {
4051
+ return;
4052
+ }
4053
+
4054
+ // In case the entire data object changed
4055
+ me.tooltip._data = me.data;
4056
+
4057
+ // Make sure dataset controllers are updated and new controllers are reset
4058
+ var newControllers = me.buildOrUpdateControllers();
4059
+
4060
+ // Make sure all dataset controllers have correct meta data counts
4061
+ helpers.each(me.data.datasets, function(dataset, datasetIndex) {
4062
+ me.getDatasetMeta(datasetIndex).controller.buildOrUpdateElements();
4063
+ }, me);
4064
+
4065
+ me.updateLayout();
4066
+
4067
+ // Can only reset the new controllers after the scales have been updated
4068
+ helpers.each(newControllers, function(controller) {
4069
+ controller.reset();
4070
+ });
4071
+
4072
+ me.updateDatasets();
4073
+
4074
+ // Need to reset tooltip in case it is displayed with elements that are removed
4075
+ // after update.
4076
+ me.tooltip.initialize();
4077
+
4078
+ // Last active contains items that were previously in the tooltip.
4079
+ // When we reset the tooltip, we need to clear it
4080
+ me.lastActive = [];
4081
+
4082
+ // Do this before render so that any plugins that need final scale updates can use it
4083
+ plugins.notify(me, 'afterUpdate');
4084
+
4085
+ if (me._bufferedRender) {
4086
+ me._bufferedRequest = {
4087
+ duration: config.duration,
4088
+ easing: config.easing,
4089
+ lazy: config.lazy
4090
+ };
4091
+ } else {
4092
+ me.render(config);
4093
+ }
4094
+ },
4095
+
4096
+ /**
4097
+ * Updates the chart layout unless a plugin returns `false` to the `beforeLayout`
4098
+ * hook, in which case, plugins will not be called on `afterLayout`.
4099
+ * @private
4100
+ */
4101
+ updateLayout: function() {
4102
+ var me = this;
4103
+
4104
+ if (plugins.notify(me, 'beforeLayout') === false) {
4105
+ return;
4106
+ }
4107
+
4108
+ Chart.layoutService.update(this, this.width, this.height);
4109
+
4110
+ /**
4111
+ * Provided for backward compatibility, use `afterLayout` instead.
4112
+ * @method IPlugin#afterScaleUpdate
4113
+ * @deprecated since version 2.5.0
4114
+ * @todo remove at version 3
4115
+ * @private
4116
+ */
4117
+ plugins.notify(me, 'afterScaleUpdate');
4118
+ plugins.notify(me, 'afterLayout');
4119
+ },
4120
+
4121
+ /**
4122
+ * Updates all datasets unless a plugin returns `false` to the `beforeDatasetsUpdate`
4123
+ * hook, in which case, plugins will not be called on `afterDatasetsUpdate`.
4124
+ * @private
4125
+ */
4126
+ updateDatasets: function() {
4127
+ var me = this;
4128
+
4129
+ if (plugins.notify(me, 'beforeDatasetsUpdate') === false) {
4130
+ return;
4131
+ }
4132
+
4133
+ for (var i = 0, ilen = me.data.datasets.length; i < ilen; ++i) {
4134
+ me.updateDataset(i);
4135
+ }
4136
+
4137
+ plugins.notify(me, 'afterDatasetsUpdate');
4138
+ },
4139
+
4140
+ /**
4141
+ * Updates dataset at index unless a plugin returns `false` to the `beforeDatasetUpdate`
4142
+ * hook, in which case, plugins will not be called on `afterDatasetUpdate`.
4143
+ * @private
4144
+ */
4145
+ updateDataset: function(index) {
4146
+ var me = this;
4147
+ var meta = me.getDatasetMeta(index);
4148
+ var args = {
4149
+ meta: meta,
4150
+ index: index
4151
+ };
4152
+
4153
+ if (plugins.notify(me, 'beforeDatasetUpdate', [args]) === false) {
4154
+ return;
4155
+ }
4156
+
4157
+ meta.controller.update();
4158
+
4159
+ plugins.notify(me, 'afterDatasetUpdate', [args]);
4160
+ },
4161
+
4162
+ render: function(config) {
4163
+ var me = this;
4164
+
4165
+ if (!config || typeof config !== 'object') {
4166
+ // backwards compatibility
4167
+ config = {
4168
+ duration: config,
4169
+ lazy: arguments[1]
4170
+ };
4171
+ }
4172
+
4173
+ var duration = config.duration;
4174
+ var lazy = config.lazy;
4175
+
4176
+ if (plugins.notify(me, 'beforeRender') === false) {
4177
+ return;
4178
+ }
4179
+
4180
+ var animationOptions = me.options.animation;
4181
+ var onComplete = function(animation) {
4182
+ plugins.notify(me, 'afterRender');
4183
+ helpers.callback(animationOptions && animationOptions.onComplete, [animation], me);
4184
+ };
4185
+
4186
+ if (animationOptions && ((typeof duration !== 'undefined' && duration !== 0) || (typeof duration === 'undefined' && animationOptions.duration !== 0))) {
4187
+ var animation = new Chart.Animation({
4188
+ numSteps: (duration || animationOptions.duration) / 16.66, // 60 fps
4189
+ easing: config.easing || animationOptions.easing,
4190
+
4191
+ render: function(chart, animationObject) {
4192
+ var easingFunction = helpers.easing.effects[animationObject.easing];
4193
+ var currentStep = animationObject.currentStep;
4194
+ var stepDecimal = currentStep / animationObject.numSteps;
4195
+
4196
+ chart.draw(easingFunction(stepDecimal), stepDecimal, currentStep);
4197
+ },
4198
+
4199
+ onAnimationProgress: animationOptions.onProgress,
4200
+ onAnimationComplete: onComplete
4201
+ });
4202
+
4203
+ Chart.animationService.addAnimation(me, animation, duration, lazy);
4204
+ } else {
4205
+ me.draw();
4206
+
4207
+ // See https://github.com/chartjs/Chart.js/issues/3781
4208
+ onComplete(new Chart.Animation({numSteps: 0, chart: me}));
4209
+ }
4210
+
4211
+ return me;
4212
+ },
4213
+
4214
+ draw: function(easingValue) {
4215
+ var me = this;
4216
+
4217
+ me.clear();
4218
+
4219
+ if (helpers.isNullOrUndef(easingValue)) {
4220
+ easingValue = 1;
4221
+ }
4222
+
4223
+ me.transition(easingValue);
4224
+
4225
+ if (plugins.notify(me, 'beforeDraw', [easingValue]) === false) {
4226
+ return;
4227
+ }
4228
+
4229
+ // Draw all the scales
4230
+ helpers.each(me.boxes, function(box) {
4231
+ box.draw(me.chartArea);
4232
+ }, me);
4233
+
4234
+ if (me.scale) {
4235
+ me.scale.draw();
4236
+ }
4237
+
4238
+ me.drawDatasets(easingValue);
4239
+ me._drawTooltip(easingValue);
4240
+
4241
+ plugins.notify(me, 'afterDraw', [easingValue]);
4242
+ },
4243
+
4244
+ /**
4245
+ * @private
4246
+ */
4247
+ transition: function(easingValue) {
4248
+ var me = this;
4249
+
4250
+ for (var i = 0, ilen = (me.data.datasets || []).length; i < ilen; ++i) {
4251
+ if (me.isDatasetVisible(i)) {
4252
+ me.getDatasetMeta(i).controller.transition(easingValue);
4253
+ }
4254
+ }
4255
+
4256
+ me.tooltip.transition(easingValue);
4257
+ },
4258
+
4259
+ /**
4260
+ * Draws all datasets unless a plugin returns `false` to the `beforeDatasetsDraw`
4261
+ * hook, in which case, plugins will not be called on `afterDatasetsDraw`.
4262
+ * @private
4263
+ */
4264
+ drawDatasets: function(easingValue) {
4265
+ var me = this;
4266
+
4267
+ if (plugins.notify(me, 'beforeDatasetsDraw', [easingValue]) === false) {
4268
+ return;
4269
+ }
4270
+
4271
+ // Draw datasets reversed to support proper line stacking
4272
+ for (var i = (me.data.datasets || []).length - 1; i >= 0; --i) {
4273
+ if (me.isDatasetVisible(i)) {
4274
+ me.drawDataset(i, easingValue);
4275
+ }
4276
+ }
4277
+
4278
+ plugins.notify(me, 'afterDatasetsDraw', [easingValue]);
4279
+ },
4280
+
4281
+ /**
4282
+ * Draws dataset at index unless a plugin returns `false` to the `beforeDatasetDraw`
4283
+ * hook, in which case, plugins will not be called on `afterDatasetDraw`.
4284
+ * @private
4285
+ */
4286
+ drawDataset: function(index, easingValue) {
4287
+ var me = this;
4288
+ var meta = me.getDatasetMeta(index);
4289
+ var args = {
4290
+ meta: meta,
4291
+ index: index,
4292
+ easingValue: easingValue
4293
+ };
4294
+
4295
+ if (plugins.notify(me, 'beforeDatasetDraw', [args]) === false) {
4296
+ return;
4297
+ }
4298
+
4299
+ meta.controller.draw(easingValue);
4300
+
4301
+ plugins.notify(me, 'afterDatasetDraw', [args]);
4302
+ },
4303
+
4304
+ /**
4305
+ * Draws tooltip unless a plugin returns `false` to the `beforeTooltipDraw`
4306
+ * hook, in which case, plugins will not be called on `afterTooltipDraw`.
4307
+ * @private
4308
+ */
4309
+ _drawTooltip: function(easingValue) {
4310
+ var me = this;
4311
+ var tooltip = me.tooltip;
4312
+ var args = {
4313
+ tooltip: tooltip,
4314
+ easingValue: easingValue
4315
+ };
4316
+
4317
+ if (plugins.notify(me, 'beforeTooltipDraw', [args]) === false) {
4318
+ return;
4319
+ }
4320
+
4321
+ tooltip.draw();
4322
+
4323
+ plugins.notify(me, 'afterTooltipDraw', [args]);
4324
+ },
4325
+
4326
+ // Get the single element that was clicked on
4327
+ // @return : An object containing the dataset index and element index of the matching element. Also contains the rectangle that was draw
4328
+ getElementAtEvent: function(e) {
4329
+ return Interaction.modes.single(this, e);
4330
+ },
4331
+
4332
+ getElementsAtEvent: function(e) {
4333
+ return Interaction.modes.label(this, e, {intersect: true});
4334
+ },
4335
+
4336
+ getElementsAtXAxis: function(e) {
4337
+ return Interaction.modes['x-axis'](this, e, {intersect: true});
4338
+ },
4339
+
4340
+ getElementsAtEventForMode: function(e, mode, options) {
4341
+ var method = Interaction.modes[mode];
4342
+ if (typeof method === 'function') {
4343
+ return method(this, e, options);
4344
+ }
4345
+
4346
+ return [];
4347
+ },
4348
+
4349
+ getDatasetAtEvent: function(e) {
4350
+ return Interaction.modes.dataset(this, e, {intersect: true});
4351
+ },
4352
+
4353
+ getDatasetMeta: function(datasetIndex) {
4354
+ var me = this;
4355
+ var dataset = me.data.datasets[datasetIndex];
4356
+ if (!dataset._meta) {
4357
+ dataset._meta = {};
4358
+ }
4359
+
4360
+ var meta = dataset._meta[me.id];
4361
+ if (!meta) {
4362
+ meta = dataset._meta[me.id] = {
4363
+ type: null,
4364
+ data: [],
4365
+ dataset: null,
4366
+ controller: null,
4367
+ hidden: null, // See isDatasetVisible() comment
4368
+ xAxisID: null,
4369
+ yAxisID: null
4370
+ };
4371
+ }
4372
+
4373
+ return meta;
4374
+ },
4375
+
4376
+ getVisibleDatasetCount: function() {
4377
+ var count = 0;
4378
+ for (var i = 0, ilen = this.data.datasets.length; i < ilen; ++i) {
4379
+ if (this.isDatasetVisible(i)) {
4380
+ count++;
4381
+ }
4382
+ }
4383
+ return count;
4384
+ },
4385
+
4386
+ isDatasetVisible: function(datasetIndex) {
4387
+ var meta = this.getDatasetMeta(datasetIndex);
4388
+
4389
+ // meta.hidden is a per chart dataset hidden flag override with 3 states: if true or false,
4390
+ // the dataset.hidden value is ignored, else if null, the dataset hidden state is returned.
4391
+ return typeof meta.hidden === 'boolean' ? !meta.hidden : !this.data.datasets[datasetIndex].hidden;
4392
+ },
4393
+
4394
+ generateLegend: function() {
4395
+ return this.options.legendCallback(this);
4396
+ },
4397
+
4398
+ /**
4399
+ * @private
4400
+ */
4401
+ destroyDatasetMeta: function(datasetIndex) {
4402
+ var id = this.id;
4403
+ var dataset = this.data.datasets[datasetIndex];
4404
+ var meta = dataset._meta && dataset._meta[id];
4405
+
4406
+ if (meta) {
4407
+ meta.controller.destroy();
4408
+ delete dataset._meta[id];
4409
+ }
4410
+ },
4411
+
4412
+ destroy: function() {
4413
+ var me = this;
4414
+ var canvas = me.canvas;
4415
+ var i, ilen;
4416
+
4417
+ me.stop();
4418
+
4419
+ // dataset controllers need to cleanup associated data
4420
+ for (i = 0, ilen = me.data.datasets.length; i < ilen; ++i) {
4421
+ me.destroyDatasetMeta(i);
4422
+ }
4423
+
4424
+ if (canvas) {
4425
+ me.unbindEvents();
4426
+ helpers.canvas.clear(me);
4427
+ platform.releaseContext(me.ctx);
4428
+ me.canvas = null;
4429
+ me.ctx = null;
4430
+ }
4431
+
4432
+ plugins.notify(me, 'destroy');
4433
+
4434
+ delete Chart.instances[me.id];
4435
+ },
4436
+
4437
+ toBase64Image: function() {
4438
+ return this.canvas.toDataURL.apply(this.canvas, arguments);
4439
+ },
4440
+
4441
+ initToolTip: function() {
4442
+ var me = this;
4443
+ me.tooltip = new Chart.Tooltip({
4444
+ _chart: me,
4445
+ _chartInstance: me, // deprecated, backward compatibility
4446
+ _data: me.data,
4447
+ _options: me.options.tooltips
4448
+ }, me);
4449
+ },
4450
+
4451
+ /**
4452
+ * @private
4453
+ */
4454
+ bindEvents: function() {
4455
+ var me = this;
4456
+ var listeners = me._listeners = {};
4457
+ var listener = function() {
4458
+ me.eventHandler.apply(me, arguments);
4459
+ };
4460
+
4461
+ helpers.each(me.options.events, function(type) {
4462
+ platform.addEventListener(me, type, listener);
4463
+ listeners[type] = listener;
4464
+ });
4465
+
4466
+ // Elements used to detect size change should not be injected for non responsive charts.
4467
+ // See https://github.com/chartjs/Chart.js/issues/2210
4468
+ if (me.options.responsive) {
4469
+ listener = function() {
4470
+ me.resize();
4471
+ };
4472
+
4473
+ platform.addEventListener(me, 'resize', listener);
4474
+ listeners.resize = listener;
4475
+ }
4476
+ },
4477
+
4478
+ /**
4479
+ * @private
4480
+ */
4481
+ unbindEvents: function() {
4482
+ var me = this;
4483
+ var listeners = me._listeners;
4484
+ if (!listeners) {
4485
+ return;
4486
+ }
4487
+
4488
+ delete me._listeners;
4489
+ helpers.each(listeners, function(listener, type) {
4490
+ platform.removeEventListener(me, type, listener);
4491
+ });
4492
+ },
4493
+
4494
+ updateHoverStyle: function(elements, mode, enabled) {
4495
+ var method = enabled ? 'setHoverStyle' : 'removeHoverStyle';
4496
+ var element, i, ilen;
4497
+
4498
+ for (i = 0, ilen = elements.length; i < ilen; ++i) {
4499
+ element = elements[i];
4500
+ if (element) {
4501
+ this.getDatasetMeta(element._datasetIndex).controller[method](element);
4502
+ }
4503
+ }
4504
+ },
4505
+
4506
+ /**
4507
+ * @private
4508
+ */
4509
+ eventHandler: function(e) {
4510
+ var me = this;
4511
+ var tooltip = me.tooltip;
4512
+
4513
+ if (plugins.notify(me, 'beforeEvent', [e]) === false) {
4514
+ return;
4515
+ }
4516
+
4517
+ // Buffer any update calls so that renders do not occur
4518
+ me._bufferedRender = true;
4519
+ me._bufferedRequest = null;
4520
+
4521
+ var changed = me.handleEvent(e);
4522
+ changed |= tooltip && tooltip.handleEvent(e);
4523
+
4524
+ plugins.notify(me, 'afterEvent', [e]);
4525
+
4526
+ var bufferedRequest = me._bufferedRequest;
4527
+ if (bufferedRequest) {
4528
+ // If we have an update that was triggered, we need to do a normal render
4529
+ me.render(bufferedRequest);
4530
+ } else if (changed && !me.animating) {
4531
+ // If entering, leaving, or changing elements, animate the change via pivot
4532
+ me.stop();
4533
+
4534
+ // We only need to render at this point. Updating will cause scales to be
4535
+ // recomputed generating flicker & using more memory than necessary.
4536
+ me.render(me.options.hover.animationDuration, true);
4537
+ }
4538
+
4539
+ me._bufferedRender = false;
4540
+ me._bufferedRequest = null;
4541
+
4542
+ return me;
4543
+ },
4544
+
4545
+ /**
4546
+ * Handle an event
4547
+ * @private
4548
+ * @param {IEvent} event the event to handle
4549
+ * @return {Boolean} true if the chart needs to re-render
4550
+ */
4551
+ handleEvent: function(e) {
4552
+ var me = this;
4553
+ var options = me.options || {};
4554
+ var hoverOptions = options.hover;
4555
+ var changed = false;
4556
+
4557
+ me.lastActive = me.lastActive || [];
4558
+
4559
+ // Find Active Elements for hover and tooltips
4560
+ if (e.type === 'mouseout') {
4561
+ me.active = [];
4562
+ } else {
4563
+ me.active = me.getElementsAtEventForMode(e, hoverOptions.mode, hoverOptions);
4564
+ }
4565
+
4566
+ // Invoke onHover hook
4567
+ // Need to call with native event here to not break backwards compatibility
4568
+ helpers.callback(options.onHover || options.hover.onHover, [e.native, me.active], me);
4569
+
4570
+ if (e.type === 'mouseup' || e.type === 'click') {
4571
+ if (options.onClick) {
4572
+ // Use e.native here for backwards compatibility
4573
+ options.onClick.call(me, e.native, me.active);
4574
+ }
4575
+ }
4576
+
4577
+ // Remove styling for last active (even if it may still be active)
4578
+ if (me.lastActive.length) {
4579
+ me.updateHoverStyle(me.lastActive, hoverOptions.mode, false);
4580
+ }
4581
+
4582
+ // Built in hover styling
4583
+ if (me.active.length && hoverOptions.mode) {
4584
+ me.updateHoverStyle(me.active, hoverOptions.mode, true);
4585
+ }
4586
+
4587
+ changed = !helpers.arrayEquals(me.active, me.lastActive);
4588
+
4589
+ // Remember Last Actives
4590
+ me.lastActive = me.active;
4591
+
4592
+ return changed;
4593
+ }
4594
+ });
4595
+
4596
+ /**
4597
+ * Provided for backward compatibility, use Chart instead.
4598
+ * @class Chart.Controller
4599
+ * @deprecated since version 2.6.0
4600
+ * @todo remove at version 3
4601
+ * @private
4602
+ */
4603
+ Chart.Controller = Chart;
4604
+ };
4605
+
4606
+ },{"25":25,"28":28,"45":45,"48":48}],24:[function(require,module,exports){
4607
+ 'use strict';
4608
+
4609
+ var helpers = require(45);
4610
+
4611
+ module.exports = function(Chart) {
4612
+
4613
+ var arrayEvents = ['push', 'pop', 'shift', 'splice', 'unshift'];
4614
+
4615
+ /**
4616
+ * Hooks the array methods that add or remove values ('push', pop', 'shift', 'splice',
4617
+ * 'unshift') and notify the listener AFTER the array has been altered. Listeners are
4618
+ * called on the 'onData*' callbacks (e.g. onDataPush, etc.) with same arguments.
4619
+ */
4620
+ function listenArrayEvents(array, listener) {
4621
+ if (array._chartjs) {
4622
+ array._chartjs.listeners.push(listener);
4623
+ return;
4624
+ }
4625
+
4626
+ Object.defineProperty(array, '_chartjs', {
4627
+ configurable: true,
4628
+ enumerable: false,
4629
+ value: {
4630
+ listeners: [listener]
4631
+ }
4632
+ });
4633
+
4634
+ arrayEvents.forEach(function(key) {
4635
+ var method = 'onData' + key.charAt(0).toUpperCase() + key.slice(1);
4636
+ var base = array[key];
4637
+
4638
+ Object.defineProperty(array, key, {
4639
+ configurable: true,
4640
+ enumerable: false,
4641
+ value: function() {
4642
+ var args = Array.prototype.slice.call(arguments);
4643
+ var res = base.apply(this, args);
4644
+
4645
+ helpers.each(array._chartjs.listeners, function(object) {
4646
+ if (typeof object[method] === 'function') {
4647
+ object[method].apply(object, args);
4648
+ }
4649
+ });
4650
+
4651
+ return res;
4652
+ }
4653
+ });
4654
+ });
4655
+ }
4656
+
4657
+ /**
4658
+ * Removes the given array event listener and cleanup extra attached properties (such as
4659
+ * the _chartjs stub and overridden methods) if array doesn't have any more listeners.
4660
+ */
4661
+ function unlistenArrayEvents(array, listener) {
4662
+ var stub = array._chartjs;
4663
+ if (!stub) {
4664
+ return;
4665
+ }
4666
+
4667
+ var listeners = stub.listeners;
4668
+ var index = listeners.indexOf(listener);
4669
+ if (index !== -1) {
4670
+ listeners.splice(index, 1);
4671
+ }
4672
+
4673
+ if (listeners.length > 0) {
4674
+ return;
4675
+ }
4676
+
4677
+ arrayEvents.forEach(function(key) {
4678
+ delete array[key];
4679
+ });
4680
+
4681
+ delete array._chartjs;
4682
+ }
4683
+
4684
+ // Base class for all dataset controllers (line, bar, etc)
4685
+ Chart.DatasetController = function(chart, datasetIndex) {
4686
+ this.initialize(chart, datasetIndex);
4687
+ };
4688
+
4689
+ helpers.extend(Chart.DatasetController.prototype, {
4690
+
4691
+ /**
4692
+ * Element type used to generate a meta dataset (e.g. Chart.element.Line).
4693
+ * @type {Chart.core.element}
4694
+ */
4695
+ datasetElementType: null,
4696
+
4697
+ /**
4698
+ * Element type used to generate a meta data (e.g. Chart.element.Point).
4699
+ * @type {Chart.core.element}
4700
+ */
4701
+ dataElementType: null,
4702
+
4703
+ initialize: function(chart, datasetIndex) {
4704
+ var me = this;
4705
+ me.chart = chart;
4706
+ me.index = datasetIndex;
4707
+ me.linkScales();
4708
+ me.addElements();
4709
+ },
4710
+
4711
+ updateIndex: function(datasetIndex) {
4712
+ this.index = datasetIndex;
4713
+ },
4714
+
4715
+ linkScales: function() {
4716
+ var me = this;
4717
+ var meta = me.getMeta();
4718
+ var dataset = me.getDataset();
4719
+
4720
+ if (meta.xAxisID === null) {
4721
+ meta.xAxisID = dataset.xAxisID || me.chart.options.scales.xAxes[0].id;
4722
+ }
4723
+ if (meta.yAxisID === null) {
4724
+ meta.yAxisID = dataset.yAxisID || me.chart.options.scales.yAxes[0].id;
4725
+ }
4726
+ },
4727
+
4728
+ getDataset: function() {
4729
+ return this.chart.data.datasets[this.index];
4730
+ },
4731
+
4732
+ getMeta: function() {
4733
+ return this.chart.getDatasetMeta(this.index);
4734
+ },
4735
+
4736
+ getScaleForId: function(scaleID) {
4737
+ return this.chart.scales[scaleID];
4738
+ },
4739
+
4740
+ reset: function() {
4741
+ this.update(true);
4742
+ },
4743
+
4744
+ /**
4745
+ * @private
4746
+ */
4747
+ destroy: function() {
4748
+ if (this._data) {
4749
+ unlistenArrayEvents(this._data, this);
4750
+ }
4751
+ },
4752
+
4753
+ createMetaDataset: function() {
4754
+ var me = this;
4755
+ var type = me.datasetElementType;
4756
+ return type && new type({
4757
+ _chart: me.chart,
4758
+ _datasetIndex: me.index
4759
+ });
4760
+ },
4761
+
4762
+ createMetaData: function(index) {
4763
+ var me = this;
4764
+ var type = me.dataElementType;
4765
+ return type && new type({
4766
+ _chart: me.chart,
4767
+ _datasetIndex: me.index,
4768
+ _index: index
4769
+ });
4770
+ },
4771
+
4772
+ addElements: function() {
4773
+ var me = this;
4774
+ var meta = me.getMeta();
4775
+ var data = me.getDataset().data || [];
4776
+ var metaData = meta.data;
4777
+ var i, ilen;
4778
+
4779
+ for (i = 0, ilen = data.length; i < ilen; ++i) {
4780
+ metaData[i] = metaData[i] || me.createMetaData(i);
4781
+ }
4782
+
4783
+ meta.dataset = meta.dataset || me.createMetaDataset();
4784
+ },
4785
+
4786
+ addElementAndReset: function(index) {
4787
+ var element = this.createMetaData(index);
4788
+ this.getMeta().data.splice(index, 0, element);
4789
+ this.updateElement(element, index, true);
4790
+ },
4791
+
4792
+ buildOrUpdateElements: function() {
4793
+ var me = this;
4794
+ var dataset = me.getDataset();
4795
+ var data = dataset.data || (dataset.data = []);
4796
+
4797
+ // In order to correctly handle data addition/deletion animation (an thus simulate
4798
+ // real-time charts), we need to monitor these data modifications and synchronize
4799
+ // the internal meta data accordingly.
4800
+ if (me._data !== data) {
4801
+ if (me._data) {
4802
+ // This case happens when the user replaced the data array instance.
4803
+ unlistenArrayEvents(me._data, me);
4804
+ }
4805
+
4806
+ listenArrayEvents(data, me);
4807
+ me._data = data;
4808
+ }
4809
+
4810
+ // Re-sync meta data in case the user replaced the data array or if we missed
4811
+ // any updates and so make sure that we handle number of datapoints changing.
4812
+ me.resyncElements();
4813
+ },
4814
+
4815
+ update: helpers.noop,
4816
+
4817
+ transition: function(easingValue) {
4818
+ var meta = this.getMeta();
4819
+ var elements = meta.data || [];
4820
+ var ilen = elements.length;
4821
+ var i = 0;
4822
+
4823
+ for (; i < ilen; ++i) {
4824
+ elements[i].transition(easingValue);
4825
+ }
4826
+
4827
+ if (meta.dataset) {
4828
+ meta.dataset.transition(easingValue);
4829
+ }
4830
+ },
4831
+
4832
+ draw: function() {
4833
+ var meta = this.getMeta();
4834
+ var elements = meta.data || [];
4835
+ var ilen = elements.length;
4836
+ var i = 0;
4837
+
4838
+ if (meta.dataset) {
4839
+ meta.dataset.draw();
4840
+ }
4841
+
4842
+ for (; i < ilen; ++i) {
4843
+ elements[i].draw();
4844
+ }
4845
+ },
4846
+
4847
+ removeHoverStyle: function(element, elementOpts) {
4848
+ var dataset = this.chart.data.datasets[element._datasetIndex];
4849
+ var index = element._index;
4850
+ var custom = element.custom || {};
4851
+ var valueOrDefault = helpers.valueAtIndexOrDefault;
4852
+ var model = element._model;
4853
+
4854
+ model.backgroundColor = custom.backgroundColor ? custom.backgroundColor : valueOrDefault(dataset.backgroundColor, index, elementOpts.backgroundColor);
4855
+ model.borderColor = custom.borderColor ? custom.borderColor : valueOrDefault(dataset.borderColor, index, elementOpts.borderColor);
4856
+ model.borderWidth = custom.borderWidth ? custom.borderWidth : valueOrDefault(dataset.borderWidth, index, elementOpts.borderWidth);
4857
+ },
4858
+
4859
+ setHoverStyle: function(element) {
4860
+ var dataset = this.chart.data.datasets[element._datasetIndex];
4861
+ var index = element._index;
4862
+ var custom = element.custom || {};
4863
+ var valueOrDefault = helpers.valueAtIndexOrDefault;
4864
+ var getHoverColor = helpers.getHoverColor;
4865
+ var model = element._model;
4866
+
4867
+ model.backgroundColor = custom.hoverBackgroundColor ? custom.hoverBackgroundColor : valueOrDefault(dataset.hoverBackgroundColor, index, getHoverColor(model.backgroundColor));
4868
+ model.borderColor = custom.hoverBorderColor ? custom.hoverBorderColor : valueOrDefault(dataset.hoverBorderColor, index, getHoverColor(model.borderColor));
4869
+ model.borderWidth = custom.hoverBorderWidth ? custom.hoverBorderWidth : valueOrDefault(dataset.hoverBorderWidth, index, model.borderWidth);
4870
+ },
4871
+
4872
+ /**
4873
+ * @private
4874
+ */
4875
+ resyncElements: function() {
4876
+ var me = this;
4877
+ var meta = me.getMeta();
4878
+ var data = me.getDataset().data;
4879
+ var numMeta = meta.data.length;
4880
+ var numData = data.length;
4881
+
4882
+ if (numData < numMeta) {
4883
+ meta.data.splice(numData, numMeta - numData);
4884
+ } else if (numData > numMeta) {
4885
+ me.insertElements(numMeta, numData - numMeta);
4886
+ }
4887
+ },
4888
+
4889
+ /**
4890
+ * @private
4891
+ */
4892
+ insertElements: function(start, count) {
4893
+ for (var i = 0; i < count; ++i) {
4894
+ this.addElementAndReset(start + i);
4895
+ }
4896
+ },
4897
+
4898
+ /**
4899
+ * @private
4900
+ */
4901
+ onDataPush: function() {
4902
+ this.insertElements(this.getDataset().data.length - 1, arguments.length);
4903
+ },
4904
+
4905
+ /**
4906
+ * @private
4907
+ */
4908
+ onDataPop: function() {
4909
+ this.getMeta().data.pop();
4910
+ },
4911
+
4912
+ /**
4913
+ * @private
4914
+ */
4915
+ onDataShift: function() {
4916
+ this.getMeta().data.shift();
4917
+ },
4918
+
4919
+ /**
4920
+ * @private
4921
+ */
4922
+ onDataSplice: function(start, count) {
4923
+ this.getMeta().data.splice(start, count);
4924
+ this.insertElements(start, arguments.length - 2);
4925
+ },
4926
+
4927
+ /**
4928
+ * @private
4929
+ */
4930
+ onDataUnshift: function() {
4931
+ this.insertElements(0, arguments.length);
4932
+ }
4933
+ });
4934
+
4935
+ Chart.DatasetController.extend = helpers.inherits;
4936
+ };
4937
+
4938
+ },{"45":45}],25:[function(require,module,exports){
4939
+ 'use strict';
4940
+
4941
+ var helpers = require(45);
4942
+
4943
+ module.exports = {
4944
+ /**
4945
+ * @private
4946
+ */
4947
+ _set: function(scope, values) {
4948
+ return helpers.merge(this[scope] || (this[scope] = {}), values);
4949
+ }
4950
+ };
4951
+
4952
+ },{"45":45}],26:[function(require,module,exports){
4953
+ 'use strict';
4954
+
4955
+ var color = require(3);
4956
+ var helpers = require(45);
4957
+
4958
+ function interpolate(start, view, model, ease) {
4959
+ var keys = Object.keys(model);
4960
+ var i, ilen, key, actual, origin, target, type, c0, c1;
4961
+
4962
+ for (i = 0, ilen = keys.length; i < ilen; ++i) {
4963
+ key = keys[i];
4964
+
4965
+ target = model[key];
4966
+
4967
+ // if a value is added to the model after pivot() has been called, the view
4968
+ // doesn't contain it, so let's initialize the view to the target value.
4969
+ if (!view.hasOwnProperty(key)) {
4970
+ view[key] = target;
4971
+ }
4972
+
4973
+ actual = view[key];
4974
+
4975
+ if (actual === target || key[0] === '_') {
4976
+ continue;
4977
+ }
4978
+
4979
+ if (!start.hasOwnProperty(key)) {
4980
+ start[key] = actual;
4981
+ }
4982
+
4983
+ origin = start[key];
4984
+
4985
+ type = typeof target;
4986
+
4987
+ if (type === typeof origin) {
4988
+ if (type === 'string') {
4989
+ c0 = color(origin);
4990
+ if (c0.valid) {
4991
+ c1 = color(target);
4992
+ if (c1.valid) {
4993
+ view[key] = c1.mix(c0, ease).rgbString();
4994
+ continue;
4995
+ }
4996
+ }
4997
+ } else if (type === 'number' && isFinite(origin) && isFinite(target)) {
4998
+ view[key] = origin + (target - origin) * ease;
4999
+ continue;
5000
+ }
5001
+ }
5002
+
5003
+ view[key] = target;
5004
+ }
5005
+ }
5006
+
5007
+ var Element = function(configuration) {
5008
+ helpers.extend(this, configuration);
5009
+ this.initialize.apply(this, arguments);
5010
+ };
5011
+
5012
+ helpers.extend(Element.prototype, {
5013
+
5014
+ initialize: function() {
5015
+ this.hidden = false;
5016
+ },
5017
+
5018
+ pivot: function() {
5019
+ var me = this;
5020
+ if (!me._view) {
5021
+ me._view = helpers.clone(me._model);
5022
+ }
5023
+ me._start = {};
5024
+ return me;
5025
+ },
5026
+
5027
+ transition: function(ease) {
5028
+ var me = this;
5029
+ var model = me._model;
5030
+ var start = me._start;
5031
+ var view = me._view;
5032
+
5033
+ // No animation -> No Transition
5034
+ if (!model || ease === 1) {
5035
+ me._view = model;
5036
+ me._start = null;
5037
+ return me;
5038
+ }
5039
+
5040
+ if (!view) {
5041
+ view = me._view = {};
5042
+ }
5043
+
5044
+ if (!start) {
5045
+ start = me._start = {};
5046
+ }
5047
+
5048
+ interpolate(start, view, model, ease);
5049
+
5050
+ return me;
5051
+ },
5052
+
5053
+ tooltipPosition: function() {
5054
+ return {
5055
+ x: this._model.x,
5056
+ y: this._model.y
5057
+ };
5058
+ },
5059
+
5060
+ hasValue: function() {
5061
+ return helpers.isNumber(this._model.x) && helpers.isNumber(this._model.y);
5062
+ }
5063
+ });
5064
+
5065
+ Element.extend = helpers.inherits;
5066
+
5067
+ module.exports = Element;
5068
+
5069
+ },{"3":3,"45":45}],27:[function(require,module,exports){
5070
+ /* global window: false */
5071
+ /* global document: false */
5072
+ 'use strict';
5073
+
5074
+ var color = require(3);
5075
+ var defaults = require(25);
5076
+ var helpers = require(45);
5077
+
5078
+ module.exports = function(Chart) {
5079
+
5080
+ // -- Basic js utility methods
5081
+
5082
+ helpers.configMerge = function(/* objects ... */) {
5083
+ return helpers.merge(helpers.clone(arguments[0]), [].slice.call(arguments, 1), {
5084
+ merger: function(key, target, source, options) {
5085
+ var tval = target[key] || {};
5086
+ var sval = source[key];
5087
+
5088
+ if (key === 'scales') {
5089
+ // scale config merging is complex. Add our own function here for that
5090
+ target[key] = helpers.scaleMerge(tval, sval);
5091
+ } else if (key === 'scale') {
5092
+ // used in polar area & radar charts since there is only one scale
5093
+ target[key] = helpers.merge(tval, [Chart.scaleService.getScaleDefaults(sval.type), sval]);
5094
+ } else {
5095
+ helpers._merger(key, target, source, options);
5096
+ }
5097
+ }
5098
+ });
5099
+ };
5100
+
5101
+ helpers.scaleMerge = function(/* objects ... */) {
5102
+ return helpers.merge(helpers.clone(arguments[0]), [].slice.call(arguments, 1), {
5103
+ merger: function(key, target, source, options) {
5104
+ if (key === 'xAxes' || key === 'yAxes') {
5105
+ var slen = source[key].length;
5106
+ var i, type, scale;
5107
+
5108
+ if (!target[key]) {
5109
+ target[key] = [];
5110
+ }
5111
+
5112
+ for (i = 0; i < slen; ++i) {
5113
+ scale = source[key][i];
5114
+ type = helpers.valueOrDefault(scale.type, key === 'xAxes' ? 'category' : 'linear');
5115
+
5116
+ if (i >= target[key].length) {
5117
+ target[key].push({});
5118
+ }
5119
+
5120
+ if (!target[key][i].type || (scale.type && scale.type !== target[key][i].type)) {
5121
+ // new/untyped scale or type changed: let's apply the new defaults
5122
+ // then merge source scale to correctly overwrite the defaults.
5123
+ helpers.merge(target[key][i], [Chart.scaleService.getScaleDefaults(type), scale]);
5124
+ } else {
5125
+ // scales type are the same
5126
+ helpers.merge(target[key][i], scale);
5127
+ }
5128
+ }
5129
+ } else {
5130
+ helpers._merger(key, target, source, options);
5131
+ }
5132
+ }
5133
+ });
5134
+ };
5135
+
5136
+ helpers.where = function(collection, filterCallback) {
5137
+ if (helpers.isArray(collection) && Array.prototype.filter) {
5138
+ return collection.filter(filterCallback);
5139
+ }
5140
+ var filtered = [];
5141
+
5142
+ helpers.each(collection, function(item) {
5143
+ if (filterCallback(item)) {
5144
+ filtered.push(item);
5145
+ }
5146
+ });
5147
+
5148
+ return filtered;
5149
+ };
5150
+ helpers.findIndex = Array.prototype.findIndex ?
5151
+ function(array, callback, scope) {
5152
+ return array.findIndex(callback, scope);
5153
+ } :
5154
+ function(array, callback, scope) {
5155
+ scope = scope === undefined ? array : scope;
5156
+ for (var i = 0, ilen = array.length; i < ilen; ++i) {
5157
+ if (callback.call(scope, array[i], i, array)) {
5158
+ return i;
5159
+ }
5160
+ }
5161
+ return -1;
5162
+ };
5163
+ helpers.findNextWhere = function(arrayToSearch, filterCallback, startIndex) {
5164
+ // Default to start of the array
5165
+ if (helpers.isNullOrUndef(startIndex)) {
5166
+ startIndex = -1;
5167
+ }
5168
+ for (var i = startIndex + 1; i < arrayToSearch.length; i++) {
5169
+ var currentItem = arrayToSearch[i];
5170
+ if (filterCallback(currentItem)) {
5171
+ return currentItem;
5172
+ }
5173
+ }
5174
+ };
5175
+ helpers.findPreviousWhere = function(arrayToSearch, filterCallback, startIndex) {
5176
+ // Default to end of the array
5177
+ if (helpers.isNullOrUndef(startIndex)) {
5178
+ startIndex = arrayToSearch.length;
5179
+ }
5180
+ for (var i = startIndex - 1; i >= 0; i--) {
5181
+ var currentItem = arrayToSearch[i];
5182
+ if (filterCallback(currentItem)) {
5183
+ return currentItem;
5184
+ }
5185
+ }
5186
+ };
5187
+
5188
+ // -- Math methods
5189
+ helpers.isNumber = function(n) {
5190
+ return !isNaN(parseFloat(n)) && isFinite(n);
5191
+ };
5192
+ helpers.almostEquals = function(x, y, epsilon) {
5193
+ return Math.abs(x - y) < epsilon;
5194
+ };
5195
+ helpers.almostWhole = function(x, epsilon) {
5196
+ var rounded = Math.round(x);
5197
+ return (((rounded - epsilon) < x) && ((rounded + epsilon) > x));
5198
+ };
5199
+ helpers.max = function(array) {
5200
+ return array.reduce(function(max, value) {
5201
+ if (!isNaN(value)) {
5202
+ return Math.max(max, value);
5203
+ }
5204
+ return max;
5205
+ }, Number.NEGATIVE_INFINITY);
5206
+ };
5207
+ helpers.min = function(array) {
5208
+ return array.reduce(function(min, value) {
5209
+ if (!isNaN(value)) {
5210
+ return Math.min(min, value);
5211
+ }
5212
+ return min;
5213
+ }, Number.POSITIVE_INFINITY);
5214
+ };
5215
+ helpers.sign = Math.sign ?
5216
+ function(x) {
5217
+ return Math.sign(x);
5218
+ } :
5219
+ function(x) {
5220
+ x = +x; // convert to a number
5221
+ if (x === 0 || isNaN(x)) {
5222
+ return x;
5223
+ }
5224
+ return x > 0 ? 1 : -1;
5225
+ };
5226
+ helpers.log10 = Math.log10 ?
5227
+ function(x) {
5228
+ return Math.log10(x);
5229
+ } :
5230
+ function(x) {
5231
+ return Math.log(x) / Math.LN10;
5232
+ };
5233
+ helpers.toRadians = function(degrees) {
5234
+ return degrees * (Math.PI / 180);
5235
+ };
5236
+ helpers.toDegrees = function(radians) {
5237
+ return radians * (180 / Math.PI);
5238
+ };
5239
+ // Gets the angle from vertical upright to the point about a centre.
5240
+ helpers.getAngleFromPoint = function(centrePoint, anglePoint) {
5241
+ var distanceFromXCenter = anglePoint.x - centrePoint.x;
5242
+ var distanceFromYCenter = anglePoint.y - centrePoint.y;
5243
+ var radialDistanceFromCenter = Math.sqrt(distanceFromXCenter * distanceFromXCenter + distanceFromYCenter * distanceFromYCenter);
5244
+
5245
+ var angle = Math.atan2(distanceFromYCenter, distanceFromXCenter);
5246
+
5247
+ if (angle < (-0.5 * Math.PI)) {
5248
+ angle += 2.0 * Math.PI; // make sure the returned angle is in the range of (-PI/2, 3PI/2]
5249
+ }
5250
+
5251
+ return {
5252
+ angle: angle,
5253
+ distance: radialDistanceFromCenter
5254
+ };
5255
+ };
5256
+ helpers.distanceBetweenPoints = function(pt1, pt2) {
5257
+ return Math.sqrt(Math.pow(pt2.x - pt1.x, 2) + Math.pow(pt2.y - pt1.y, 2));
5258
+ };
5259
+ helpers.aliasPixel = function(pixelWidth) {
5260
+ return (pixelWidth % 2 === 0) ? 0 : 0.5;
5261
+ };
5262
+ helpers.splineCurve = function(firstPoint, middlePoint, afterPoint, t) {
5263
+ // Props to Rob Spencer at scaled innovation for his post on splining between points
5264
+ // http://scaledinnovation.com/analytics/splines/aboutSplines.html
5265
+
5266
+ // This function must also respect "skipped" points
5267
+
5268
+ var previous = firstPoint.skip ? middlePoint : firstPoint;
5269
+ var current = middlePoint;
5270
+ var next = afterPoint.skip ? middlePoint : afterPoint;
5271
+
5272
+ var d01 = Math.sqrt(Math.pow(current.x - previous.x, 2) + Math.pow(current.y - previous.y, 2));
5273
+ var d12 = Math.sqrt(Math.pow(next.x - current.x, 2) + Math.pow(next.y - current.y, 2));
5274
+
5275
+ var s01 = d01 / (d01 + d12);
5276
+ var s12 = d12 / (d01 + d12);
5277
+
5278
+ // If all points are the same, s01 & s02 will be inf
5279
+ s01 = isNaN(s01) ? 0 : s01;
5280
+ s12 = isNaN(s12) ? 0 : s12;
5281
+
5282
+ var fa = t * s01; // scaling factor for triangle Ta
5283
+ var fb = t * s12;
5284
+
5285
+ return {
5286
+ previous: {
5287
+ x: current.x - fa * (next.x - previous.x),
5288
+ y: current.y - fa * (next.y - previous.y)
5289
+ },
5290
+ next: {
5291
+ x: current.x + fb * (next.x - previous.x),
5292
+ y: current.y + fb * (next.y - previous.y)
5293
+ }
5294
+ };
5295
+ };
5296
+ helpers.EPSILON = Number.EPSILON || 1e-14;
5297
+ helpers.splineCurveMonotone = function(points) {
5298
+ // This function calculates Bézier control points in a similar way than |splineCurve|,
5299
+ // but preserves monotonicity of the provided data and ensures no local extremums are added
5300
+ // between the dataset discrete points due to the interpolation.
5301
+ // See : https://en.wikipedia.org/wiki/Monotone_cubic_interpolation
5302
+
5303
+ var pointsWithTangents = (points || []).map(function(point) {
5304
+ return {
5305
+ model: point._model,
5306
+ deltaK: 0,
5307
+ mK: 0
5308
+ };
5309
+ });
5310
+
5311
+ // Calculate slopes (deltaK) and initialize tangents (mK)
5312
+ var pointsLen = pointsWithTangents.length;
5313
+ var i, pointBefore, pointCurrent, pointAfter;
5314
+ for (i = 0; i < pointsLen; ++i) {
5315
+ pointCurrent = pointsWithTangents[i];
5316
+ if (pointCurrent.model.skip) {
5317
+ continue;
5318
+ }
5319
+
5320
+ pointBefore = i > 0 ? pointsWithTangents[i - 1] : null;
5321
+ pointAfter = i < pointsLen - 1 ? pointsWithTangents[i + 1] : null;
5322
+ if (pointAfter && !pointAfter.model.skip) {
5323
+ var slopeDeltaX = (pointAfter.model.x - pointCurrent.model.x);
5324
+
5325
+ // In the case of two points that appear at the same x pixel, slopeDeltaX is 0
5326
+ pointCurrent.deltaK = slopeDeltaX !== 0 ? (pointAfter.model.y - pointCurrent.model.y) / slopeDeltaX : 0;
5327
+ }
5328
+
5329
+ if (!pointBefore || pointBefore.model.skip) {
5330
+ pointCurrent.mK = pointCurrent.deltaK;
5331
+ } else if (!pointAfter || pointAfter.model.skip) {
5332
+ pointCurrent.mK = pointBefore.deltaK;
5333
+ } else if (this.sign(pointBefore.deltaK) !== this.sign(pointCurrent.deltaK)) {
5334
+ pointCurrent.mK = 0;
5335
+ } else {
5336
+ pointCurrent.mK = (pointBefore.deltaK + pointCurrent.deltaK) / 2;
5337
+ }
5338
+ }
5339
+
5340
+ // Adjust tangents to ensure monotonic properties
5341
+ var alphaK, betaK, tauK, squaredMagnitude;
5342
+ for (i = 0; i < pointsLen - 1; ++i) {
5343
+ pointCurrent = pointsWithTangents[i];
5344
+ pointAfter = pointsWithTangents[i + 1];
5345
+ if (pointCurrent.model.skip || pointAfter.model.skip) {
5346
+ continue;
5347
+ }
5348
+
5349
+ if (helpers.almostEquals(pointCurrent.deltaK, 0, this.EPSILON)) {
5350
+ pointCurrent.mK = pointAfter.mK = 0;
5351
+ continue;
5352
+ }
5353
+
5354
+ alphaK = pointCurrent.mK / pointCurrent.deltaK;
5355
+ betaK = pointAfter.mK / pointCurrent.deltaK;
5356
+ squaredMagnitude = Math.pow(alphaK, 2) + Math.pow(betaK, 2);
5357
+ if (squaredMagnitude <= 9) {
5358
+ continue;
5359
+ }
5360
+
5361
+ tauK = 3 / Math.sqrt(squaredMagnitude);
5362
+ pointCurrent.mK = alphaK * tauK * pointCurrent.deltaK;
5363
+ pointAfter.mK = betaK * tauK * pointCurrent.deltaK;
5364
+ }
5365
+
5366
+ // Compute control points
5367
+ var deltaX;
5368
+ for (i = 0; i < pointsLen; ++i) {
5369
+ pointCurrent = pointsWithTangents[i];
5370
+ if (pointCurrent.model.skip) {
5371
+ continue;
5372
+ }
5373
+
5374
+ pointBefore = i > 0 ? pointsWithTangents[i - 1] : null;
5375
+ pointAfter = i < pointsLen - 1 ? pointsWithTangents[i + 1] : null;
5376
+ if (pointBefore && !pointBefore.model.skip) {
5377
+ deltaX = (pointCurrent.model.x - pointBefore.model.x) / 3;
5378
+ pointCurrent.model.controlPointPreviousX = pointCurrent.model.x - deltaX;
5379
+ pointCurrent.model.controlPointPreviousY = pointCurrent.model.y - deltaX * pointCurrent.mK;
5380
+ }
5381
+ if (pointAfter && !pointAfter.model.skip) {
5382
+ deltaX = (pointAfter.model.x - pointCurrent.model.x) / 3;
5383
+ pointCurrent.model.controlPointNextX = pointCurrent.model.x + deltaX;
5384
+ pointCurrent.model.controlPointNextY = pointCurrent.model.y + deltaX * pointCurrent.mK;
5385
+ }
5386
+ }
5387
+ };
5388
+ helpers.nextItem = function(collection, index, loop) {
5389
+ if (loop) {
5390
+ return index >= collection.length - 1 ? collection[0] : collection[index + 1];
5391
+ }
5392
+ return index >= collection.length - 1 ? collection[collection.length - 1] : collection[index + 1];
5393
+ };
5394
+ helpers.previousItem = function(collection, index, loop) {
5395
+ if (loop) {
5396
+ return index <= 0 ? collection[collection.length - 1] : collection[index - 1];
5397
+ }
5398
+ return index <= 0 ? collection[0] : collection[index - 1];
5399
+ };
5400
+ // Implementation of the nice number algorithm used in determining where axis labels will go
5401
+ helpers.niceNum = function(range, round) {
5402
+ var exponent = Math.floor(helpers.log10(range));
5403
+ var fraction = range / Math.pow(10, exponent);
5404
+ var niceFraction;
5405
+
5406
+ if (round) {
5407
+ if (fraction < 1.5) {
5408
+ niceFraction = 1;
5409
+ } else if (fraction < 3) {
5410
+ niceFraction = 2;
5411
+ } else if (fraction < 7) {
5412
+ niceFraction = 5;
5413
+ } else {
5414
+ niceFraction = 10;
5415
+ }
5416
+ } else if (fraction <= 1.0) {
5417
+ niceFraction = 1;
5418
+ } else if (fraction <= 2) {
5419
+ niceFraction = 2;
5420
+ } else if (fraction <= 5) {
5421
+ niceFraction = 5;
5422
+ } else {
5423
+ niceFraction = 10;
5424
+ }
5425
+
5426
+ return niceFraction * Math.pow(10, exponent);
5427
+ };
5428
+ // Request animation polyfill - http://www.paulirish.com/2011/requestanimationframe-for-smart-animating/
5429
+ helpers.requestAnimFrame = (function() {
5430
+ if (typeof window === 'undefined') {
5431
+ return function(callback) {
5432
+ callback();
5433
+ };
5434
+ }
5435
+ return window.requestAnimationFrame ||
5436
+ window.webkitRequestAnimationFrame ||
5437
+ window.mozRequestAnimationFrame ||
5438
+ window.oRequestAnimationFrame ||
5439
+ window.msRequestAnimationFrame ||
5440
+ function(callback) {
5441
+ return window.setTimeout(callback, 1000 / 60);
5442
+ };
5443
+ }());
5444
+ // -- DOM methods
5445
+ helpers.getRelativePosition = function(evt, chart) {
5446
+ var mouseX, mouseY;
5447
+ var e = evt.originalEvent || evt;
5448
+ var canvas = evt.currentTarget || evt.srcElement;
5449
+ var boundingRect = canvas.getBoundingClientRect();
5450
+
5451
+ var touches = e.touches;
5452
+ if (touches && touches.length > 0) {
5453
+ mouseX = touches[0].clientX;
5454
+ mouseY = touches[0].clientY;
5455
+
5456
+ } else {
5457
+ mouseX = e.clientX;
5458
+ mouseY = e.clientY;
5459
+ }
5460
+
5461
+ // Scale mouse coordinates into canvas coordinates
5462
+ // by following the pattern laid out by 'jerryj' in the comments of
5463
+ // http://www.html5canvastutorials.com/advanced/html5-canvas-mouse-coordinates/
5464
+ var paddingLeft = parseFloat(helpers.getStyle(canvas, 'padding-left'));
5465
+ var paddingTop = parseFloat(helpers.getStyle(canvas, 'padding-top'));
5466
+ var paddingRight = parseFloat(helpers.getStyle(canvas, 'padding-right'));
5467
+ var paddingBottom = parseFloat(helpers.getStyle(canvas, 'padding-bottom'));
5468
+ var width = boundingRect.right - boundingRect.left - paddingLeft - paddingRight;
5469
+ var height = boundingRect.bottom - boundingRect.top - paddingTop - paddingBottom;
5470
+
5471
+ // We divide by the current device pixel ratio, because the canvas is scaled up by that amount in each direction. However
5472
+ // the backend model is in unscaled coordinates. Since we are going to deal with our model coordinates, we go back here
5473
+ mouseX = Math.round((mouseX - boundingRect.left - paddingLeft) / (width) * canvas.width / chart.currentDevicePixelRatio);
5474
+ mouseY = Math.round((mouseY - boundingRect.top - paddingTop) / (height) * canvas.height / chart.currentDevicePixelRatio);
5475
+
5476
+ return {
5477
+ x: mouseX,
5478
+ y: mouseY
5479
+ };
5480
+
5481
+ };
5482
+
5483
+ // Private helper function to convert max-width/max-height values that may be percentages into a number
5484
+ function parseMaxStyle(styleValue, node, parentProperty) {
5485
+ var valueInPixels;
5486
+ if (typeof styleValue === 'string') {
5487
+ valueInPixels = parseInt(styleValue, 10);
5488
+
5489
+ if (styleValue.indexOf('%') !== -1) {
5490
+ // percentage * size in dimension
5491
+ valueInPixels = valueInPixels / 100 * node.parentNode[parentProperty];
5492
+ }
5493
+ } else {
5494
+ valueInPixels = styleValue;
5495
+ }
5496
+
5497
+ return valueInPixels;
5498
+ }
5499
+
5500
+ /**
5501
+ * Returns if the given value contains an effective constraint.
5502
+ * @private
5503
+ */
5504
+ function isConstrainedValue(value) {
5505
+ return value !== undefined && value !== null && value !== 'none';
5506
+ }
5507
+
5508
+ // Private helper to get a constraint dimension
5509
+ // @param domNode : the node to check the constraint on
5510
+ // @param maxStyle : the style that defines the maximum for the direction we are using (maxWidth / maxHeight)
5511
+ // @param percentageProperty : property of parent to use when calculating width as a percentage
5512
+ // @see http://www.nathanaeljones.com/blog/2013/reading-max-width-cross-browser
5513
+ function getConstraintDimension(domNode, maxStyle, percentageProperty) {
5514
+ var view = document.defaultView;
5515
+ var parentNode = domNode.parentNode;
5516
+ var constrainedNode = view.getComputedStyle(domNode)[maxStyle];
5517
+ var constrainedContainer = view.getComputedStyle(parentNode)[maxStyle];
5518
+ var hasCNode = isConstrainedValue(constrainedNode);
5519
+ var hasCContainer = isConstrainedValue(constrainedContainer);
5520
+ var infinity = Number.POSITIVE_INFINITY;
5521
+
5522
+ if (hasCNode || hasCContainer) {
5523
+ return Math.min(
5524
+ hasCNode ? parseMaxStyle(constrainedNode, domNode, percentageProperty) : infinity,
5525
+ hasCContainer ? parseMaxStyle(constrainedContainer, parentNode, percentageProperty) : infinity);
5526
+ }
5527
+
5528
+ return 'none';
5529
+ }
5530
+ // returns Number or undefined if no constraint
5531
+ helpers.getConstraintWidth = function(domNode) {
5532
+ return getConstraintDimension(domNode, 'max-width', 'clientWidth');
5533
+ };
5534
+ // returns Number or undefined if no constraint
5535
+ helpers.getConstraintHeight = function(domNode) {
5536
+ return getConstraintDimension(domNode, 'max-height', 'clientHeight');
5537
+ };
5538
+ helpers.getMaximumWidth = function(domNode) {
5539
+ var container = domNode.parentNode;
5540
+ if (!container) {
5541
+ return domNode.clientWidth;
5542
+ }
5543
+
5544
+ var paddingLeft = parseInt(helpers.getStyle(container, 'padding-left'), 10);
5545
+ var paddingRight = parseInt(helpers.getStyle(container, 'padding-right'), 10);
5546
+ var w = container.clientWidth - paddingLeft - paddingRight;
5547
+ var cw = helpers.getConstraintWidth(domNode);
5548
+ return isNaN(cw) ? w : Math.min(w, cw);
5549
+ };
5550
+ helpers.getMaximumHeight = function(domNode) {
5551
+ var container = domNode.parentNode;
5552
+ if (!container) {
5553
+ return domNode.clientHeight;
5554
+ }
5555
+
5556
+ var paddingTop = parseInt(helpers.getStyle(container, 'padding-top'), 10);
5557
+ var paddingBottom = parseInt(helpers.getStyle(container, 'padding-bottom'), 10);
5558
+ var h = container.clientHeight - paddingTop - paddingBottom;
5559
+ var ch = helpers.getConstraintHeight(domNode);
5560
+ return isNaN(ch) ? h : Math.min(h, ch);
5561
+ };
5562
+ helpers.getStyle = function(el, property) {
5563
+ return el.currentStyle ?
5564
+ el.currentStyle[property] :
5565
+ document.defaultView.getComputedStyle(el, null).getPropertyValue(property);
5566
+ };
5567
+ helpers.retinaScale = function(chart, forceRatio) {
5568
+ var pixelRatio = chart.currentDevicePixelRatio = forceRatio || window.devicePixelRatio || 1;
5569
+ if (pixelRatio === 1) {
5570
+ return;
5571
+ }
5572
+
5573
+ var canvas = chart.canvas;
5574
+ var height = chart.height;
5575
+ var width = chart.width;
5576
+
5577
+ canvas.height = height * pixelRatio;
5578
+ canvas.width = width * pixelRatio;
5579
+ chart.ctx.scale(pixelRatio, pixelRatio);
5580
+
5581
+ // If no style has been set on the canvas, the render size is used as display size,
5582
+ // making the chart visually bigger, so let's enforce it to the "correct" values.
5583
+ // See https://github.com/chartjs/Chart.js/issues/3575
5584
+ canvas.style.height = height + 'px';
5585
+ canvas.style.width = width + 'px';
5586
+ };
5587
+ // -- Canvas methods
5588
+ helpers.fontString = function(pixelSize, fontStyle, fontFamily) {
5589
+ return fontStyle + ' ' + pixelSize + 'px ' + fontFamily;
5590
+ };
5591
+ helpers.longestText = function(ctx, font, arrayOfThings, cache) {
5592
+ cache = cache || {};
5593
+ var data = cache.data = cache.data || {};
5594
+ var gc = cache.garbageCollect = cache.garbageCollect || [];
5595
+
5596
+ if (cache.font !== font) {
5597
+ data = cache.data = {};
5598
+ gc = cache.garbageCollect = [];
5599
+ cache.font = font;
5600
+ }
5601
+
5602
+ ctx.font = font;
5603
+ var longest = 0;
5604
+ helpers.each(arrayOfThings, function(thing) {
5605
+ // Undefined strings and arrays should not be measured
5606
+ if (thing !== undefined && thing !== null && helpers.isArray(thing) !== true) {
5607
+ longest = helpers.measureText(ctx, data, gc, longest, thing);
5608
+ } else if (helpers.isArray(thing)) {
5609
+ // if it is an array lets measure each element
5610
+ // to do maybe simplify this function a bit so we can do this more recursively?
5611
+ helpers.each(thing, function(nestedThing) {
5612
+ // Undefined strings and arrays should not be measured
5613
+ if (nestedThing !== undefined && nestedThing !== null && !helpers.isArray(nestedThing)) {
5614
+ longest = helpers.measureText(ctx, data, gc, longest, nestedThing);
5615
+ }
5616
+ });
5617
+ }
5618
+ });
5619
+
5620
+ var gcLen = gc.length / 2;
5621
+ if (gcLen > arrayOfThings.length) {
5622
+ for (var i = 0; i < gcLen; i++) {
5623
+ delete data[gc[i]];
5624
+ }
5625
+ gc.splice(0, gcLen);
5626
+ }
5627
+ return longest;
5628
+ };
5629
+ helpers.measureText = function(ctx, data, gc, longest, string) {
5630
+ var textWidth = data[string];
5631
+ if (!textWidth) {
5632
+ textWidth = data[string] = ctx.measureText(string).width;
5633
+ gc.push(string);
5634
+ }
5635
+ if (textWidth > longest) {
5636
+ longest = textWidth;
5637
+ }
5638
+ return longest;
5639
+ };
5640
+ helpers.numberOfLabelLines = function(arrayOfThings) {
5641
+ var numberOfLines = 1;
5642
+ helpers.each(arrayOfThings, function(thing) {
5643
+ if (helpers.isArray(thing)) {
5644
+ if (thing.length > numberOfLines) {
5645
+ numberOfLines = thing.length;
5646
+ }
5647
+ }
5648
+ });
5649
+ return numberOfLines;
5650
+ };
5651
+
5652
+ helpers.color = !color ?
5653
+ function(value) {
5654
+ console.error('Color.js not found!');
5655
+ return value;
5656
+ } :
5657
+ function(value) {
5658
+ /* global CanvasGradient */
5659
+ if (value instanceof CanvasGradient) {
5660
+ value = defaults.global.defaultColor;
5661
+ }
5662
+
5663
+ return color(value);
5664
+ };
5665
+
5666
+ helpers.getHoverColor = function(colorValue) {
5667
+ /* global CanvasPattern */
5668
+ return (colorValue instanceof CanvasPattern) ?
5669
+ colorValue :
5670
+ helpers.color(colorValue).saturate(0.5).darken(0.1).rgbString();
5671
+ };
5672
+ };
5673
+
5674
+ },{"25":25,"3":3,"45":45}],28:[function(require,module,exports){
5675
+ 'use strict';
5676
+
5677
+ var helpers = require(45);
5678
+
5679
+ /**
5680
+ * Helper function to get relative position for an event
5681
+ * @param {Event|IEvent} event - The event to get the position for
5682
+ * @param {Chart} chart - The chart
5683
+ * @returns {Point} the event position
5684
+ */
5685
+ function getRelativePosition(e, chart) {
5686
+ if (e.native) {
5687
+ return {
5688
+ x: e.x,
5689
+ y: e.y
5690
+ };
5691
+ }
5692
+
5693
+ return helpers.getRelativePosition(e, chart);
5694
+ }
5695
+
5696
+ /**
5697
+ * Helper function to traverse all of the visible elements in the chart
5698
+ * @param chart {chart} the chart
5699
+ * @param handler {Function} the callback to execute for each visible item
5700
+ */
5701
+ function parseVisibleItems(chart, handler) {
5702
+ var datasets = chart.data.datasets;
5703
+ var meta, i, j, ilen, jlen;
5704
+
5705
+ for (i = 0, ilen = datasets.length; i < ilen; ++i) {
5706
+ if (!chart.isDatasetVisible(i)) {
5707
+ continue;
5708
+ }
5709
+
5710
+ meta = chart.getDatasetMeta(i);
5711
+ for (j = 0, jlen = meta.data.length; j < jlen; ++j) {
5712
+ var element = meta.data[j];
5713
+ if (!element._view.skip) {
5714
+ handler(element);
5715
+ }
5716
+ }
5717
+ }
5718
+ }
5719
+
5720
+ /**
5721
+ * Helper function to get the items that intersect the event position
5722
+ * @param items {ChartElement[]} elements to filter
5723
+ * @param position {Point} the point to be nearest to
5724
+ * @return {ChartElement[]} the nearest items
5725
+ */
5726
+ function getIntersectItems(chart, position) {
5727
+ var elements = [];
5728
+
5729
+ parseVisibleItems(chart, function(element) {
5730
+ if (element.inRange(position.x, position.y)) {
5731
+ elements.push(element);
5732
+ }
5733
+ });
5734
+
5735
+ return elements;
5736
+ }
5737
+
5738
+ /**
5739
+ * Helper function to get the items nearest to the event position considering all visible items in teh chart
5740
+ * @param chart {Chart} the chart to look at elements from
5741
+ * @param position {Point} the point to be nearest to
5742
+ * @param intersect {Boolean} if true, only consider items that intersect the position
5743
+ * @param distanceMetric {Function} function to provide the distance between points
5744
+ * @return {ChartElement[]} the nearest items
5745
+ */
5746
+ function getNearestItems(chart, position, intersect, distanceMetric) {
5747
+ var minDistance = Number.POSITIVE_INFINITY;
5748
+ var nearestItems = [];
5749
+
5750
+ parseVisibleItems(chart, function(element) {
5751
+ if (intersect && !element.inRange(position.x, position.y)) {
5752
+ return;
5753
+ }
5754
+
5755
+ var center = element.getCenterPoint();
5756
+ var distance = distanceMetric(position, center);
5757
+
5758
+ if (distance < minDistance) {
5759
+ nearestItems = [element];
5760
+ minDistance = distance;
5761
+ } else if (distance === minDistance) {
5762
+ // Can have multiple items at the same distance in which case we sort by size
5763
+ nearestItems.push(element);
5764
+ }
5765
+ });
5766
+
5767
+ return nearestItems;
5768
+ }
5769
+
5770
+ /**
5771
+ * Get a distance metric function for two points based on the
5772
+ * axis mode setting
5773
+ * @param {String} axis the axis mode. x|y|xy
5774
+ */
5775
+ function getDistanceMetricForAxis(axis) {
5776
+ var useX = axis.indexOf('x') !== -1;
5777
+ var useY = axis.indexOf('y') !== -1;
5778
+
5779
+ return function(pt1, pt2) {
5780
+ var deltaX = useX ? Math.abs(pt1.x - pt2.x) : 0;
5781
+ var deltaY = useY ? Math.abs(pt1.y - pt2.y) : 0;
5782
+ return Math.sqrt(Math.pow(deltaX, 2) + Math.pow(deltaY, 2));
5783
+ };
5784
+ }
5785
+
5786
+ function indexMode(chart, e, options) {
5787
+ var position = getRelativePosition(e, chart);
5788
+ // Default axis for index mode is 'x' to match old behaviour
5789
+ options.axis = options.axis || 'x';
5790
+ var distanceMetric = getDistanceMetricForAxis(options.axis);
5791
+ var items = options.intersect ? getIntersectItems(chart, position) : getNearestItems(chart, position, false, distanceMetric);
5792
+ var elements = [];
5793
+
5794
+ if (!items.length) {
5795
+ return [];
5796
+ }
5797
+
5798
+ chart.data.datasets.forEach(function(dataset, datasetIndex) {
5799
+ if (chart.isDatasetVisible(datasetIndex)) {
5800
+ var meta = chart.getDatasetMeta(datasetIndex);
5801
+ var element = meta.data[items[0]._index];
5802
+
5803
+ // don't count items that are skipped (null data)
5804
+ if (element && !element._view.skip) {
5805
+ elements.push(element);
5806
+ }
5807
+ }
5808
+ });
5809
+
5810
+ return elements;
5811
+ }
5812
+
5813
+ /**
5814
+ * @interface IInteractionOptions
5815
+ */
5816
+ /**
5817
+ * If true, only consider items that intersect the point
5818
+ * @name IInterfaceOptions#boolean
5819
+ * @type Boolean
5820
+ */
5821
+
5822
+ /**
5823
+ * Contains interaction related functions
5824
+ * @namespace Chart.Interaction
5825
+ */
5826
+ module.exports = {
5827
+ // Helper function for different modes
5828
+ modes: {
5829
+ single: function(chart, e) {
5830
+ var position = getRelativePosition(e, chart);
5831
+ var elements = [];
5832
+
5833
+ parseVisibleItems(chart, function(element) {
5834
+ if (element.inRange(position.x, position.y)) {
5835
+ elements.push(element);
5836
+ return elements;
5837
+ }
5838
+ });
5839
+
5840
+ return elements.slice(0, 1);
5841
+ },
5842
+
5843
+ /**
5844
+ * @function Chart.Interaction.modes.label
5845
+ * @deprecated since version 2.4.0
5846
+ * @todo remove at version 3
5847
+ * @private
5848
+ */
5849
+ label: indexMode,
5850
+
5851
+ /**
5852
+ * Returns items at the same index. If the options.intersect parameter is true, we only return items if we intersect something
5853
+ * If the options.intersect mode is false, we find the nearest item and return the items at the same index as that item
5854
+ * @function Chart.Interaction.modes.index
5855
+ * @since v2.4.0
5856
+ * @param chart {chart} the chart we are returning items from
5857
+ * @param e {Event} the event we are find things at
5858
+ * @param options {IInteractionOptions} options to use during interaction
5859
+ * @return {Chart.Element[]} Array of elements that are under the point. If none are found, an empty array is returned
5860
+ */
5861
+ index: indexMode,
5862
+
5863
+ /**
5864
+ * Returns items in the same dataset. If the options.intersect parameter is true, we only return items if we intersect something
5865
+ * If the options.intersect is false, we find the nearest item and return the items in that dataset
5866
+ * @function Chart.Interaction.modes.dataset
5867
+ * @param chart {chart} the chart we are returning items from
5868
+ * @param e {Event} the event we are find things at
5869
+ * @param options {IInteractionOptions} options to use during interaction
5870
+ * @return {Chart.Element[]} Array of elements that are under the point. If none are found, an empty array is returned
5871
+ */
5872
+ dataset: function(chart, e, options) {
5873
+ var position = getRelativePosition(e, chart);
5874
+ options.axis = options.axis || 'xy';
5875
+ var distanceMetric = getDistanceMetricForAxis(options.axis);
5876
+ var items = options.intersect ? getIntersectItems(chart, position) : getNearestItems(chart, position, false, distanceMetric);
5877
+
5878
+ if (items.length > 0) {
5879
+ items = chart.getDatasetMeta(items[0]._datasetIndex).data;
5880
+ }
5881
+
5882
+ return items;
5883
+ },
5884
+
5885
+ /**
5886
+ * @function Chart.Interaction.modes.x-axis
5887
+ * @deprecated since version 2.4.0. Use index mode and intersect == true
5888
+ * @todo remove at version 3
5889
+ * @private
5890
+ */
5891
+ 'x-axis': function(chart, e) {
5892
+ return indexMode(chart, e, {intersect: false});
5893
+ },
5894
+
5895
+ /**
5896
+ * Point mode returns all elements that hit test based on the event position
5897
+ * of the event
5898
+ * @function Chart.Interaction.modes.intersect
5899
+ * @param chart {chart} the chart we are returning items from
5900
+ * @param e {Event} the event we are find things at
5901
+ * @return {Chart.Element[]} Array of elements that are under the point. If none are found, an empty array is returned
5902
+ */
5903
+ point: function(chart, e) {
5904
+ var position = getRelativePosition(e, chart);
5905
+ return getIntersectItems(chart, position);
5906
+ },
5907
+
5908
+ /**
5909
+ * nearest mode returns the element closest to the point
5910
+ * @function Chart.Interaction.modes.intersect
5911
+ * @param chart {chart} the chart we are returning items from
5912
+ * @param e {Event} the event we are find things at
5913
+ * @param options {IInteractionOptions} options to use
5914
+ * @return {Chart.Element[]} Array of elements that are under the point. If none are found, an empty array is returned
5915
+ */
5916
+ nearest: function(chart, e, options) {
5917
+ var position = getRelativePosition(e, chart);
5918
+ options.axis = options.axis || 'xy';
5919
+ var distanceMetric = getDistanceMetricForAxis(options.axis);
5920
+ var nearestItems = getNearestItems(chart, position, options.intersect, distanceMetric);
5921
+
5922
+ // We have multiple items at the same distance from the event. Now sort by smallest
5923
+ if (nearestItems.length > 1) {
5924
+ nearestItems.sort(function(a, b) {
5925
+ var sizeA = a.getArea();
5926
+ var sizeB = b.getArea();
5927
+ var ret = sizeA - sizeB;
5928
+
5929
+ if (ret === 0) {
5930
+ // if equal sort by dataset index
5931
+ ret = a._datasetIndex - b._datasetIndex;
5932
+ }
5933
+
5934
+ return ret;
5935
+ });
5936
+ }
5937
+
5938
+ // Return only 1 item
5939
+ return nearestItems.slice(0, 1);
5940
+ },
5941
+
5942
+ /**
5943
+ * x mode returns the elements that hit-test at the current x coordinate
5944
+ * @function Chart.Interaction.modes.x
5945
+ * @param chart {chart} the chart we are returning items from
5946
+ * @param e {Event} the event we are find things at
5947
+ * @param options {IInteractionOptions} options to use
5948
+ * @return {Chart.Element[]} Array of elements that are under the point. If none are found, an empty array is returned
5949
+ */
5950
+ x: function(chart, e, options) {
5951
+ var position = getRelativePosition(e, chart);
5952
+ var items = [];
5953
+ var intersectsItem = false;
5954
+
5955
+ parseVisibleItems(chart, function(element) {
5956
+ if (element.inXRange(position.x)) {
5957
+ items.push(element);
5958
+ }
5959
+
5960
+ if (element.inRange(position.x, position.y)) {
5961
+ intersectsItem = true;
5962
+ }
5963
+ });
5964
+
5965
+ // If we want to trigger on an intersect and we don't have any items
5966
+ // that intersect the position, return nothing
5967
+ if (options.intersect && !intersectsItem) {
5968
+ items = [];
5969
+ }
5970
+ return items;
5971
+ },
5972
+
5973
+ /**
5974
+ * y mode returns the elements that hit-test at the current y coordinate
5975
+ * @function Chart.Interaction.modes.y
5976
+ * @param chart {chart} the chart we are returning items from
5977
+ * @param e {Event} the event we are find things at
5978
+ * @param options {IInteractionOptions} options to use
5979
+ * @return {Chart.Element[]} Array of elements that are under the point. If none are found, an empty array is returned
5980
+ */
5981
+ y: function(chart, e, options) {
5982
+ var position = getRelativePosition(e, chart);
5983
+ var items = [];
5984
+ var intersectsItem = false;
5985
+
5986
+ parseVisibleItems(chart, function(element) {
5987
+ if (element.inYRange(position.y)) {
5988
+ items.push(element);
5989
+ }
5990
+
5991
+ if (element.inRange(position.x, position.y)) {
5992
+ intersectsItem = true;
5993
+ }
5994
+ });
5995
+
5996
+ // If we want to trigger on an intersect and we don't have any items
5997
+ // that intersect the position, return nothing
5998
+ if (options.intersect && !intersectsItem) {
5999
+ items = [];
6000
+ }
6001
+ return items;
6002
+ }
6003
+ }
6004
+ };
6005
+
6006
+ },{"45":45}],29:[function(require,module,exports){
6007
+ 'use strict';
6008
+
6009
+ var defaults = require(25);
6010
+
6011
+ defaults._set('global', {
6012
+ responsive: true,
6013
+ responsiveAnimationDuration: 0,
6014
+ maintainAspectRatio: true,
6015
+ events: ['mousemove', 'mouseout', 'click', 'touchstart', 'touchmove'],
6016
+ hover: {
6017
+ onHover: null,
6018
+ mode: 'nearest',
6019
+ intersect: true,
6020
+ animationDuration: 400
6021
+ },
6022
+ onClick: null,
6023
+ defaultColor: 'rgba(0,0,0,0.1)',
6024
+ defaultFontColor: '#666',
6025
+ defaultFontFamily: "'Helvetica Neue', 'Helvetica', 'Arial', sans-serif",
6026
+ defaultFontSize: 12,
6027
+ defaultFontStyle: 'normal',
6028
+ showLines: true,
6029
+
6030
+ // Element defaults defined in element extensions
6031
+ elements: {},
6032
+
6033
+ // Layout options such as padding
6034
+ layout: {
6035
+ padding: {
6036
+ top: 0,
6037
+ right: 0,
6038
+ bottom: 0,
6039
+ left: 0
6040
+ }
6041
+ }
6042
+ });
6043
+
6044
+ module.exports = function() {
6045
+
6046
+ // Occupy the global variable of Chart, and create a simple base class
6047
+ var Chart = function(item, config) {
6048
+ this.construct(item, config);
6049
+ return this;
6050
+ };
6051
+
6052
+ Chart.Chart = Chart;
6053
+
6054
+ return Chart;
6055
+ };
6056
+
6057
+ },{"25":25}],30:[function(require,module,exports){
6058
+ 'use strict';
6059
+
6060
+ var helpers = require(45);
6061
+
6062
+ module.exports = function(Chart) {
6063
+
6064
+ function filterByPosition(array, position) {
6065
+ return helpers.where(array, function(v) {
6066
+ return v.position === position;
6067
+ });
6068
+ }
6069
+
6070
+ function sortByWeight(array, reverse) {
6071
+ array.forEach(function(v, i) {
6072
+ v._tmpIndex_ = i;
6073
+ return v;
6074
+ });
6075
+ array.sort(function(a, b) {
6076
+ var v0 = reverse ? b : a;
6077
+ var v1 = reverse ? a : b;
6078
+ return v0.weight === v1.weight ?
6079
+ v0._tmpIndex_ - v1._tmpIndex_ :
6080
+ v0.weight - v1.weight;
6081
+ });
6082
+ array.forEach(function(v) {
6083
+ delete v._tmpIndex_;
6084
+ });
6085
+ }
6086
+
6087
+ /**
6088
+ * @interface ILayoutItem
6089
+ * @prop {String} position - The position of the item in the chart layout. Possible values are
6090
+ * 'left', 'top', 'right', 'bottom', and 'chartArea'
6091
+ * @prop {Number} weight - The weight used to sort the item. Higher weights are further away from the chart area
6092
+ * @prop {Boolean} fullWidth - if true, and the item is horizontal, then push vertical boxes down
6093
+ * @prop {Function} isHorizontal - returns true if the layout item is horizontal (ie. top or bottom)
6094
+ * @prop {Function} update - Takes two parameters: width and height. Returns size of item
6095
+ * @prop {Function} getPadding - Returns an object with padding on the edges
6096
+ * @prop {Number} width - Width of item. Must be valid after update()
6097
+ * @prop {Number} height - Height of item. Must be valid after update()
6098
+ * @prop {Number} left - Left edge of the item. Set by layout system and cannot be used in update
6099
+ * @prop {Number} top - Top edge of the item. Set by layout system and cannot be used in update
6100
+ * @prop {Number} right - Right edge of the item. Set by layout system and cannot be used in update
6101
+ * @prop {Number} bottom - Bottom edge of the item. Set by layout system and cannot be used in update
6102
+ */
6103
+
6104
+ // The layout service is very self explanatory. It's responsible for the layout within a chart.
6105
+ // Scales, Legends and Plugins all rely on the layout service and can easily register to be placed anywhere they need
6106
+ // It is this service's responsibility of carrying out that layout.
6107
+ Chart.layoutService = {
6108
+ defaults: {},
6109
+
6110
+ /**
6111
+ * Register a box to a chart.
6112
+ * A box is simply a reference to an object that requires layout. eg. Scales, Legend, Title.
6113
+ * @param {Chart} chart - the chart to use
6114
+ * @param {ILayoutItem} item - the item to add to be layed out
6115
+ */
6116
+ addBox: function(chart, item) {
6117
+ if (!chart.boxes) {
6118
+ chart.boxes = [];
6119
+ }
6120
+
6121
+ // initialize item with default values
6122
+ item.fullWidth = item.fullWidth || false;
6123
+ item.position = item.position || 'top';
6124
+ item.weight = item.weight || 0;
6125
+
6126
+ chart.boxes.push(item);
6127
+ },
6128
+
6129
+ /**
6130
+ * Remove a layoutItem from a chart
6131
+ * @param {Chart} chart - the chart to remove the box from
6132
+ * @param {Object} layoutItem - the item to remove from the layout
6133
+ */
6134
+ removeBox: function(chart, layoutItem) {
6135
+ var index = chart.boxes ? chart.boxes.indexOf(layoutItem) : -1;
6136
+ if (index !== -1) {
6137
+ chart.boxes.splice(index, 1);
6138
+ }
6139
+ },
6140
+
6141
+ /**
6142
+ * Sets (or updates) options on the given `item`.
6143
+ * @param {Chart} chart - the chart in which the item lives (or will be added to)
6144
+ * @param {Object} item - the item to configure with the given options
6145
+ * @param {Object} options - the new item options.
6146
+ */
6147
+ configure: function(chart, item, options) {
6148
+ var props = ['fullWidth', 'position', 'weight'];
6149
+ var ilen = props.length;
6150
+ var i = 0;
6151
+ var prop;
6152
+
6153
+ for (; i < ilen; ++i) {
6154
+ prop = props[i];
6155
+ if (options.hasOwnProperty(prop)) {
6156
+ item[prop] = options[prop];
6157
+ }
6158
+ }
6159
+ },
6160
+
6161
+ /**
6162
+ * Fits boxes of the given chart into the given size by having each box measure itself
6163
+ * then running a fitting algorithm
6164
+ * @param {Chart} chart - the chart
6165
+ * @param {Number} width - the width to fit into
6166
+ * @param {Number} height - the height to fit into
6167
+ */
6168
+ update: function(chart, width, height) {
6169
+ if (!chart) {
6170
+ return;
6171
+ }
6172
+
6173
+ var layoutOptions = chart.options.layout || {};
6174
+ var padding = helpers.options.toPadding(layoutOptions.padding);
6175
+ var leftPadding = padding.left;
6176
+ var rightPadding = padding.right;
6177
+ var topPadding = padding.top;
6178
+ var bottomPadding = padding.bottom;
6179
+
6180
+ var leftBoxes = filterByPosition(chart.boxes, 'left');
6181
+ var rightBoxes = filterByPosition(chart.boxes, 'right');
6182
+ var topBoxes = filterByPosition(chart.boxes, 'top');
6183
+ var bottomBoxes = filterByPosition(chart.boxes, 'bottom');
6184
+ var chartAreaBoxes = filterByPosition(chart.boxes, 'chartArea');
6185
+
6186
+ // Sort boxes by weight. A higher weight is further away from the chart area
6187
+ sortByWeight(leftBoxes, true);
6188
+ sortByWeight(rightBoxes, false);
6189
+ sortByWeight(topBoxes, true);
6190
+ sortByWeight(bottomBoxes, false);
6191
+
6192
+ // Essentially we now have any number of boxes on each of the 4 sides.
6193
+ // Our canvas looks like the following.
6194
+ // The areas L1 and L2 are the left axes. R1 is the right axis, T1 is the top axis and
6195
+ // B1 is the bottom axis
6196
+ // There are also 4 quadrant-like locations (left to right instead of clockwise) reserved for chart overlays
6197
+ // These locations are single-box locations only, when trying to register a chartArea location that is already taken,
6198
+ // an error will be thrown.
6199
+ //
6200
+ // |----------------------------------------------------|
6201
+ // | T1 (Full Width) |
6202
+ // |----------------------------------------------------|
6203
+ // | | | T2 | |
6204
+ // | |----|-------------------------------------|----|
6205
+ // | | | C1 | | C2 | |
6206
+ // | | |----| |----| |
6207
+ // | | | | |
6208
+ // | L1 | L2 | ChartArea (C0) | R1 |
6209
+ // | | | | |
6210
+ // | | |----| |----| |
6211
+ // | | | C3 | | C4 | |
6212
+ // | |----|-------------------------------------|----|
6213
+ // | | | B1 | |
6214
+ // |----------------------------------------------------|
6215
+ // | B2 (Full Width) |
6216
+ // |----------------------------------------------------|
6217
+ //
6218
+ // What we do to find the best sizing, we do the following
6219
+ // 1. Determine the minimum size of the chart area.
6220
+ // 2. Split the remaining width equally between each vertical axis
6221
+ // 3. Split the remaining height equally between each horizontal axis
6222
+ // 4. Give each layout the maximum size it can be. The layout will return it's minimum size
6223
+ // 5. Adjust the sizes of each axis based on it's minimum reported size.
6224
+ // 6. Refit each axis
6225
+ // 7. Position each axis in the final location
6226
+ // 8. Tell the chart the final location of the chart area
6227
+ // 9. Tell any axes that overlay the chart area the positions of the chart area
6228
+
6229
+ // Step 1
6230
+ var chartWidth = width - leftPadding - rightPadding;
6231
+ var chartHeight = height - topPadding - bottomPadding;
6232
+ var chartAreaWidth = chartWidth / 2; // min 50%
6233
+ var chartAreaHeight = chartHeight / 2; // min 50%
6234
+
6235
+ // Step 2
6236
+ var verticalBoxWidth = (width - chartAreaWidth) / (leftBoxes.length + rightBoxes.length);
6237
+
6238
+ // Step 3
6239
+ var horizontalBoxHeight = (height - chartAreaHeight) / (topBoxes.length + bottomBoxes.length);
6240
+
6241
+ // Step 4
6242
+ var maxChartAreaWidth = chartWidth;
6243
+ var maxChartAreaHeight = chartHeight;
6244
+ var minBoxSizes = [];
6245
+
6246
+ function getMinimumBoxSize(box) {
6247
+ var minSize;
6248
+ var isHorizontal = box.isHorizontal();
6249
+
6250
+ if (isHorizontal) {
6251
+ minSize = box.update(box.fullWidth ? chartWidth : maxChartAreaWidth, horizontalBoxHeight);
6252
+ maxChartAreaHeight -= minSize.height;
6253
+ } else {
6254
+ minSize = box.update(verticalBoxWidth, chartAreaHeight);
6255
+ maxChartAreaWidth -= minSize.width;
6256
+ }
6257
+
6258
+ minBoxSizes.push({
6259
+ horizontal: isHorizontal,
6260
+ minSize: minSize,
6261
+ box: box,
6262
+ });
6263
+ }
6264
+
6265
+ helpers.each(leftBoxes.concat(rightBoxes, topBoxes, bottomBoxes), getMinimumBoxSize);
6266
+
6267
+ // If a horizontal box has padding, we move the left boxes over to avoid ugly charts (see issue #2478)
6268
+ var maxHorizontalLeftPadding = 0;
6269
+ var maxHorizontalRightPadding = 0;
6270
+ var maxVerticalTopPadding = 0;
6271
+ var maxVerticalBottomPadding = 0;
6272
+
6273
+ helpers.each(topBoxes.concat(bottomBoxes), function(horizontalBox) {
6274
+ if (horizontalBox.getPadding) {
6275
+ var boxPadding = horizontalBox.getPadding();
6276
+ maxHorizontalLeftPadding = Math.max(maxHorizontalLeftPadding, boxPadding.left);
6277
+ maxHorizontalRightPadding = Math.max(maxHorizontalRightPadding, boxPadding.right);
6278
+ }
6279
+ });
6280
+
6281
+ helpers.each(leftBoxes.concat(rightBoxes), function(verticalBox) {
6282
+ if (verticalBox.getPadding) {
6283
+ var boxPadding = verticalBox.getPadding();
6284
+ maxVerticalTopPadding = Math.max(maxVerticalTopPadding, boxPadding.top);
6285
+ maxVerticalBottomPadding = Math.max(maxVerticalBottomPadding, boxPadding.bottom);
6286
+ }
6287
+ });
6288
+
6289
+ // At this point, maxChartAreaHeight and maxChartAreaWidth are the size the chart area could
6290
+ // be if the axes are drawn at their minimum sizes.
6291
+ // Steps 5 & 6
6292
+ var totalLeftBoxesWidth = leftPadding;
6293
+ var totalRightBoxesWidth = rightPadding;
6294
+ var totalTopBoxesHeight = topPadding;
6295
+ var totalBottomBoxesHeight = bottomPadding;
6296
+
6297
+ // Function to fit a box
6298
+ function fitBox(box) {
6299
+ var minBoxSize = helpers.findNextWhere(minBoxSizes, function(minBox) {
6300
+ return minBox.box === box;
6301
+ });
6302
+
6303
+ if (minBoxSize) {
6304
+ if (box.isHorizontal()) {
6305
+ var scaleMargin = {
6306
+ left: Math.max(totalLeftBoxesWidth, maxHorizontalLeftPadding),
6307
+ right: Math.max(totalRightBoxesWidth, maxHorizontalRightPadding),
6308
+ top: 0,
6309
+ bottom: 0
6310
+ };
6311
+
6312
+ // Don't use min size here because of label rotation. When the labels are rotated, their rotation highly depends
6313
+ // on the margin. Sometimes they need to increase in size slightly
6314
+ box.update(box.fullWidth ? chartWidth : maxChartAreaWidth, chartHeight / 2, scaleMargin);
6315
+ } else {
6316
+ box.update(minBoxSize.minSize.width, maxChartAreaHeight);
6317
+ }
6318
+ }
6319
+ }
6320
+
6321
+ // Update, and calculate the left and right margins for the horizontal boxes
6322
+ helpers.each(leftBoxes.concat(rightBoxes), fitBox);
6323
+
6324
+ helpers.each(leftBoxes, function(box) {
6325
+ totalLeftBoxesWidth += box.width;
6326
+ });
6327
+
6328
+ helpers.each(rightBoxes, function(box) {
6329
+ totalRightBoxesWidth += box.width;
6330
+ });
6331
+
6332
+ // Set the Left and Right margins for the horizontal boxes
6333
+ helpers.each(topBoxes.concat(bottomBoxes), fitBox);
6334
+
6335
+ // Figure out how much margin is on the top and bottom of the vertical boxes
6336
+ helpers.each(topBoxes, function(box) {
6337
+ totalTopBoxesHeight += box.height;
6338
+ });
6339
+
6340
+ helpers.each(bottomBoxes, function(box) {
6341
+ totalBottomBoxesHeight += box.height;
6342
+ });
6343
+
6344
+ function finalFitVerticalBox(box) {
6345
+ var minBoxSize = helpers.findNextWhere(minBoxSizes, function(minSize) {
6346
+ return minSize.box === box;
6347
+ });
6348
+
6349
+ var scaleMargin = {
6350
+ left: 0,
6351
+ right: 0,
6352
+ top: totalTopBoxesHeight,
6353
+ bottom: totalBottomBoxesHeight
6354
+ };
6355
+
6356
+ if (minBoxSize) {
6357
+ box.update(minBoxSize.minSize.width, maxChartAreaHeight, scaleMargin);
6358
+ }
6359
+ }
6360
+
6361
+ // Let the left layout know the final margin
6362
+ helpers.each(leftBoxes.concat(rightBoxes), finalFitVerticalBox);
6363
+
6364
+ // Recalculate because the size of each layout might have changed slightly due to the margins (label rotation for instance)
6365
+ totalLeftBoxesWidth = leftPadding;
6366
+ totalRightBoxesWidth = rightPadding;
6367
+ totalTopBoxesHeight = topPadding;
6368
+ totalBottomBoxesHeight = bottomPadding;
6369
+
6370
+ helpers.each(leftBoxes, function(box) {
6371
+ totalLeftBoxesWidth += box.width;
6372
+ });
6373
+
6374
+ helpers.each(rightBoxes, function(box) {
6375
+ totalRightBoxesWidth += box.width;
6376
+ });
6377
+
6378
+ helpers.each(topBoxes, function(box) {
6379
+ totalTopBoxesHeight += box.height;
6380
+ });
6381
+ helpers.each(bottomBoxes, function(box) {
6382
+ totalBottomBoxesHeight += box.height;
6383
+ });
6384
+
6385
+ // We may be adding some padding to account for rotated x axis labels
6386
+ var leftPaddingAddition = Math.max(maxHorizontalLeftPadding - totalLeftBoxesWidth, 0);
6387
+ totalLeftBoxesWidth += leftPaddingAddition;
6388
+ totalRightBoxesWidth += Math.max(maxHorizontalRightPadding - totalRightBoxesWidth, 0);
6389
+
6390
+ var topPaddingAddition = Math.max(maxVerticalTopPadding - totalTopBoxesHeight, 0);
6391
+ totalTopBoxesHeight += topPaddingAddition;
6392
+ totalBottomBoxesHeight += Math.max(maxVerticalBottomPadding - totalBottomBoxesHeight, 0);
6393
+
6394
+ // Figure out if our chart area changed. This would occur if the dataset layout label rotation
6395
+ // changed due to the application of the margins in step 6. Since we can only get bigger, this is safe to do
6396
+ // without calling `fit` again
6397
+ var newMaxChartAreaHeight = height - totalTopBoxesHeight - totalBottomBoxesHeight;
6398
+ var newMaxChartAreaWidth = width - totalLeftBoxesWidth - totalRightBoxesWidth;
6399
+
6400
+ if (newMaxChartAreaWidth !== maxChartAreaWidth || newMaxChartAreaHeight !== maxChartAreaHeight) {
6401
+ helpers.each(leftBoxes, function(box) {
6402
+ box.height = newMaxChartAreaHeight;
6403
+ });
6404
+
6405
+ helpers.each(rightBoxes, function(box) {
6406
+ box.height = newMaxChartAreaHeight;
6407
+ });
6408
+
6409
+ helpers.each(topBoxes, function(box) {
6410
+ if (!box.fullWidth) {
6411
+ box.width = newMaxChartAreaWidth;
6412
+ }
6413
+ });
6414
+
6415
+ helpers.each(bottomBoxes, function(box) {
6416
+ if (!box.fullWidth) {
6417
+ box.width = newMaxChartAreaWidth;
6418
+ }
6419
+ });
6420
+
6421
+ maxChartAreaHeight = newMaxChartAreaHeight;
6422
+ maxChartAreaWidth = newMaxChartAreaWidth;
6423
+ }
6424
+
6425
+ // Step 7 - Position the boxes
6426
+ var left = leftPadding + leftPaddingAddition;
6427
+ var top = topPadding + topPaddingAddition;
6428
+
6429
+ function placeBox(box) {
6430
+ if (box.isHorizontal()) {
6431
+ box.left = box.fullWidth ? leftPadding : totalLeftBoxesWidth;
6432
+ box.right = box.fullWidth ? width - rightPadding : totalLeftBoxesWidth + maxChartAreaWidth;
6433
+ box.top = top;
6434
+ box.bottom = top + box.height;
6435
+
6436
+ // Move to next point
6437
+ top = box.bottom;
6438
+
6439
+ } else {
6440
+
6441
+ box.left = left;
6442
+ box.right = left + box.width;
6443
+ box.top = totalTopBoxesHeight;
6444
+ box.bottom = totalTopBoxesHeight + maxChartAreaHeight;
6445
+
6446
+ // Move to next point
6447
+ left = box.right;
6448
+ }
6449
+ }
6450
+
6451
+ helpers.each(leftBoxes.concat(topBoxes), placeBox);
6452
+
6453
+ // Account for chart width and height
6454
+ left += maxChartAreaWidth;
6455
+ top += maxChartAreaHeight;
6456
+
6457
+ helpers.each(rightBoxes, placeBox);
6458
+ helpers.each(bottomBoxes, placeBox);
6459
+
6460
+ // Step 8
6461
+ chart.chartArea = {
6462
+ left: totalLeftBoxesWidth,
6463
+ top: totalTopBoxesHeight,
6464
+ right: totalLeftBoxesWidth + maxChartAreaWidth,
6465
+ bottom: totalTopBoxesHeight + maxChartAreaHeight
6466
+ };
6467
+
6468
+ // Step 9
6469
+ helpers.each(chartAreaBoxes, function(box) {
6470
+ box.left = chart.chartArea.left;
6471
+ box.top = chart.chartArea.top;
6472
+ box.right = chart.chartArea.right;
6473
+ box.bottom = chart.chartArea.bottom;
6474
+
6475
+ box.update(maxChartAreaWidth, maxChartAreaHeight);
6476
+ });
6477
+ }
6478
+ };
6479
+ };
6480
+
6481
+ },{"45":45}],31:[function(require,module,exports){
6482
+ 'use strict';
6483
+
6484
+ var defaults = require(25);
6485
+ var Element = require(26);
6486
+ var helpers = require(45);
6487
+
6488
+ defaults._set('global', {
6489
+ plugins: {}
6490
+ });
6491
+
6492
+ module.exports = function(Chart) {
6493
+
6494
+ /**
6495
+ * The plugin service singleton
6496
+ * @namespace Chart.plugins
6497
+ * @since 2.1.0
6498
+ */
6499
+ Chart.plugins = {
6500
+ /**
6501
+ * Globally registered plugins.
6502
+ * @private
6503
+ */
6504
+ _plugins: [],
6505
+
6506
+ /**
6507
+ * This identifier is used to invalidate the descriptors cache attached to each chart
6508
+ * when a global plugin is registered or unregistered. In this case, the cache ID is
6509
+ * incremented and descriptors are regenerated during following API calls.
6510
+ * @private
6511
+ */
6512
+ _cacheId: 0,
6513
+
6514
+ /**
6515
+ * Registers the given plugin(s) if not already registered.
6516
+ * @param {Array|Object} plugins plugin instance(s).
6517
+ */
6518
+ register: function(plugins) {
6519
+ var p = this._plugins;
6520
+ ([]).concat(plugins).forEach(function(plugin) {
6521
+ if (p.indexOf(plugin) === -1) {
6522
+ p.push(plugin);
6523
+ }
6524
+ });
6525
+
6526
+ this._cacheId++;
6527
+ },
6528
+
6529
+ /**
6530
+ * Unregisters the given plugin(s) only if registered.
6531
+ * @param {Array|Object} plugins plugin instance(s).
6532
+ */
6533
+ unregister: function(plugins) {
6534
+ var p = this._plugins;
6535
+ ([]).concat(plugins).forEach(function(plugin) {
6536
+ var idx = p.indexOf(plugin);
6537
+ if (idx !== -1) {
6538
+ p.splice(idx, 1);
6539
+ }
6540
+ });
6541
+
6542
+ this._cacheId++;
6543
+ },
6544
+
6545
+ /**
6546
+ * Remove all registered plugins.
6547
+ * @since 2.1.5
6548
+ */
6549
+ clear: function() {
6550
+ this._plugins = [];
6551
+ this._cacheId++;
6552
+ },
6553
+
6554
+ /**
6555
+ * Returns the number of registered plugins?
6556
+ * @returns {Number}
6557
+ * @since 2.1.5
6558
+ */
6559
+ count: function() {
6560
+ return this._plugins.length;
6561
+ },
6562
+
6563
+ /**
6564
+ * Returns all registered plugin instances.
6565
+ * @returns {Array} array of plugin objects.
6566
+ * @since 2.1.5
6567
+ */
6568
+ getAll: function() {
6569
+ return this._plugins;
6570
+ },
6571
+
6572
+ /**
6573
+ * Calls enabled plugins for `chart` on the specified hook and with the given args.
6574
+ * This method immediately returns as soon as a plugin explicitly returns false. The
6575
+ * returned value can be used, for instance, to interrupt the current action.
6576
+ * @param {Object} chart - The chart instance for which plugins should be called.
6577
+ * @param {String} hook - The name of the plugin method to call (e.g. 'beforeUpdate').
6578
+ * @param {Array} [args] - Extra arguments to apply to the hook call.
6579
+ * @returns {Boolean} false if any of the plugins return false, else returns true.
6580
+ */
6581
+ notify: function(chart, hook, args) {
6582
+ var descriptors = this.descriptors(chart);
6583
+ var ilen = descriptors.length;
6584
+ var i, descriptor, plugin, params, method;
6585
+
6586
+ for (i = 0; i < ilen; ++i) {
6587
+ descriptor = descriptors[i];
6588
+ plugin = descriptor.plugin;
6589
+ method = plugin[hook];
6590
+ if (typeof method === 'function') {
6591
+ params = [chart].concat(args || []);
6592
+ params.push(descriptor.options);
6593
+ if (method.apply(plugin, params) === false) {
6594
+ return false;
6595
+ }
6596
+ }
6597
+ }
6598
+
6599
+ return true;
6600
+ },
6601
+
6602
+ /**
6603
+ * Returns descriptors of enabled plugins for the given chart.
6604
+ * @returns {Array} [{ plugin, options }]
6605
+ * @private
6606
+ */
6607
+ descriptors: function(chart) {
6608
+ var cache = chart._plugins || (chart._plugins = {});
6609
+ if (cache.id === this._cacheId) {
6610
+ return cache.descriptors;
6611
+ }
6612
+
6613
+ var plugins = [];
6614
+ var descriptors = [];
6615
+ var config = (chart && chart.config) || {};
6616
+ var options = (config.options && config.options.plugins) || {};
6617
+
6618
+ this._plugins.concat(config.plugins || []).forEach(function(plugin) {
6619
+ var idx = plugins.indexOf(plugin);
6620
+ if (idx !== -1) {
6621
+ return;
6622
+ }
6623
+
6624
+ var id = plugin.id;
6625
+ var opts = options[id];
6626
+ if (opts === false) {
6627
+ return;
6628
+ }
6629
+
6630
+ if (opts === true) {
6631
+ opts = helpers.clone(defaults.global.plugins[id]);
6632
+ }
6633
+
6634
+ plugins.push(plugin);
6635
+ descriptors.push({
6636
+ plugin: plugin,
6637
+ options: opts || {}
6638
+ });
6639
+ });
6640
+
6641
+ cache.descriptors = descriptors;
6642
+ cache.id = this._cacheId;
6643
+ return descriptors;
6644
+ }
6645
+ };
6646
+
6647
+ /**
6648
+ * Plugin extension hooks.
6649
+ * @interface IPlugin
6650
+ * @since 2.1.0
6651
+ */
6652
+ /**
6653
+ * @method IPlugin#beforeInit
6654
+ * @desc Called before initializing `chart`.
6655
+ * @param {Chart.Controller} chart - The chart instance.
6656
+ * @param {Object} options - The plugin options.
6657
+ */
6658
+ /**
6659
+ * @method IPlugin#afterInit
6660
+ * @desc Called after `chart` has been initialized and before the first update.
6661
+ * @param {Chart.Controller} chart - The chart instance.
6662
+ * @param {Object} options - The plugin options.
6663
+ */
6664
+ /**
6665
+ * @method IPlugin#beforeUpdate
6666
+ * @desc Called before updating `chart`. If any plugin returns `false`, the update
6667
+ * is cancelled (and thus subsequent render(s)) until another `update` is triggered.
6668
+ * @param {Chart.Controller} chart - The chart instance.
6669
+ * @param {Object} options - The plugin options.
6670
+ * @returns {Boolean} `false` to cancel the chart update.
6671
+ */
6672
+ /**
6673
+ * @method IPlugin#afterUpdate
6674
+ * @desc Called after `chart` has been updated and before rendering. Note that this
6675
+ * hook will not be called if the chart update has been previously cancelled.
6676
+ * @param {Chart.Controller} chart - The chart instance.
6677
+ * @param {Object} options - The plugin options.
6678
+ */
6679
+ /**
6680
+ * @method IPlugin#beforeDatasetsUpdate
6681
+ * @desc Called before updating the `chart` datasets. If any plugin returns `false`,
6682
+ * the datasets update is cancelled until another `update` is triggered.
6683
+ * @param {Chart.Controller} chart - The chart instance.
6684
+ * @param {Object} options - The plugin options.
6685
+ * @returns {Boolean} false to cancel the datasets update.
6686
+ * @since version 2.1.5
6687
+ */
6688
+ /**
6689
+ * @method IPlugin#afterDatasetsUpdate
6690
+ * @desc Called after the `chart` datasets have been updated. Note that this hook
6691
+ * will not be called if the datasets update has been previously cancelled.
6692
+ * @param {Chart.Controller} chart - The chart instance.
6693
+ * @param {Object} options - The plugin options.
6694
+ * @since version 2.1.5
6695
+ */
6696
+ /**
6697
+ * @method IPlugin#beforeDatasetUpdate
6698
+ * @desc Called before updating the `chart` dataset at the given `args.index`. If any plugin
6699
+ * returns `false`, the datasets update is cancelled until another `update` is triggered.
6700
+ * @param {Chart} chart - The chart instance.
6701
+ * @param {Object} args - The call arguments.
6702
+ * @param {Number} args.index - The dataset index.
6703
+ * @param {Object} args.meta - The dataset metadata.
6704
+ * @param {Object} options - The plugin options.
6705
+ * @returns {Boolean} `false` to cancel the chart datasets drawing.
6706
+ */
6707
+ /**
6708
+ * @method IPlugin#afterDatasetUpdate
6709
+ * @desc Called after the `chart` datasets at the given `args.index` has been updated. Note
6710
+ * that this hook will not be called if the datasets update has been previously cancelled.
6711
+ * @param {Chart} chart - The chart instance.
6712
+ * @param {Object} args - The call arguments.
6713
+ * @param {Number} args.index - The dataset index.
6714
+ * @param {Object} args.meta - The dataset metadata.
6715
+ * @param {Object} options - The plugin options.
6716
+ */
6717
+ /**
6718
+ * @method IPlugin#beforeLayout
6719
+ * @desc Called before laying out `chart`. If any plugin returns `false`,
6720
+ * the layout update is cancelled until another `update` is triggered.
6721
+ * @param {Chart.Controller} chart - The chart instance.
6722
+ * @param {Object} options - The plugin options.
6723
+ * @returns {Boolean} `false` to cancel the chart layout.
6724
+ */
6725
+ /**
6726
+ * @method IPlugin#afterLayout
6727
+ * @desc Called after the `chart` has been layed out. Note that this hook will not
6728
+ * be called if the layout update has been previously cancelled.
6729
+ * @param {Chart.Controller} chart - The chart instance.
6730
+ * @param {Object} options - The plugin options.
6731
+ */
6732
+ /**
6733
+ * @method IPlugin#beforeRender
6734
+ * @desc Called before rendering `chart`. If any plugin returns `false`,
6735
+ * the rendering is cancelled until another `render` is triggered.
6736
+ * @param {Chart.Controller} chart - The chart instance.
6737
+ * @param {Object} options - The plugin options.
6738
+ * @returns {Boolean} `false` to cancel the chart rendering.
6739
+ */
6740
+ /**
6741
+ * @method IPlugin#afterRender
6742
+ * @desc Called after the `chart` has been fully rendered (and animation completed). Note
6743
+ * that this hook will not be called if the rendering has been previously cancelled.
6744
+ * @param {Chart.Controller} chart - The chart instance.
6745
+ * @param {Object} options - The plugin options.
6746
+ */
6747
+ /**
6748
+ * @method IPlugin#beforeDraw
6749
+ * @desc Called before drawing `chart` at every animation frame specified by the given
6750
+ * easing value. If any plugin returns `false`, the frame drawing is cancelled until
6751
+ * another `render` is triggered.
6752
+ * @param {Chart.Controller} chart - The chart instance.
6753
+ * @param {Number} easingValue - The current animation value, between 0.0 and 1.0.
6754
+ * @param {Object} options - The plugin options.
6755
+ * @returns {Boolean} `false` to cancel the chart drawing.
6756
+ */
6757
+ /**
6758
+ * @method IPlugin#afterDraw
6759
+ * @desc Called after the `chart` has been drawn for the specific easing value. Note
6760
+ * that this hook will not be called if the drawing has been previously cancelled.
6761
+ * @param {Chart.Controller} chart - The chart instance.
6762
+ * @param {Number} easingValue - The current animation value, between 0.0 and 1.0.
6763
+ * @param {Object} options - The plugin options.
6764
+ */
6765
+ /**
6766
+ * @method IPlugin#beforeDatasetsDraw
6767
+ * @desc Called before drawing the `chart` datasets. If any plugin returns `false`,
6768
+ * the datasets drawing is cancelled until another `render` is triggered.
6769
+ * @param {Chart.Controller} chart - The chart instance.
6770
+ * @param {Number} easingValue - The current animation value, between 0.0 and 1.0.
6771
+ * @param {Object} options - The plugin options.
6772
+ * @returns {Boolean} `false` to cancel the chart datasets drawing.
6773
+ */
6774
+ /**
6775
+ * @method IPlugin#afterDatasetsDraw
6776
+ * @desc Called after the `chart` datasets have been drawn. Note that this hook
6777
+ * will not be called if the datasets drawing has been previously cancelled.
6778
+ * @param {Chart.Controller} chart - The chart instance.
6779
+ * @param {Number} easingValue - The current animation value, between 0.0 and 1.0.
6780
+ * @param {Object} options - The plugin options.
6781
+ */
6782
+ /**
6783
+ * @method IPlugin#beforeDatasetDraw
6784
+ * @desc Called before drawing the `chart` dataset at the given `args.index` (datasets
6785
+ * are drawn in the reverse order). If any plugin returns `false`, the datasets drawing
6786
+ * is cancelled until another `render` is triggered.
6787
+ * @param {Chart} chart - The chart instance.
6788
+ * @param {Object} args - The call arguments.
6789
+ * @param {Number} args.index - The dataset index.
6790
+ * @param {Object} args.meta - The dataset metadata.
6791
+ * @param {Number} args.easingValue - The current animation value, between 0.0 and 1.0.
6792
+ * @param {Object} options - The plugin options.
6793
+ * @returns {Boolean} `false` to cancel the chart datasets drawing.
6794
+ */
6795
+ /**
6796
+ * @method IPlugin#afterDatasetDraw
6797
+ * @desc Called after the `chart` datasets at the given `args.index` have been drawn
6798
+ * (datasets are drawn in the reverse order). Note that this hook will not be called
6799
+ * if the datasets drawing has been previously cancelled.
6800
+ * @param {Chart} chart - The chart instance.
6801
+ * @param {Object} args - The call arguments.
6802
+ * @param {Number} args.index - The dataset index.
6803
+ * @param {Object} args.meta - The dataset metadata.
6804
+ * @param {Number} args.easingValue - The current animation value, between 0.0 and 1.0.
6805
+ * @param {Object} options - The plugin options.
6806
+ */
6807
+ /**
6808
+ * @method IPlugin#beforeTooltipDraw
6809
+ * @desc Called before drawing the `tooltip`. If any plugin returns `false`,
6810
+ * the tooltip drawing is cancelled until another `render` is triggered.
6811
+ * @param {Chart} chart - The chart instance.
6812
+ * @param {Object} args - The call arguments.
6813
+ * @param {Object} args.tooltip - The tooltip.
6814
+ * @param {Number} args.easingValue - The current animation value, between 0.0 and 1.0.
6815
+ * @param {Object} options - The plugin options.
6816
+ * @returns {Boolean} `false` to cancel the chart tooltip drawing.
6817
+ */
6818
+ /**
6819
+ * @method IPlugin#afterTooltipDraw
6820
+ * @desc Called after drawing the `tooltip`. Note that this hook will not
6821
+ * be called if the tooltip drawing has been previously cancelled.
6822
+ * @param {Chart} chart - The chart instance.
6823
+ * @param {Object} args - The call arguments.
6824
+ * @param {Object} args.tooltip - The tooltip.
6825
+ * @param {Number} args.easingValue - The current animation value, between 0.0 and 1.0.
6826
+ * @param {Object} options - The plugin options.
6827
+ */
6828
+ /**
6829
+ * @method IPlugin#beforeEvent
6830
+ * @desc Called before processing the specified `event`. If any plugin returns `false`,
6831
+ * the event will be discarded.
6832
+ * @param {Chart.Controller} chart - The chart instance.
6833
+ * @param {IEvent} event - The event object.
6834
+ * @param {Object} options - The plugin options.
6835
+ */
6836
+ /**
6837
+ * @method IPlugin#afterEvent
6838
+ * @desc Called after the `event` has been consumed. Note that this hook
6839
+ * will not be called if the `event` has been previously discarded.
6840
+ * @param {Chart.Controller} chart - The chart instance.
6841
+ * @param {IEvent} event - The event object.
6842
+ * @param {Object} options - The plugin options.
6843
+ */
6844
+ /**
6845
+ * @method IPlugin#resize
6846
+ * @desc Called after the chart as been resized.
6847
+ * @param {Chart.Controller} chart - The chart instance.
6848
+ * @param {Number} size - The new canvas display size (eq. canvas.style width & height).
6849
+ * @param {Object} options - The plugin options.
6850
+ */
6851
+ /**
6852
+ * @method IPlugin#destroy
6853
+ * @desc Called after the chart as been destroyed.
6854
+ * @param {Chart.Controller} chart - The chart instance.
6855
+ * @param {Object} options - The plugin options.
6856
+ */
6857
+
6858
+ /**
6859
+ * Provided for backward compatibility, use Chart.plugins instead
6860
+ * @namespace Chart.pluginService
6861
+ * @deprecated since version 2.1.5
6862
+ * @todo remove at version 3
6863
+ * @private
6864
+ */
6865
+ Chart.pluginService = Chart.plugins;
6866
+
6867
+ /**
6868
+ * Provided for backward compatibility, inheriting from Chart.PlugingBase has no
6869
+ * effect, instead simply create/register plugins via plain JavaScript objects.
6870
+ * @interface Chart.PluginBase
6871
+ * @deprecated since version 2.5.0
6872
+ * @todo remove at version 3
6873
+ * @private
6874
+ */
6875
+ Chart.PluginBase = Element.extend({});
6876
+ };
6877
+
6878
+ },{"25":25,"26":26,"45":45}],32:[function(require,module,exports){
6879
+ 'use strict';
6880
+
6881
+ var defaults = require(25);
6882
+ var Element = require(26);
6883
+ var helpers = require(45);
6884
+ var Ticks = require(34);
6885
+
6886
+ defaults._set('scale', {
6887
+ display: true,
6888
+ position: 'left',
6889
+ offset: false,
6890
+
6891
+ // grid line settings
6892
+ gridLines: {
6893
+ display: true,
6894
+ color: 'rgba(0, 0, 0, 0.1)',
6895
+ lineWidth: 1,
6896
+ drawBorder: true,
6897
+ drawOnChartArea: true,
6898
+ drawTicks: true,
6899
+ tickMarkLength: 10,
6900
+ zeroLineWidth: 1,
6901
+ zeroLineColor: 'rgba(0,0,0,0.25)',
6902
+ zeroLineBorderDash: [],
6903
+ zeroLineBorderDashOffset: 0.0,
6904
+ offsetGridLines: false,
6905
+ borderDash: [],
6906
+ borderDashOffset: 0.0
6907
+ },
6908
+
6909
+ // scale label
6910
+ scaleLabel: {
6911
+ // display property
6912
+ display: false,
6913
+
6914
+ // actual label
6915
+ labelString: '',
6916
+
6917
+ // line height
6918
+ lineHeight: 1.2,
6919
+
6920
+ // top/bottom padding
6921
+ padding: {
6922
+ top: 4,
6923
+ bottom: 4
6924
+ }
6925
+ },
6926
+
6927
+ // label settings
6928
+ ticks: {
6929
+ beginAtZero: false,
6930
+ minRotation: 0,
6931
+ maxRotation: 50,
6932
+ mirror: false,
6933
+ padding: 0,
6934
+ reverse: false,
6935
+ display: true,
6936
+ autoSkip: true,
6937
+ autoSkipPadding: 0,
6938
+ labelOffset: 0,
6939
+ // We pass through arrays to be rendered as multiline labels, we convert Others to strings here.
6940
+ callback: Ticks.formatters.values,
6941
+ minor: {},
6942
+ major: {}
6943
+ }
6944
+ });
6945
+
6946
+ function labelsFromTicks(ticks) {
6947
+ var labels = [];
6948
+ var i, ilen;
6949
+
6950
+ for (i = 0, ilen = ticks.length; i < ilen; ++i) {
6951
+ labels.push(ticks[i].label);
6952
+ }
6953
+
6954
+ return labels;
6955
+ }
6956
+
6957
+ function getLineValue(scale, index, offsetGridLines) {
6958
+ var lineValue = scale.getPixelForTick(index);
6959
+
6960
+ if (offsetGridLines) {
6961
+ if (index === 0) {
6962
+ lineValue -= (scale.getPixelForTick(1) - lineValue) / 2;
6963
+ } else {
6964
+ lineValue -= (lineValue - scale.getPixelForTick(index - 1)) / 2;
6965
+ }
6966
+ }
6967
+ return lineValue;
6968
+ }
6969
+
6970
+ module.exports = function(Chart) {
6971
+
6972
+ function computeTextSize(context, tick, font) {
6973
+ return helpers.isArray(tick) ?
6974
+ helpers.longestText(context, font, tick) :
6975
+ context.measureText(tick).width;
6976
+ }
6977
+
6978
+ function parseFontOptions(options) {
6979
+ var valueOrDefault = helpers.valueOrDefault;
6980
+ var globalDefaults = defaults.global;
6981
+ var size = valueOrDefault(options.fontSize, globalDefaults.defaultFontSize);
6982
+ var style = valueOrDefault(options.fontStyle, globalDefaults.defaultFontStyle);
6983
+ var family = valueOrDefault(options.fontFamily, globalDefaults.defaultFontFamily);
6984
+
6985
+ return {
6986
+ size: size,
6987
+ style: style,
6988
+ family: family,
6989
+ font: helpers.fontString(size, style, family)
6990
+ };
6991
+ }
6992
+
6993
+ function parseLineHeight(options) {
6994
+ return helpers.options.toLineHeight(
6995
+ helpers.valueOrDefault(options.lineHeight, 1.2),
6996
+ helpers.valueOrDefault(options.fontSize, defaults.global.defaultFontSize));
6997
+ }
6998
+
6999
+ Chart.Scale = Element.extend({
7000
+ /**
7001
+ * Get the padding needed for the scale
7002
+ * @method getPadding
7003
+ * @private
7004
+ * @returns {Padding} the necessary padding
7005
+ */
7006
+ getPadding: function() {
7007
+ var me = this;
7008
+ return {
7009
+ left: me.paddingLeft || 0,
7010
+ top: me.paddingTop || 0,
7011
+ right: me.paddingRight || 0,
7012
+ bottom: me.paddingBottom || 0
7013
+ };
7014
+ },
7015
+
7016
+ /**
7017
+ * Returns the scale tick objects ({label, major})
7018
+ * @since 2.7
7019
+ */
7020
+ getTicks: function() {
7021
+ return this._ticks;
7022
+ },
7023
+
7024
+ // These methods are ordered by lifecyle. Utilities then follow.
7025
+ // Any function defined here is inherited by all scale types.
7026
+ // Any function can be extended by the scale type
7027
+
7028
+ mergeTicksOptions: function() {
7029
+ var ticks = this.options.ticks;
7030
+ if (ticks.minor === false) {
7031
+ ticks.minor = {
7032
+ display: false
7033
+ };
7034
+ }
7035
+ if (ticks.major === false) {
7036
+ ticks.major = {
7037
+ display: false
7038
+ };
7039
+ }
7040
+ for (var key in ticks) {
7041
+ if (key !== 'major' && key !== 'minor') {
7042
+ if (typeof ticks.minor[key] === 'undefined') {
7043
+ ticks.minor[key] = ticks[key];
7044
+ }
7045
+ if (typeof ticks.major[key] === 'undefined') {
7046
+ ticks.major[key] = ticks[key];
7047
+ }
7048
+ }
7049
+ }
7050
+ },
7051
+ beforeUpdate: function() {
7052
+ helpers.callback(this.options.beforeUpdate, [this]);
7053
+ },
7054
+ update: function(maxWidth, maxHeight, margins) {
7055
+ var me = this;
7056
+ var i, ilen, labels, label, ticks, tick;
7057
+
7058
+ // Update Lifecycle - Probably don't want to ever extend or overwrite this function ;)
7059
+ me.beforeUpdate();
7060
+
7061
+ // Absorb the master measurements
7062
+ me.maxWidth = maxWidth;
7063
+ me.maxHeight = maxHeight;
7064
+ me.margins = helpers.extend({
7065
+ left: 0,
7066
+ right: 0,
7067
+ top: 0,
7068
+ bottom: 0
7069
+ }, margins);
7070
+ me.longestTextCache = me.longestTextCache || {};
7071
+
7072
+ // Dimensions
7073
+ me.beforeSetDimensions();
7074
+ me.setDimensions();
7075
+ me.afterSetDimensions();
7076
+
7077
+ // Data min/max
7078
+ me.beforeDataLimits();
7079
+ me.determineDataLimits();
7080
+ me.afterDataLimits();
7081
+
7082
+ // Ticks - `this.ticks` is now DEPRECATED!
7083
+ // Internal ticks are now stored as objects in the PRIVATE `this._ticks` member
7084
+ // and must not be accessed directly from outside this class. `this.ticks` being
7085
+ // around for long time and not marked as private, we can't change its structure
7086
+ // without unexpected breaking changes. If you need to access the scale ticks,
7087
+ // use scale.getTicks() instead.
7088
+
7089
+ me.beforeBuildTicks();
7090
+
7091
+ // New implementations should return an array of objects but for BACKWARD COMPAT,
7092
+ // we still support no return (`this.ticks` internally set by calling this method).
7093
+ ticks = me.buildTicks() || [];
7094
+
7095
+ me.afterBuildTicks();
7096
+
7097
+ me.beforeTickToLabelConversion();
7098
+
7099
+ // New implementations should return the formatted tick labels but for BACKWARD
7100
+ // COMPAT, we still support no return (`this.ticks` internally changed by calling
7101
+ // this method and supposed to contain only string values).
7102
+ labels = me.convertTicksToLabels(ticks) || me.ticks;
7103
+
7104
+ me.afterTickToLabelConversion();
7105
+
7106
+ me.ticks = labels; // BACKWARD COMPATIBILITY
7107
+
7108
+ // IMPORTANT: from this point, we consider that `this.ticks` will NEVER change!
7109
+
7110
+ // BACKWARD COMPAT: synchronize `_ticks` with labels (so potentially `this.ticks`)
7111
+ for (i = 0, ilen = labels.length; i < ilen; ++i) {
7112
+ label = labels[i];
7113
+ tick = ticks[i];
7114
+ if (!tick) {
7115
+ ticks.push(tick = {
7116
+ label: label,
7117
+ major: false
7118
+ });
7119
+ } else {
7120
+ tick.label = label;
7121
+ }
7122
+ }
7123
+
7124
+ me._ticks = ticks;
7125
+
7126
+ // Tick Rotation
7127
+ me.beforeCalculateTickRotation();
7128
+ me.calculateTickRotation();
7129
+ me.afterCalculateTickRotation();
7130
+ // Fit
7131
+ me.beforeFit();
7132
+ me.fit();
7133
+ me.afterFit();
7134
+ //
7135
+ me.afterUpdate();
7136
+
7137
+ return me.minSize;
7138
+
7139
+ },
7140
+ afterUpdate: function() {
7141
+ helpers.callback(this.options.afterUpdate, [this]);
7142
+ },
7143
+
7144
+ //
7145
+
7146
+ beforeSetDimensions: function() {
7147
+ helpers.callback(this.options.beforeSetDimensions, [this]);
7148
+ },
7149
+ setDimensions: function() {
7150
+ var me = this;
7151
+ // Set the unconstrained dimension before label rotation
7152
+ if (me.isHorizontal()) {
7153
+ // Reset position before calculating rotation
7154
+ me.width = me.maxWidth;
7155
+ me.left = 0;
7156
+ me.right = me.width;
7157
+ } else {
7158
+ me.height = me.maxHeight;
7159
+
7160
+ // Reset position before calculating rotation
7161
+ me.top = 0;
7162
+ me.bottom = me.height;
7163
+ }
7164
+
7165
+ // Reset padding
7166
+ me.paddingLeft = 0;
7167
+ me.paddingTop = 0;
7168
+ me.paddingRight = 0;
7169
+ me.paddingBottom = 0;
7170
+ },
7171
+ afterSetDimensions: function() {
7172
+ helpers.callback(this.options.afterSetDimensions, [this]);
7173
+ },
7174
+
7175
+ // Data limits
7176
+ beforeDataLimits: function() {
7177
+ helpers.callback(this.options.beforeDataLimits, [this]);
7178
+ },
7179
+ determineDataLimits: helpers.noop,
7180
+ afterDataLimits: function() {
7181
+ helpers.callback(this.options.afterDataLimits, [this]);
7182
+ },
7183
+
7184
+ //
7185
+ beforeBuildTicks: function() {
7186
+ helpers.callback(this.options.beforeBuildTicks, [this]);
7187
+ },
7188
+ buildTicks: helpers.noop,
7189
+ afterBuildTicks: function() {
7190
+ helpers.callback(this.options.afterBuildTicks, [this]);
7191
+ },
7192
+
7193
+ beforeTickToLabelConversion: function() {
7194
+ helpers.callback(this.options.beforeTickToLabelConversion, [this]);
7195
+ },
7196
+ convertTicksToLabels: function() {
7197
+ var me = this;
7198
+ // Convert ticks to strings
7199
+ var tickOpts = me.options.ticks;
7200
+ me.ticks = me.ticks.map(tickOpts.userCallback || tickOpts.callback, this);
7201
+ },
7202
+ afterTickToLabelConversion: function() {
7203
+ helpers.callback(this.options.afterTickToLabelConversion, [this]);
7204
+ },
7205
+
7206
+ //
7207
+
7208
+ beforeCalculateTickRotation: function() {
7209
+ helpers.callback(this.options.beforeCalculateTickRotation, [this]);
7210
+ },
7211
+ calculateTickRotation: function() {
7212
+ var me = this;
7213
+ var context = me.ctx;
7214
+ var tickOpts = me.options.ticks;
7215
+ var labels = labelsFromTicks(me._ticks);
7216
+
7217
+ // Get the width of each grid by calculating the difference
7218
+ // between x offsets between 0 and 1.
7219
+ var tickFont = parseFontOptions(tickOpts);
7220
+ context.font = tickFont.font;
7221
+
7222
+ var labelRotation = tickOpts.minRotation || 0;
7223
+
7224
+ if (labels.length && me.options.display && me.isHorizontal()) {
7225
+ var originalLabelWidth = helpers.longestText(context, tickFont.font, labels, me.longestTextCache);
7226
+ var labelWidth = originalLabelWidth;
7227
+ var cosRotation, sinRotation;
7228
+
7229
+ // Allow 3 pixels x2 padding either side for label readability
7230
+ var tickWidth = me.getPixelForTick(1) - me.getPixelForTick(0) - 6;
7231
+
7232
+ // Max label rotation can be set or default to 90 - also act as a loop counter
7233
+ while (labelWidth > tickWidth && labelRotation < tickOpts.maxRotation) {
7234
+ var angleRadians = helpers.toRadians(labelRotation);
7235
+ cosRotation = Math.cos(angleRadians);
7236
+ sinRotation = Math.sin(angleRadians);
7237
+
7238
+ if (sinRotation * originalLabelWidth > me.maxHeight) {
7239
+ // go back one step
7240
+ labelRotation--;
7241
+ break;
7242
+ }
7243
+
7244
+ labelRotation++;
7245
+ labelWidth = cosRotation * originalLabelWidth;
7246
+ }
7247
+ }
7248
+
7249
+ me.labelRotation = labelRotation;
7250
+ },
7251
+ afterCalculateTickRotation: function() {
7252
+ helpers.callback(this.options.afterCalculateTickRotation, [this]);
7253
+ },
7254
+
7255
+ //
7256
+
7257
+ beforeFit: function() {
7258
+ helpers.callback(this.options.beforeFit, [this]);
7259
+ },
7260
+ fit: function() {
7261
+ var me = this;
7262
+ // Reset
7263
+ var minSize = me.minSize = {
7264
+ width: 0,
7265
+ height: 0
7266
+ };
7267
+
7268
+ var labels = labelsFromTicks(me._ticks);
7269
+
7270
+ var opts = me.options;
7271
+ var tickOpts = opts.ticks;
7272
+ var scaleLabelOpts = opts.scaleLabel;
7273
+ var gridLineOpts = opts.gridLines;
7274
+ var display = opts.display;
7275
+ var isHorizontal = me.isHorizontal();
7276
+
7277
+ var tickFont = parseFontOptions(tickOpts);
7278
+ var tickMarkLength = opts.gridLines.tickMarkLength;
7279
+
7280
+ // Width
7281
+ if (isHorizontal) {
7282
+ // subtract the margins to line up with the chartArea if we are a full width scale
7283
+ minSize.width = me.isFullWidth() ? me.maxWidth - me.margins.left - me.margins.right : me.maxWidth;
7284
+ } else {
7285
+ minSize.width = display && gridLineOpts.drawTicks ? tickMarkLength : 0;
7286
+ }
7287
+
7288
+ // height
7289
+ if (isHorizontal) {
7290
+ minSize.height = display && gridLineOpts.drawTicks ? tickMarkLength : 0;
7291
+ } else {
7292
+ minSize.height = me.maxHeight; // fill all the height
7293
+ }
7294
+
7295
+ // Are we showing a title for the scale?
7296
+ if (scaleLabelOpts.display && display) {
7297
+ var scaleLabelLineHeight = parseLineHeight(scaleLabelOpts);
7298
+ var scaleLabelPadding = helpers.options.toPadding(scaleLabelOpts.padding);
7299
+ var deltaHeight = scaleLabelLineHeight + scaleLabelPadding.height;
7300
+
7301
+ if (isHorizontal) {
7302
+ minSize.height += deltaHeight;
7303
+ } else {
7304
+ minSize.width += deltaHeight;
7305
+ }
7306
+ }
7307
+
7308
+ // Don't bother fitting the ticks if we are not showing them
7309
+ if (tickOpts.display && display) {
7310
+ var largestTextWidth = helpers.longestText(me.ctx, tickFont.font, labels, me.longestTextCache);
7311
+ var tallestLabelHeightInLines = helpers.numberOfLabelLines(labels);
7312
+ var lineSpace = tickFont.size * 0.5;
7313
+ var tickPadding = me.options.ticks.padding;
7314
+
7315
+ if (isHorizontal) {
7316
+ // A horizontal axis is more constrained by the height.
7317
+ me.longestLabelWidth = largestTextWidth;
7318
+
7319
+ var angleRadians = helpers.toRadians(me.labelRotation);
7320
+ var cosRotation = Math.cos(angleRadians);
7321
+ var sinRotation = Math.sin(angleRadians);
7322
+
7323
+ // TODO - improve this calculation
7324
+ var labelHeight = (sinRotation * largestTextWidth)
7325
+ + (tickFont.size * tallestLabelHeightInLines)
7326
+ + (lineSpace * (tallestLabelHeightInLines - 1))
7327
+ + lineSpace; // padding
7328
+
7329
+ minSize.height = Math.min(me.maxHeight, minSize.height + labelHeight + tickPadding);
7330
+
7331
+ me.ctx.font = tickFont.font;
7332
+ var firstLabelWidth = computeTextSize(me.ctx, labels[0], tickFont.font);
7333
+ var lastLabelWidth = computeTextSize(me.ctx, labels[labels.length - 1], tickFont.font);
7334
+
7335
+ // Ensure that our ticks are always inside the canvas. When rotated, ticks are right aligned
7336
+ // which means that the right padding is dominated by the font height
7337
+ if (me.labelRotation !== 0) {
7338
+ me.paddingLeft = opts.position === 'bottom' ? (cosRotation * firstLabelWidth) + 3 : (cosRotation * lineSpace) + 3; // add 3 px to move away from canvas edges
7339
+ me.paddingRight = opts.position === 'bottom' ? (cosRotation * lineSpace) + 3 : (cosRotation * lastLabelWidth) + 3;
7340
+ } else {
7341
+ me.paddingLeft = firstLabelWidth / 2 + 3; // add 3 px to move away from canvas edges
7342
+ me.paddingRight = lastLabelWidth / 2 + 3;
7343
+ }
7344
+ } else {
7345
+ // A vertical axis is more constrained by the width. Labels are the
7346
+ // dominant factor here, so get that length first and account for padding
7347
+ if (tickOpts.mirror) {
7348
+ largestTextWidth = 0;
7349
+ } else {
7350
+ // use lineSpace for consistency with horizontal axis
7351
+ // tickPadding is not implemented for horizontal
7352
+ largestTextWidth += tickPadding + lineSpace;
7353
+ }
7354
+
7355
+ minSize.width = Math.min(me.maxWidth, minSize.width + largestTextWidth);
7356
+
7357
+ me.paddingTop = tickFont.size / 2;
7358
+ me.paddingBottom = tickFont.size / 2;
7359
+ }
7360
+ }
7361
+
7362
+ me.handleMargins();
7363
+
7364
+ me.width = minSize.width;
7365
+ me.height = minSize.height;
7366
+ },
7367
+
7368
+ /**
7369
+ * Handle margins and padding interactions
7370
+ * @private
7371
+ */
7372
+ handleMargins: function() {
7373
+ var me = this;
7374
+ if (me.margins) {
7375
+ me.paddingLeft = Math.max(me.paddingLeft - me.margins.left, 0);
7376
+ me.paddingTop = Math.max(me.paddingTop - me.margins.top, 0);
7377
+ me.paddingRight = Math.max(me.paddingRight - me.margins.right, 0);
7378
+ me.paddingBottom = Math.max(me.paddingBottom - me.margins.bottom, 0);
7379
+ }
7380
+ },
7381
+
7382
+ afterFit: function() {
7383
+ helpers.callback(this.options.afterFit, [this]);
7384
+ },
7385
+
7386
+ // Shared Methods
7387
+ isHorizontal: function() {
7388
+ return this.options.position === 'top' || this.options.position === 'bottom';
7389
+ },
7390
+ isFullWidth: function() {
7391
+ return (this.options.fullWidth);
7392
+ },
7393
+
7394
+ // 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
7395
+ getRightValue: function(rawValue) {
7396
+ // Null and undefined values first
7397
+ if (helpers.isNullOrUndef(rawValue)) {
7398
+ return NaN;
7399
+ }
7400
+ // isNaN(object) returns true, so make sure NaN is checking for a number; Discard Infinite values
7401
+ if (typeof rawValue === 'number' && !isFinite(rawValue)) {
7402
+ return NaN;
7403
+ }
7404
+ // If it is in fact an object, dive in one more level
7405
+ if (rawValue) {
7406
+ if (this.isHorizontal()) {
7407
+ if (rawValue.x !== undefined) {
7408
+ return this.getRightValue(rawValue.x);
7409
+ }
7410
+ } else if (rawValue.y !== undefined) {
7411
+ return this.getRightValue(rawValue.y);
7412
+ }
7413
+ }
7414
+
7415
+ // Value is good, return it
7416
+ return rawValue;
7417
+ },
7418
+
7419
+ /**
7420
+ * Used to get the value to display in the tooltip for the data at the given index
7421
+ * @param index
7422
+ * @param datasetIndex
7423
+ */
7424
+ getLabelForIndex: helpers.noop,
7425
+
7426
+ /**
7427
+ * Returns the location of the given data point. Value can either be an index or a numerical value
7428
+ * The coordinate (0, 0) is at the upper-left corner of the canvas
7429
+ * @param value
7430
+ * @param index
7431
+ * @param datasetIndex
7432
+ */
7433
+ getPixelForValue: helpers.noop,
7434
+
7435
+ /**
7436
+ * Used to get the data value from a given pixel. This is the inverse of getPixelForValue
7437
+ * The coordinate (0, 0) is at the upper-left corner of the canvas
7438
+ * @param pixel
7439
+ */
7440
+ getValueForPixel: helpers.noop,
7441
+
7442
+ /**
7443
+ * Returns the location of the tick at the given index
7444
+ * The coordinate (0, 0) is at the upper-left corner of the canvas
7445
+ */
7446
+ getPixelForTick: function(index) {
7447
+ var me = this;
7448
+ var offset = me.options.offset;
7449
+ if (me.isHorizontal()) {
7450
+ var innerWidth = me.width - (me.paddingLeft + me.paddingRight);
7451
+ var tickWidth = innerWidth / Math.max((me._ticks.length - (offset ? 0 : 1)), 1);
7452
+ var pixel = (tickWidth * index) + me.paddingLeft;
7453
+
7454
+ if (offset) {
7455
+ pixel += tickWidth / 2;
7456
+ }
7457
+
7458
+ var finalVal = me.left + Math.round(pixel);
7459
+ finalVal += me.isFullWidth() ? me.margins.left : 0;
7460
+ return finalVal;
7461
+ }
7462
+ var innerHeight = me.height - (me.paddingTop + me.paddingBottom);
7463
+ return me.top + (index * (innerHeight / (me._ticks.length - 1)));
7464
+ },
7465
+
7466
+ /**
7467
+ * Utility for getting the pixel location of a percentage of scale
7468
+ * The coordinate (0, 0) is at the upper-left corner of the canvas
7469
+ */
7470
+ getPixelForDecimal: function(decimal) {
7471
+ var me = this;
7472
+ if (me.isHorizontal()) {
7473
+ var innerWidth = me.width - (me.paddingLeft + me.paddingRight);
7474
+ var valueOffset = (innerWidth * decimal) + me.paddingLeft;
7475
+
7476
+ var finalVal = me.left + Math.round(valueOffset);
7477
+ finalVal += me.isFullWidth() ? me.margins.left : 0;
7478
+ return finalVal;
7479
+ }
7480
+ return me.top + (decimal * me.height);
7481
+ },
7482
+
7483
+ /**
7484
+ * Returns the pixel for the minimum chart value
7485
+ * The coordinate (0, 0) is at the upper-left corner of the canvas
7486
+ */
7487
+ getBasePixel: function() {
7488
+ return this.getPixelForValue(this.getBaseValue());
7489
+ },
7490
+
7491
+ getBaseValue: function() {
7492
+ var me = this;
7493
+ var min = me.min;
7494
+ var max = me.max;
7495
+
7496
+ return me.beginAtZero ? 0 :
7497
+ min < 0 && max < 0 ? max :
7498
+ min > 0 && max > 0 ? min :
7499
+ 0;
7500
+ },
7501
+
7502
+ /**
7503
+ * Returns a subset of ticks to be plotted to avoid overlapping labels.
7504
+ * @private
7505
+ */
7506
+ _autoSkip: function(ticks) {
7507
+ var skipRatio;
7508
+ var me = this;
7509
+ var isHorizontal = me.isHorizontal();
7510
+ var optionTicks = me.options.ticks.minor;
7511
+ var tickCount = ticks.length;
7512
+ var labelRotationRadians = helpers.toRadians(me.labelRotation);
7513
+ var cosRotation = Math.cos(labelRotationRadians);
7514
+ var longestRotatedLabel = me.longestLabelWidth * cosRotation;
7515
+ var result = [];
7516
+ var i, tick, shouldSkip;
7517
+
7518
+ // figure out the maximum number of gridlines to show
7519
+ var maxTicks;
7520
+ if (optionTicks.maxTicksLimit) {
7521
+ maxTicks = optionTicks.maxTicksLimit;
7522
+ }
7523
+
7524
+ if (isHorizontal) {
7525
+ skipRatio = false;
7526
+
7527
+ if ((longestRotatedLabel + optionTicks.autoSkipPadding) * tickCount > (me.width - (me.paddingLeft + me.paddingRight))) {
7528
+ skipRatio = 1 + Math.floor(((longestRotatedLabel + optionTicks.autoSkipPadding) * tickCount) / (me.width - (me.paddingLeft + me.paddingRight)));
7529
+ }
7530
+
7531
+ // if they defined a max number of optionTicks,
7532
+ // increase skipRatio until that number is met
7533
+ if (maxTicks && tickCount > maxTicks) {
7534
+ skipRatio = Math.max(skipRatio, Math.floor(tickCount / maxTicks));
7535
+ }
7536
+ }
7537
+
7538
+ for (i = 0; i < tickCount; i++) {
7539
+ tick = ticks[i];
7540
+
7541
+ // Since we always show the last tick,we need may need to hide the last shown one before
7542
+ shouldSkip = (skipRatio > 1 && i % skipRatio > 0) || (i % skipRatio === 0 && i + skipRatio >= tickCount);
7543
+ if (shouldSkip && i !== tickCount - 1) {
7544
+ // leave tick in place but make sure it's not displayed (#4635)
7545
+ delete tick.label;
7546
+ }
7547
+ result.push(tick);
7548
+ }
7549
+ return result;
7550
+ },
7551
+
7552
+ // Actually draw the scale on the canvas
7553
+ // @param {rectangle} chartArea : the area of the chart to draw full grid lines on
7554
+ draw: function(chartArea) {
7555
+ var me = this;
7556
+ var options = me.options;
7557
+ if (!options.display) {
7558
+ return;
7559
+ }
7560
+
7561
+ var context = me.ctx;
7562
+ var globalDefaults = defaults.global;
7563
+ var optionTicks = options.ticks.minor;
7564
+ var optionMajorTicks = options.ticks.major || optionTicks;
7565
+ var gridLines = options.gridLines;
7566
+ var scaleLabel = options.scaleLabel;
7567
+
7568
+ var isRotated = me.labelRotation !== 0;
7569
+ var isHorizontal = me.isHorizontal();
7570
+
7571
+ var ticks = optionTicks.autoSkip ? me._autoSkip(me.getTicks()) : me.getTicks();
7572
+ var tickFontColor = helpers.valueOrDefault(optionTicks.fontColor, globalDefaults.defaultFontColor);
7573
+ var tickFont = parseFontOptions(optionTicks);
7574
+ var majorTickFontColor = helpers.valueOrDefault(optionMajorTicks.fontColor, globalDefaults.defaultFontColor);
7575
+ var majorTickFont = parseFontOptions(optionMajorTicks);
7576
+
7577
+ var tl = gridLines.drawTicks ? gridLines.tickMarkLength : 0;
7578
+
7579
+ var scaleLabelFontColor = helpers.valueOrDefault(scaleLabel.fontColor, globalDefaults.defaultFontColor);
7580
+ var scaleLabelFont = parseFontOptions(scaleLabel);
7581
+ var scaleLabelPadding = helpers.options.toPadding(scaleLabel.padding);
7582
+ var labelRotationRadians = helpers.toRadians(me.labelRotation);
7583
+
7584
+ var itemsToDraw = [];
7585
+
7586
+ var xTickStart = options.position === 'right' ? me.left : me.right - tl;
7587
+ var xTickEnd = options.position === 'right' ? me.left + tl : me.right;
7588
+ var yTickStart = options.position === 'bottom' ? me.top : me.bottom - tl;
7589
+ var yTickEnd = options.position === 'bottom' ? me.top + tl : me.bottom;
7590
+
7591
+ helpers.each(ticks, function(tick, index) {
7592
+ // autoskipper skipped this tick (#4635)
7593
+ if (helpers.isNullOrUndef(tick.label)) {
7594
+ return;
7595
+ }
7596
+
7597
+ var label = tick.label;
7598
+ var lineWidth, lineColor, borderDash, borderDashOffset;
7599
+ if (index === me.zeroLineIndex && options.offset === gridLines.offsetGridLines) {
7600
+ // Draw the first index specially
7601
+ lineWidth = gridLines.zeroLineWidth;
7602
+ lineColor = gridLines.zeroLineColor;
7603
+ borderDash = gridLines.zeroLineBorderDash;
7604
+ borderDashOffset = gridLines.zeroLineBorderDashOffset;
7605
+ } else {
7606
+ lineWidth = helpers.valueAtIndexOrDefault(gridLines.lineWidth, index);
7607
+ lineColor = helpers.valueAtIndexOrDefault(gridLines.color, index);
7608
+ borderDash = helpers.valueOrDefault(gridLines.borderDash, globalDefaults.borderDash);
7609
+ borderDashOffset = helpers.valueOrDefault(gridLines.borderDashOffset, globalDefaults.borderDashOffset);
7610
+ }
7611
+
7612
+ // Common properties
7613
+ var tx1, ty1, tx2, ty2, x1, y1, x2, y2, labelX, labelY;
7614
+ var textAlign = 'middle';
7615
+ var textBaseline = 'middle';
7616
+ var tickPadding = optionTicks.padding;
7617
+
7618
+ if (isHorizontal) {
7619
+ var labelYOffset = tl + tickPadding;
7620
+
7621
+ if (options.position === 'bottom') {
7622
+ // bottom
7623
+ textBaseline = !isRotated ? 'top' : 'middle';
7624
+ textAlign = !isRotated ? 'center' : 'right';
7625
+ labelY = me.top + labelYOffset;
7626
+ } else {
7627
+ // top
7628
+ textBaseline = !isRotated ? 'bottom' : 'middle';
7629
+ textAlign = !isRotated ? 'center' : 'left';
7630
+ labelY = me.bottom - labelYOffset;
7631
+ }
7632
+
7633
+ var xLineValue = getLineValue(me, index, gridLines.offsetGridLines && ticks.length > 1);
7634
+ if (xLineValue < me.left) {
7635
+ lineColor = 'rgba(0,0,0,0)';
7636
+ }
7637
+ xLineValue += helpers.aliasPixel(lineWidth);
7638
+
7639
+ labelX = me.getPixelForTick(index) + optionTicks.labelOffset; // x values for optionTicks (need to consider offsetLabel option)
7640
+
7641
+ tx1 = tx2 = x1 = x2 = xLineValue;
7642
+ ty1 = yTickStart;
7643
+ ty2 = yTickEnd;
7644
+ y1 = chartArea.top;
7645
+ y2 = chartArea.bottom;
7646
+ } else {
7647
+ var isLeft = options.position === 'left';
7648
+ var labelXOffset;
7649
+
7650
+ if (optionTicks.mirror) {
7651
+ textAlign = isLeft ? 'left' : 'right';
7652
+ labelXOffset = tickPadding;
7653
+ } else {
7654
+ textAlign = isLeft ? 'right' : 'left';
7655
+ labelXOffset = tl + tickPadding;
7656
+ }
7657
+
7658
+ labelX = isLeft ? me.right - labelXOffset : me.left + labelXOffset;
7659
+
7660
+ var yLineValue = getLineValue(me, index, gridLines.offsetGridLines && ticks.length > 1);
7661
+ if (yLineValue < me.top) {
7662
+ lineColor = 'rgba(0,0,0,0)';
7663
+ }
7664
+ yLineValue += helpers.aliasPixel(lineWidth);
7665
+
7666
+ labelY = me.getPixelForTick(index) + optionTicks.labelOffset;
7667
+
7668
+ tx1 = xTickStart;
7669
+ tx2 = xTickEnd;
7670
+ x1 = chartArea.left;
7671
+ x2 = chartArea.right;
7672
+ ty1 = ty2 = y1 = y2 = yLineValue;
7673
+ }
7674
+
7675
+ itemsToDraw.push({
7676
+ tx1: tx1,
7677
+ ty1: ty1,
7678
+ tx2: tx2,
7679
+ ty2: ty2,
7680
+ x1: x1,
7681
+ y1: y1,
7682
+ x2: x2,
7683
+ y2: y2,
7684
+ labelX: labelX,
7685
+ labelY: labelY,
7686
+ glWidth: lineWidth,
7687
+ glColor: lineColor,
7688
+ glBorderDash: borderDash,
7689
+ glBorderDashOffset: borderDashOffset,
7690
+ rotation: -1 * labelRotationRadians,
7691
+ label: label,
7692
+ major: tick.major,
7693
+ textBaseline: textBaseline,
7694
+ textAlign: textAlign
7695
+ });
7696
+ });
7697
+
7698
+ // Draw all of the tick labels, tick marks, and grid lines at the correct places
7699
+ helpers.each(itemsToDraw, function(itemToDraw) {
7700
+ if (gridLines.display) {
7701
+ context.save();
7702
+ context.lineWidth = itemToDraw.glWidth;
7703
+ context.strokeStyle = itemToDraw.glColor;
7704
+ if (context.setLineDash) {
7705
+ context.setLineDash(itemToDraw.glBorderDash);
7706
+ context.lineDashOffset = itemToDraw.glBorderDashOffset;
7707
+ }
7708
+
7709
+ context.beginPath();
7710
+
7711
+ if (gridLines.drawTicks) {
7712
+ context.moveTo(itemToDraw.tx1, itemToDraw.ty1);
7713
+ context.lineTo(itemToDraw.tx2, itemToDraw.ty2);
7714
+ }
7715
+
7716
+ if (gridLines.drawOnChartArea) {
7717
+ context.moveTo(itemToDraw.x1, itemToDraw.y1);
7718
+ context.lineTo(itemToDraw.x2, itemToDraw.y2);
7719
+ }
7720
+
7721
+ context.stroke();
7722
+ context.restore();
7723
+ }
7724
+
7725
+ if (optionTicks.display) {
7726
+ // Make sure we draw text in the correct color and font
7727
+ context.save();
7728
+ context.translate(itemToDraw.labelX, itemToDraw.labelY);
7729
+ context.rotate(itemToDraw.rotation);
7730
+ context.font = itemToDraw.major ? majorTickFont.font : tickFont.font;
7731
+ context.fillStyle = itemToDraw.major ? majorTickFontColor : tickFontColor;
7732
+ context.textBaseline = itemToDraw.textBaseline;
7733
+ context.textAlign = itemToDraw.textAlign;
7734
+
7735
+ var label = itemToDraw.label;
7736
+ if (helpers.isArray(label)) {
7737
+ for (var i = 0, y = 0; i < label.length; ++i) {
7738
+ // We just make sure the multiline element is a string here..
7739
+ context.fillText('' + label[i], 0, y);
7740
+ // apply same lineSpacing as calculated @ L#320
7741
+ y += (tickFont.size * 1.5);
7742
+ }
7743
+ } else {
7744
+ context.fillText(label, 0, 0);
7745
+ }
7746
+ context.restore();
7747
+ }
7748
+ });
7749
+
7750
+ if (scaleLabel.display) {
7751
+ // Draw the scale label
7752
+ var scaleLabelX;
7753
+ var scaleLabelY;
7754
+ var rotation = 0;
7755
+ var halfLineHeight = parseLineHeight(scaleLabel) / 2;
7756
+
7757
+ if (isHorizontal) {
7758
+ scaleLabelX = me.left + ((me.right - me.left) / 2); // midpoint of the width
7759
+ scaleLabelY = options.position === 'bottom'
7760
+ ? me.bottom - halfLineHeight - scaleLabelPadding.bottom
7761
+ : me.top + halfLineHeight + scaleLabelPadding.top;
7762
+ } else {
7763
+ var isLeft = options.position === 'left';
7764
+ scaleLabelX = isLeft
7765
+ ? me.left + halfLineHeight + scaleLabelPadding.top
7766
+ : me.right - halfLineHeight - scaleLabelPadding.top;
7767
+ scaleLabelY = me.top + ((me.bottom - me.top) / 2);
7768
+ rotation = isLeft ? -0.5 * Math.PI : 0.5 * Math.PI;
7769
+ }
7770
+
7771
+ context.save();
7772
+ context.translate(scaleLabelX, scaleLabelY);
7773
+ context.rotate(rotation);
7774
+ context.textAlign = 'center';
7775
+ context.textBaseline = 'middle';
7776
+ context.fillStyle = scaleLabelFontColor; // render in correct colour
7777
+ context.font = scaleLabelFont.font;
7778
+ context.fillText(scaleLabel.labelString, 0, 0);
7779
+ context.restore();
7780
+ }
7781
+
7782
+ if (gridLines.drawBorder) {
7783
+ // Draw the line at the edge of the axis
7784
+ context.lineWidth = helpers.valueAtIndexOrDefault(gridLines.lineWidth, 0);
7785
+ context.strokeStyle = helpers.valueAtIndexOrDefault(gridLines.color, 0);
7786
+ var x1 = me.left;
7787
+ var x2 = me.right;
7788
+ var y1 = me.top;
7789
+ var y2 = me.bottom;
7790
+
7791
+ var aliasPixel = helpers.aliasPixel(context.lineWidth);
7792
+ if (isHorizontal) {
7793
+ y1 = y2 = options.position === 'top' ? me.bottom : me.top;
7794
+ y1 += aliasPixel;
7795
+ y2 += aliasPixel;
7796
+ } else {
7797
+ x1 = x2 = options.position === 'left' ? me.right : me.left;
7798
+ x1 += aliasPixel;
7799
+ x2 += aliasPixel;
7800
+ }
7801
+
7802
+ context.beginPath();
7803
+ context.moveTo(x1, y1);
7804
+ context.lineTo(x2, y2);
7805
+ context.stroke();
7806
+ }
7807
+ }
7808
+ });
7809
+ };
7810
+
7811
+ },{"25":25,"26":26,"34":34,"45":45}],33:[function(require,module,exports){
7812
+ 'use strict';
7813
+
7814
+ var defaults = require(25);
7815
+ var helpers = require(45);
7816
+
7817
+ module.exports = function(Chart) {
7818
+
7819
+ Chart.scaleService = {
7820
+ // Scale registration object. Extensions can register new scale types (such as log or DB scales) and then
7821
+ // use the new chart options to grab the correct scale
7822
+ constructors: {},
7823
+ // Use a registration function so that we can move to an ES6 map when we no longer need to support
7824
+ // old browsers
7825
+
7826
+ // Scale config defaults
7827
+ defaults: {},
7828
+ registerScaleType: function(type, scaleConstructor, scaleDefaults) {
7829
+ this.constructors[type] = scaleConstructor;
7830
+ this.defaults[type] = helpers.clone(scaleDefaults);
7831
+ },
7832
+ getScaleConstructor: function(type) {
7833
+ return this.constructors.hasOwnProperty(type) ? this.constructors[type] : undefined;
7834
+ },
7835
+ getScaleDefaults: function(type) {
7836
+ // Return the scale defaults merged with the global settings so that we always use the latest ones
7837
+ return this.defaults.hasOwnProperty(type) ? helpers.merge({}, [defaults.scale, this.defaults[type]]) : {};
7838
+ },
7839
+ updateScaleDefaults: function(type, additions) {
7840
+ var me = this;
7841
+ if (me.defaults.hasOwnProperty(type)) {
7842
+ me.defaults[type] = helpers.extend(me.defaults[type], additions);
7843
+ }
7844
+ },
7845
+ addScalesToLayout: function(chart) {
7846
+ // Adds each scale to the chart.boxes array to be sized accordingly
7847
+ helpers.each(chart.scales, function(scale) {
7848
+ // Set ILayoutItem parameters for backwards compatibility
7849
+ scale.fullWidth = scale.options.fullWidth;
7850
+ scale.position = scale.options.position;
7851
+ scale.weight = scale.options.weight;
7852
+ Chart.layoutService.addBox(chart, scale);
7853
+ });
7854
+ }
7855
+ };
7856
+ };
7857
+
7858
+ },{"25":25,"45":45}],34:[function(require,module,exports){
7859
+ 'use strict';
7860
+
7861
+ var helpers = require(45);
7862
+
7863
+ /**
7864
+ * Namespace to hold static tick generation functions
7865
+ * @namespace Chart.Ticks
7866
+ */
7867
+ module.exports = {
7868
+ /**
7869
+ * Namespace to hold generators for different types of ticks
7870
+ * @namespace Chart.Ticks.generators
7871
+ */
7872
+ generators: {
7873
+ /**
7874
+ * Interface for the options provided to the numeric tick generator
7875
+ * @interface INumericTickGenerationOptions
7876
+ */
7877
+ /**
7878
+ * The maximum number of ticks to display
7879
+ * @name INumericTickGenerationOptions#maxTicks
7880
+ * @type Number
7881
+ */
7882
+ /**
7883
+ * The distance between each tick.
7884
+ * @name INumericTickGenerationOptions#stepSize
7885
+ * @type Number
7886
+ * @optional
7887
+ */
7888
+ /**
7889
+ * Forced minimum for the ticks. If not specified, the minimum of the data range is used to calculate the tick minimum
7890
+ * @name INumericTickGenerationOptions#min
7891
+ * @type Number
7892
+ * @optional
7893
+ */
7894
+ /**
7895
+ * The maximum value of the ticks. If not specified, the maximum of the data range is used to calculate the tick maximum
7896
+ * @name INumericTickGenerationOptions#max
7897
+ * @type Number
7898
+ * @optional
7899
+ */
7900
+
7901
+ /**
7902
+ * Generate a set of linear ticks
7903
+ * @method Chart.Ticks.generators.linear
7904
+ * @param generationOptions {INumericTickGenerationOptions} the options used to generate the ticks
7905
+ * @param dataRange {IRange} the range of the data
7906
+ * @returns {Array<Number>} array of tick values
7907
+ */
7908
+ linear: function(generationOptions, dataRange) {
7909
+ var ticks = [];
7910
+ // To get a "nice" value for the tick spacing, we will use the appropriately named
7911
+ // "nice number" algorithm. See http://stackoverflow.com/questions/8506881/nice-label-algorithm-for-charts-with-minimum-ticks
7912
+ // for details.
7913
+
7914
+ var spacing;
7915
+ if (generationOptions.stepSize && generationOptions.stepSize > 0) {
7916
+ spacing = generationOptions.stepSize;
7917
+ } else {
7918
+ var niceRange = helpers.niceNum(dataRange.max - dataRange.min, false);
7919
+ spacing = helpers.niceNum(niceRange / (generationOptions.maxTicks - 1), true);
7920
+ }
7921
+ var niceMin = Math.floor(dataRange.min / spacing) * spacing;
7922
+ var niceMax = Math.ceil(dataRange.max / spacing) * spacing;
7923
+
7924
+ // If min, max and stepSize is set and they make an evenly spaced scale use it.
7925
+ if (generationOptions.min && generationOptions.max && generationOptions.stepSize) {
7926
+ // If very close to our whole number, use it.
7927
+ if (helpers.almostWhole((generationOptions.max - generationOptions.min) / generationOptions.stepSize, spacing / 1000)) {
7928
+ niceMin = generationOptions.min;
7929
+ niceMax = generationOptions.max;
7930
+ }
7931
+ }
7932
+
7933
+ var numSpaces = (niceMax - niceMin) / spacing;
7934
+ // If very close to our rounded value, use it.
7935
+ if (helpers.almostEquals(numSpaces, Math.round(numSpaces), spacing / 1000)) {
7936
+ numSpaces = Math.round(numSpaces);
7937
+ } else {
7938
+ numSpaces = Math.ceil(numSpaces);
7939
+ }
7940
+
7941
+ // Put the values into the ticks array
7942
+ ticks.push(generationOptions.min !== undefined ? generationOptions.min : niceMin);
7943
+ for (var j = 1; j < numSpaces; ++j) {
7944
+ ticks.push(niceMin + (j * spacing));
7945
+ }
7946
+ ticks.push(generationOptions.max !== undefined ? generationOptions.max : niceMax);
7947
+
7948
+ return ticks;
7949
+ },
7950
+
7951
+ /**
7952
+ * Generate a set of logarithmic ticks
7953
+ * @method Chart.Ticks.generators.logarithmic
7954
+ * @param generationOptions {INumericTickGenerationOptions} the options used to generate the ticks
7955
+ * @param dataRange {IRange} the range of the data
7956
+ * @returns {Array<Number>} array of tick values
7957
+ */
7958
+ logarithmic: function(generationOptions, dataRange) {
7959
+ var ticks = [];
7960
+ var valueOrDefault = helpers.valueOrDefault;
7961
+
7962
+ // Figure out what the max number of ticks we can support it is based on the size of
7963
+ // the axis area. For now, we say that the minimum tick spacing in pixels must be 50
7964
+ // We also limit the maximum number of ticks to 11 which gives a nice 10 squares on
7965
+ // the graph
7966
+ var tickVal = valueOrDefault(generationOptions.min, Math.pow(10, Math.floor(helpers.log10(dataRange.min))));
7967
+
7968
+ var endExp = Math.floor(helpers.log10(dataRange.max));
7969
+ var endSignificand = Math.ceil(dataRange.max / Math.pow(10, endExp));
7970
+ var exp, significand;
7971
+
7972
+ if (tickVal === 0) {
7973
+ exp = Math.floor(helpers.log10(dataRange.minNotZero));
7974
+ significand = Math.floor(dataRange.minNotZero / Math.pow(10, exp));
7975
+
7976
+ ticks.push(tickVal);
7977
+ tickVal = significand * Math.pow(10, exp);
7978
+ } else {
7979
+ exp = Math.floor(helpers.log10(tickVal));
7980
+ significand = Math.floor(tickVal / Math.pow(10, exp));
7981
+ }
7982
+
7983
+ do {
7984
+ ticks.push(tickVal);
7985
+
7986
+ ++significand;
7987
+ if (significand === 10) {
7988
+ significand = 1;
7989
+ ++exp;
7990
+ }
7991
+
7992
+ tickVal = significand * Math.pow(10, exp);
7993
+ } while (exp < endExp || (exp === endExp && significand < endSignificand));
7994
+
7995
+ var lastTick = valueOrDefault(generationOptions.max, tickVal);
7996
+ ticks.push(lastTick);
7997
+
7998
+ return ticks;
7999
+ }
8000
+ },
8001
+
8002
+ /**
8003
+ * Namespace to hold formatters for different types of ticks
8004
+ * @namespace Chart.Ticks.formatters
8005
+ */
8006
+ formatters: {
8007
+ /**
8008
+ * Formatter for value labels
8009
+ * @method Chart.Ticks.formatters.values
8010
+ * @param value the value to display
8011
+ * @return {String|Array} the label to display
8012
+ */
8013
+ values: function(value) {
8014
+ return helpers.isArray(value) ? value : '' + value;
8015
+ },
8016
+
8017
+ /**
8018
+ * Formatter for linear numeric ticks
8019
+ * @method Chart.Ticks.formatters.linear
8020
+ * @param tickValue {Number} the value to be formatted
8021
+ * @param index {Number} the position of the tickValue parameter in the ticks array
8022
+ * @param ticks {Array<Number>} the list of ticks being converted
8023
+ * @return {String} string representation of the tickValue parameter
8024
+ */
8025
+ linear: function(tickValue, index, ticks) {
8026
+ // If we have lots of ticks, don't use the ones
8027
+ var delta = ticks.length > 3 ? ticks[2] - ticks[1] : ticks[1] - ticks[0];
8028
+
8029
+ // If we have a number like 2.5 as the delta, figure out how many decimal places we need
8030
+ if (Math.abs(delta) > 1) {
8031
+ if (tickValue !== Math.floor(tickValue)) {
8032
+ // not an integer
8033
+ delta = tickValue - Math.floor(tickValue);
8034
+ }
8035
+ }
8036
+
8037
+ var logDelta = helpers.log10(Math.abs(delta));
8038
+ var tickString = '';
8039
+
8040
+ if (tickValue !== 0) {
8041
+ var numDecimal = -1 * Math.floor(logDelta);
8042
+ numDecimal = Math.max(Math.min(numDecimal, 20), 0); // toFixed has a max of 20 decimal places
8043
+ tickString = tickValue.toFixed(numDecimal);
8044
+ } else {
8045
+ tickString = '0'; // never show decimal places for 0
8046
+ }
8047
+
8048
+ return tickString;
8049
+ },
8050
+
8051
+ logarithmic: function(tickValue, index, ticks) {
8052
+ var remain = tickValue / (Math.pow(10, Math.floor(helpers.log10(tickValue))));
8053
+
8054
+ if (tickValue === 0) {
8055
+ return '0';
8056
+ } else if (remain === 1 || remain === 2 || remain === 5 || index === 0 || index === ticks.length - 1) {
8057
+ return tickValue.toExponential();
8058
+ }
8059
+ return '';
8060
+ }
8061
+ }
8062
+ };
8063
+
8064
+ },{"45":45}],35:[function(require,module,exports){
8065
+ 'use strict';
8066
+
8067
+ var defaults = require(25);
8068
+ var Element = require(26);
8069
+ var helpers = require(45);
8070
+
8071
+ defaults._set('global', {
8072
+ tooltips: {
8073
+ enabled: true,
8074
+ custom: null,
8075
+ mode: 'nearest',
8076
+ position: 'average',
8077
+ intersect: true,
8078
+ backgroundColor: 'rgba(0,0,0,0.8)',
8079
+ titleFontStyle: 'bold',
8080
+ titleSpacing: 2,
8081
+ titleMarginBottom: 6,
8082
+ titleFontColor: '#fff',
8083
+ titleAlign: 'left',
8084
+ bodySpacing: 2,
8085
+ bodyFontColor: '#fff',
8086
+ bodyAlign: 'left',
8087
+ footerFontStyle: 'bold',
8088
+ footerSpacing: 2,
8089
+ footerMarginTop: 6,
8090
+ footerFontColor: '#fff',
8091
+ footerAlign: 'left',
8092
+ yPadding: 6,
8093
+ xPadding: 6,
8094
+ caretPadding: 2,
8095
+ caretSize: 5,
8096
+ cornerRadius: 6,
8097
+ multiKeyBackground: '#fff',
8098
+ displayColors: true,
8099
+ borderColor: 'rgba(0,0,0,0)',
8100
+ borderWidth: 0,
8101
+ callbacks: {
8102
+ // Args are: (tooltipItems, data)
8103
+ beforeTitle: helpers.noop,
8104
+ title: function(tooltipItems, data) {
8105
+ // Pick first xLabel for now
8106
+ var title = '';
8107
+ var labels = data.labels;
8108
+ var labelCount = labels ? labels.length : 0;
8109
+
8110
+ if (tooltipItems.length > 0) {
8111
+ var item = tooltipItems[0];
8112
+
8113
+ if (item.xLabel) {
8114
+ title = item.xLabel;
8115
+ } else if (labelCount > 0 && item.index < labelCount) {
8116
+ title = labels[item.index];
8117
+ }
8118
+ }
8119
+
8120
+ return title;
8121
+ },
8122
+ afterTitle: helpers.noop,
8123
+
8124
+ // Args are: (tooltipItems, data)
8125
+ beforeBody: helpers.noop,
8126
+
8127
+ // Args are: (tooltipItem, data)
8128
+ beforeLabel: helpers.noop,
8129
+ label: function(tooltipItem, data) {
8130
+ var label = data.datasets[tooltipItem.datasetIndex].label || '';
8131
+
8132
+ if (label) {
8133
+ label += ': ';
8134
+ }
8135
+ label += tooltipItem.yLabel;
8136
+ return label;
8137
+ },
8138
+ labelColor: function(tooltipItem, chart) {
8139
+ var meta = chart.getDatasetMeta(tooltipItem.datasetIndex);
8140
+ var activeElement = meta.data[tooltipItem.index];
8141
+ var view = activeElement._view;
8142
+ return {
8143
+ borderColor: view.borderColor,
8144
+ backgroundColor: view.backgroundColor
8145
+ };
8146
+ },
8147
+ labelTextColor: function() {
8148
+ return this._options.bodyFontColor;
8149
+ },
8150
+ afterLabel: helpers.noop,
8151
+
8152
+ // Args are: (tooltipItems, data)
8153
+ afterBody: helpers.noop,
8154
+
8155
+ // Args are: (tooltipItems, data)
8156
+ beforeFooter: helpers.noop,
8157
+ footer: helpers.noop,
8158
+ afterFooter: helpers.noop
8159
+ }
8160
+ }
8161
+ });
8162
+
8163
+ module.exports = function(Chart) {
8164
+
8165
+ /**
8166
+ * Helper method to merge the opacity into a color
8167
+ */
8168
+ function mergeOpacity(colorString, opacity) {
8169
+ var color = helpers.color(colorString);
8170
+ return color.alpha(opacity * color.alpha()).rgbaString();
8171
+ }
8172
+
8173
+ // Helper to push or concat based on if the 2nd parameter is an array or not
8174
+ function pushOrConcat(base, toPush) {
8175
+ if (toPush) {
8176
+ if (helpers.isArray(toPush)) {
8177
+ // base = base.concat(toPush);
8178
+ Array.prototype.push.apply(base, toPush);
8179
+ } else {
8180
+ base.push(toPush);
8181
+ }
8182
+ }
8183
+
8184
+ return base;
8185
+ }
8186
+
8187
+ // Private helper to create a tooltip item model
8188
+ // @param element : the chart element (point, arc, bar) to create the tooltip item for
8189
+ // @return : new tooltip item
8190
+ function createTooltipItem(element) {
8191
+ var xScale = element._xScale;
8192
+ var yScale = element._yScale || element._scale; // handle radar || polarArea charts
8193
+ var index = element._index;
8194
+ var datasetIndex = element._datasetIndex;
8195
+
8196
+ return {
8197
+ xLabel: xScale ? xScale.getLabelForIndex(index, datasetIndex) : '',
8198
+ yLabel: yScale ? yScale.getLabelForIndex(index, datasetIndex) : '',
8199
+ index: index,
8200
+ datasetIndex: datasetIndex,
8201
+ x: element._model.x,
8202
+ y: element._model.y
8203
+ };
8204
+ }
8205
+
8206
+ /**
8207
+ * Helper to get the reset model for the tooltip
8208
+ * @param tooltipOpts {Object} the tooltip options
8209
+ */
8210
+ function getBaseModel(tooltipOpts) {
8211
+ var globalDefaults = defaults.global;
8212
+ var valueOrDefault = helpers.valueOrDefault;
8213
+
8214
+ return {
8215
+ // Positioning
8216
+ xPadding: tooltipOpts.xPadding,
8217
+ yPadding: tooltipOpts.yPadding,
8218
+ xAlign: tooltipOpts.xAlign,
8219
+ yAlign: tooltipOpts.yAlign,
8220
+
8221
+ // Body
8222
+ bodyFontColor: tooltipOpts.bodyFontColor,
8223
+ _bodyFontFamily: valueOrDefault(tooltipOpts.bodyFontFamily, globalDefaults.defaultFontFamily),
8224
+ _bodyFontStyle: valueOrDefault(tooltipOpts.bodyFontStyle, globalDefaults.defaultFontStyle),
8225
+ _bodyAlign: tooltipOpts.bodyAlign,
8226
+ bodyFontSize: valueOrDefault(tooltipOpts.bodyFontSize, globalDefaults.defaultFontSize),
8227
+ bodySpacing: tooltipOpts.bodySpacing,
8228
+
8229
+ // Title
8230
+ titleFontColor: tooltipOpts.titleFontColor,
8231
+ _titleFontFamily: valueOrDefault(tooltipOpts.titleFontFamily, globalDefaults.defaultFontFamily),
8232
+ _titleFontStyle: valueOrDefault(tooltipOpts.titleFontStyle, globalDefaults.defaultFontStyle),
8233
+ titleFontSize: valueOrDefault(tooltipOpts.titleFontSize, globalDefaults.defaultFontSize),
8234
+ _titleAlign: tooltipOpts.titleAlign,
8235
+ titleSpacing: tooltipOpts.titleSpacing,
8236
+ titleMarginBottom: tooltipOpts.titleMarginBottom,
8237
+
8238
+ // Footer
8239
+ footerFontColor: tooltipOpts.footerFontColor,
8240
+ _footerFontFamily: valueOrDefault(tooltipOpts.footerFontFamily, globalDefaults.defaultFontFamily),
8241
+ _footerFontStyle: valueOrDefault(tooltipOpts.footerFontStyle, globalDefaults.defaultFontStyle),
8242
+ footerFontSize: valueOrDefault(tooltipOpts.footerFontSize, globalDefaults.defaultFontSize),
8243
+ _footerAlign: tooltipOpts.footerAlign,
8244
+ footerSpacing: tooltipOpts.footerSpacing,
8245
+ footerMarginTop: tooltipOpts.footerMarginTop,
8246
+
8247
+ // Appearance
8248
+ caretSize: tooltipOpts.caretSize,
8249
+ cornerRadius: tooltipOpts.cornerRadius,
8250
+ backgroundColor: tooltipOpts.backgroundColor,
8251
+ opacity: 0,
8252
+ legendColorBackground: tooltipOpts.multiKeyBackground,
8253
+ displayColors: tooltipOpts.displayColors,
8254
+ borderColor: tooltipOpts.borderColor,
8255
+ borderWidth: tooltipOpts.borderWidth
8256
+ };
8257
+ }
8258
+
8259
+ /**
8260
+ * Get the size of the tooltip
8261
+ */
8262
+ function getTooltipSize(tooltip, model) {
8263
+ var ctx = tooltip._chart.ctx;
8264
+
8265
+ var height = model.yPadding * 2; // Tooltip Padding
8266
+ var width = 0;
8267
+
8268
+ // Count of all lines in the body
8269
+ var body = model.body;
8270
+ var combinedBodyLength = body.reduce(function(count, bodyItem) {
8271
+ return count + bodyItem.before.length + bodyItem.lines.length + bodyItem.after.length;
8272
+ }, 0);
8273
+ combinedBodyLength += model.beforeBody.length + model.afterBody.length;
8274
+
8275
+ var titleLineCount = model.title.length;
8276
+ var footerLineCount = model.footer.length;
8277
+ var titleFontSize = model.titleFontSize;
8278
+ var bodyFontSize = model.bodyFontSize;
8279
+ var footerFontSize = model.footerFontSize;
8280
+
8281
+ height += titleLineCount * titleFontSize; // Title Lines
8282
+ height += titleLineCount ? (titleLineCount - 1) * model.titleSpacing : 0; // Title Line Spacing
8283
+ height += titleLineCount ? model.titleMarginBottom : 0; // Title's bottom Margin
8284
+ height += combinedBodyLength * bodyFontSize; // Body Lines
8285
+ height += combinedBodyLength ? (combinedBodyLength - 1) * model.bodySpacing : 0; // Body Line Spacing
8286
+ height += footerLineCount ? model.footerMarginTop : 0; // Footer Margin
8287
+ height += footerLineCount * (footerFontSize); // Footer Lines
8288
+ height += footerLineCount ? (footerLineCount - 1) * model.footerSpacing : 0; // Footer Line Spacing
8289
+
8290
+ // Title width
8291
+ var widthPadding = 0;
8292
+ var maxLineWidth = function(line) {
8293
+ width = Math.max(width, ctx.measureText(line).width + widthPadding);
8294
+ };
8295
+
8296
+ ctx.font = helpers.fontString(titleFontSize, model._titleFontStyle, model._titleFontFamily);
8297
+ helpers.each(model.title, maxLineWidth);
8298
+
8299
+ // Body width
8300
+ ctx.font = helpers.fontString(bodyFontSize, model._bodyFontStyle, model._bodyFontFamily);
8301
+ helpers.each(model.beforeBody.concat(model.afterBody), maxLineWidth);
8302
+
8303
+ // Body lines may include some extra width due to the color box
8304
+ widthPadding = model.displayColors ? (bodyFontSize + 2) : 0;
8305
+ helpers.each(body, function(bodyItem) {
8306
+ helpers.each(bodyItem.before, maxLineWidth);
8307
+ helpers.each(bodyItem.lines, maxLineWidth);
8308
+ helpers.each(bodyItem.after, maxLineWidth);
8309
+ });
8310
+
8311
+ // Reset back to 0
8312
+ widthPadding = 0;
8313
+
8314
+ // Footer width
8315
+ ctx.font = helpers.fontString(footerFontSize, model._footerFontStyle, model._footerFontFamily);
8316
+ helpers.each(model.footer, maxLineWidth);
8317
+
8318
+ // Add padding
8319
+ width += 2 * model.xPadding;
8320
+
8321
+ return {
8322
+ width: width,
8323
+ height: height
8324
+ };
8325
+ }
8326
+
8327
+ /**
8328
+ * Helper to get the alignment of a tooltip given the size
8329
+ */
8330
+ function determineAlignment(tooltip, size) {
8331
+ var model = tooltip._model;
8332
+ var chart = tooltip._chart;
8333
+ var chartArea = tooltip._chart.chartArea;
8334
+ var xAlign = 'center';
8335
+ var yAlign = 'center';
8336
+
8337
+ if (model.y < size.height) {
8338
+ yAlign = 'top';
8339
+ } else if (model.y > (chart.height - size.height)) {
8340
+ yAlign = 'bottom';
8341
+ }
8342
+
8343
+ var lf, rf; // functions to determine left, right alignment
8344
+ var olf, orf; // functions to determine if left/right alignment causes tooltip to go outside chart
8345
+ var yf; // function to get the y alignment if the tooltip goes outside of the left or right edges
8346
+ var midX = (chartArea.left + chartArea.right) / 2;
8347
+ var midY = (chartArea.top + chartArea.bottom) / 2;
8348
+
8349
+ if (yAlign === 'center') {
8350
+ lf = function(x) {
8351
+ return x <= midX;
8352
+ };
8353
+ rf = function(x) {
8354
+ return x > midX;
8355
+ };
8356
+ } else {
8357
+ lf = function(x) {
8358
+ return x <= (size.width / 2);
8359
+ };
8360
+ rf = function(x) {
8361
+ return x >= (chart.width - (size.width / 2));
8362
+ };
8363
+ }
8364
+
8365
+ olf = function(x) {
8366
+ return x + size.width > chart.width;
8367
+ };
8368
+ orf = function(x) {
8369
+ return x - size.width < 0;
8370
+ };
8371
+ yf = function(y) {
8372
+ return y <= midY ? 'top' : 'bottom';
8373
+ };
8374
+
8375
+ if (lf(model.x)) {
8376
+ xAlign = 'left';
8377
+
8378
+ // Is tooltip too wide and goes over the right side of the chart.?
8379
+ if (olf(model.x)) {
8380
+ xAlign = 'center';
8381
+ yAlign = yf(model.y);
8382
+ }
8383
+ } else if (rf(model.x)) {
8384
+ xAlign = 'right';
8385
+
8386
+ // Is tooltip too wide and goes outside left edge of canvas?
8387
+ if (orf(model.x)) {
8388
+ xAlign = 'center';
8389
+ yAlign = yf(model.y);
8390
+ }
8391
+ }
8392
+
8393
+ var opts = tooltip._options;
8394
+ return {
8395
+ xAlign: opts.xAlign ? opts.xAlign : xAlign,
8396
+ yAlign: opts.yAlign ? opts.yAlign : yAlign
8397
+ };
8398
+ }
8399
+
8400
+ /**
8401
+ * @Helper to get the location a tooltip needs to be placed at given the initial position (via the vm) and the size and alignment
8402
+ */
8403
+ function getBackgroundPoint(vm, size, alignment) {
8404
+ // Background Position
8405
+ var x = vm.x;
8406
+ var y = vm.y;
8407
+
8408
+ var caretSize = vm.caretSize;
8409
+ var caretPadding = vm.caretPadding;
8410
+ var cornerRadius = vm.cornerRadius;
8411
+ var xAlign = alignment.xAlign;
8412
+ var yAlign = alignment.yAlign;
8413
+ var paddingAndSize = caretSize + caretPadding;
8414
+ var radiusAndPadding = cornerRadius + caretPadding;
8415
+
8416
+ if (xAlign === 'right') {
8417
+ x -= size.width;
8418
+ } else if (xAlign === 'center') {
8419
+ x -= (size.width / 2);
8420
+ }
8421
+
8422
+ if (yAlign === 'top') {
8423
+ y += paddingAndSize;
8424
+ } else if (yAlign === 'bottom') {
8425
+ y -= size.height + paddingAndSize;
8426
+ } else {
8427
+ y -= (size.height / 2);
8428
+ }
8429
+
8430
+ if (yAlign === 'center') {
8431
+ if (xAlign === 'left') {
8432
+ x += paddingAndSize;
8433
+ } else if (xAlign === 'right') {
8434
+ x -= paddingAndSize;
8435
+ }
8436
+ } else if (xAlign === 'left') {
8437
+ x -= radiusAndPadding;
8438
+ } else if (xAlign === 'right') {
8439
+ x += radiusAndPadding;
8440
+ }
8441
+
8442
+ return {
8443
+ x: x,
8444
+ y: y
8445
+ };
8446
+ }
8447
+
8448
+ Chart.Tooltip = Element.extend({
8449
+ initialize: function() {
8450
+ this._model = getBaseModel(this._options);
8451
+ this._lastActive = [];
8452
+ },
8453
+
8454
+ // Get the title
8455
+ // Args are: (tooltipItem, data)
8456
+ getTitle: function() {
8457
+ var me = this;
8458
+ var opts = me._options;
8459
+ var callbacks = opts.callbacks;
8460
+
8461
+ var beforeTitle = callbacks.beforeTitle.apply(me, arguments);
8462
+ var title = callbacks.title.apply(me, arguments);
8463
+ var afterTitle = callbacks.afterTitle.apply(me, arguments);
8464
+
8465
+ var lines = [];
8466
+ lines = pushOrConcat(lines, beforeTitle);
8467
+ lines = pushOrConcat(lines, title);
8468
+ lines = pushOrConcat(lines, afterTitle);
8469
+
8470
+ return lines;
8471
+ },
8472
+
8473
+ // Args are: (tooltipItem, data)
8474
+ getBeforeBody: function() {
8475
+ var lines = this._options.callbacks.beforeBody.apply(this, arguments);
8476
+ return helpers.isArray(lines) ? lines : lines !== undefined ? [lines] : [];
8477
+ },
8478
+
8479
+ // Args are: (tooltipItem, data)
8480
+ getBody: function(tooltipItems, data) {
8481
+ var me = this;
8482
+ var callbacks = me._options.callbacks;
8483
+ var bodyItems = [];
8484
+
8485
+ helpers.each(tooltipItems, function(tooltipItem) {
8486
+ var bodyItem = {
8487
+ before: [],
8488
+ lines: [],
8489
+ after: []
8490
+ };
8491
+ pushOrConcat(bodyItem.before, callbacks.beforeLabel.call(me, tooltipItem, data));
8492
+ pushOrConcat(bodyItem.lines, callbacks.label.call(me, tooltipItem, data));
8493
+ pushOrConcat(bodyItem.after, callbacks.afterLabel.call(me, tooltipItem, data));
8494
+
8495
+ bodyItems.push(bodyItem);
8496
+ });
8497
+
8498
+ return bodyItems;
8499
+ },
8500
+
8501
+ // Args are: (tooltipItem, data)
8502
+ getAfterBody: function() {
8503
+ var lines = this._options.callbacks.afterBody.apply(this, arguments);
8504
+ return helpers.isArray(lines) ? lines : lines !== undefined ? [lines] : [];
8505
+ },
8506
+
8507
+ // Get the footer and beforeFooter and afterFooter lines
8508
+ // Args are: (tooltipItem, data)
8509
+ getFooter: function() {
8510
+ var me = this;
8511
+ var callbacks = me._options.callbacks;
8512
+
8513
+ var beforeFooter = callbacks.beforeFooter.apply(me, arguments);
8514
+ var footer = callbacks.footer.apply(me, arguments);
8515
+ var afterFooter = callbacks.afterFooter.apply(me, arguments);
8516
+
8517
+ var lines = [];
8518
+ lines = pushOrConcat(lines, beforeFooter);
8519
+ lines = pushOrConcat(lines, footer);
8520
+ lines = pushOrConcat(lines, afterFooter);
8521
+
8522
+ return lines;
8523
+ },
8524
+
8525
+ update: function(changed) {
8526
+ var me = this;
8527
+ var opts = me._options;
8528
+
8529
+ // Need to regenerate the model because its faster than using extend and it is necessary due to the optimization in Chart.Element.transition
8530
+ // 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
8531
+ // which breaks any animations.
8532
+ var existingModel = me._model;
8533
+ var model = me._model = getBaseModel(opts);
8534
+ var active = me._active;
8535
+
8536
+ var data = me._data;
8537
+
8538
+ // In the case where active.length === 0 we need to keep these at existing values for good animations
8539
+ var alignment = {
8540
+ xAlign: existingModel.xAlign,
8541
+ yAlign: existingModel.yAlign
8542
+ };
8543
+ var backgroundPoint = {
8544
+ x: existingModel.x,
8545
+ y: existingModel.y
8546
+ };
8547
+ var tooltipSize = {
8548
+ width: existingModel.width,
8549
+ height: existingModel.height
8550
+ };
8551
+ var tooltipPosition = {
8552
+ x: existingModel.caretX,
8553
+ y: existingModel.caretY
8554
+ };
8555
+
8556
+ var i, len;
8557
+
8558
+ if (active.length) {
8559
+ model.opacity = 1;
8560
+
8561
+ var labelColors = [];
8562
+ var labelTextColors = [];
8563
+ tooltipPosition = Chart.Tooltip.positioners[opts.position].call(me, active, me._eventPosition);
8564
+
8565
+ var tooltipItems = [];
8566
+ for (i = 0, len = active.length; i < len; ++i) {
8567
+ tooltipItems.push(createTooltipItem(active[i]));
8568
+ }
8569
+
8570
+ // If the user provided a filter function, use it to modify the tooltip items
8571
+ if (opts.filter) {
8572
+ tooltipItems = tooltipItems.filter(function(a) {
8573
+ return opts.filter(a, data);
8574
+ });
8575
+ }
8576
+
8577
+ // If the user provided a sorting function, use it to modify the tooltip items
8578
+ if (opts.itemSort) {
8579
+ tooltipItems = tooltipItems.sort(function(a, b) {
8580
+ return opts.itemSort(a, b, data);
8581
+ });
8582
+ }
8583
+
8584
+ // Determine colors for boxes
8585
+ helpers.each(tooltipItems, function(tooltipItem) {
8586
+ labelColors.push(opts.callbacks.labelColor.call(me, tooltipItem, me._chart));
8587
+ labelTextColors.push(opts.callbacks.labelTextColor.call(me, tooltipItem, me._chart));
8588
+ });
8589
+
8590
+
8591
+ // Build the Text Lines
8592
+ model.title = me.getTitle(tooltipItems, data);
8593
+ model.beforeBody = me.getBeforeBody(tooltipItems, data);
8594
+ model.body = me.getBody(tooltipItems, data);
8595
+ model.afterBody = me.getAfterBody(tooltipItems, data);
8596
+ model.footer = me.getFooter(tooltipItems, data);
8597
+
8598
+ // Initial positioning and colors
8599
+ model.x = Math.round(tooltipPosition.x);
8600
+ model.y = Math.round(tooltipPosition.y);
8601
+ model.caretPadding = opts.caretPadding;
8602
+ model.labelColors = labelColors;
8603
+ model.labelTextColors = labelTextColors;
8604
+
8605
+ // data points
8606
+ model.dataPoints = tooltipItems;
8607
+
8608
+ // We need to determine alignment of the tooltip
8609
+ tooltipSize = getTooltipSize(this, model);
8610
+ alignment = determineAlignment(this, tooltipSize);
8611
+ // Final Size and Position
8612
+ backgroundPoint = getBackgroundPoint(model, tooltipSize, alignment);
8613
+ } else {
8614
+ model.opacity = 0;
8615
+ }
8616
+
8617
+ model.xAlign = alignment.xAlign;
8618
+ model.yAlign = alignment.yAlign;
8619
+ model.x = backgroundPoint.x;
8620
+ model.y = backgroundPoint.y;
8621
+ model.width = tooltipSize.width;
8622
+ model.height = tooltipSize.height;
8623
+
8624
+ // Point where the caret on the tooltip points to
8625
+ model.caretX = tooltipPosition.x;
8626
+ model.caretY = tooltipPosition.y;
8627
+
8628
+ me._model = model;
8629
+
8630
+ if (changed && opts.custom) {
8631
+ opts.custom.call(me, model);
8632
+ }
8633
+
8634
+ return me;
8635
+ },
8636
+ drawCaret: function(tooltipPoint, size) {
8637
+ var ctx = this._chart.ctx;
8638
+ var vm = this._view;
8639
+ var caretPosition = this.getCaretPosition(tooltipPoint, size, vm);
8640
+
8641
+ ctx.lineTo(caretPosition.x1, caretPosition.y1);
8642
+ ctx.lineTo(caretPosition.x2, caretPosition.y2);
8643
+ ctx.lineTo(caretPosition.x3, caretPosition.y3);
8644
+ },
8645
+ getCaretPosition: function(tooltipPoint, size, vm) {
8646
+ var x1, x2, x3, y1, y2, y3;
8647
+ var caretSize = vm.caretSize;
8648
+ var cornerRadius = vm.cornerRadius;
8649
+ var xAlign = vm.xAlign;
8650
+ var yAlign = vm.yAlign;
8651
+ var ptX = tooltipPoint.x;
8652
+ var ptY = tooltipPoint.y;
8653
+ var width = size.width;
8654
+ var height = size.height;
8655
+
8656
+ if (yAlign === 'center') {
8657
+ y2 = ptY + (height / 2);
8658
+
8659
+ if (xAlign === 'left') {
8660
+ x1 = ptX;
8661
+ x2 = x1 - caretSize;
8662
+ x3 = x1;
8663
+
8664
+ y1 = y2 + caretSize;
8665
+ y3 = y2 - caretSize;
8666
+ } else {
8667
+ x1 = ptX + width;
8668
+ x2 = x1 + caretSize;
8669
+ x3 = x1;
8670
+
8671
+ y1 = y2 - caretSize;
8672
+ y3 = y2 + caretSize;
8673
+ }
8674
+ } else {
8675
+ if (xAlign === 'left') {
8676
+ x2 = ptX + cornerRadius + (caretSize);
8677
+ x1 = x2 - caretSize;
8678
+ x3 = x2 + caretSize;
8679
+ } else if (xAlign === 'right') {
8680
+ x2 = ptX + width - cornerRadius - caretSize;
8681
+ x1 = x2 - caretSize;
8682
+ x3 = x2 + caretSize;
8683
+ } else {
8684
+ x2 = ptX + (width / 2);
8685
+ x1 = x2 - caretSize;
8686
+ x3 = x2 + caretSize;
8687
+ }
8688
+ if (yAlign === 'top') {
8689
+ y1 = ptY;
8690
+ y2 = y1 - caretSize;
8691
+ y3 = y1;
8692
+ } else {
8693
+ y1 = ptY + height;
8694
+ y2 = y1 + caretSize;
8695
+ y3 = y1;
8696
+ // invert drawing order
8697
+ var tmp = x3;
8698
+ x3 = x1;
8699
+ x1 = tmp;
8700
+ }
8701
+ }
8702
+ return {x1: x1, x2: x2, x3: x3, y1: y1, y2: y2, y3: y3};
8703
+ },
8704
+ drawTitle: function(pt, vm, ctx, opacity) {
8705
+ var title = vm.title;
8706
+
8707
+ if (title.length) {
8708
+ ctx.textAlign = vm._titleAlign;
8709
+ ctx.textBaseline = 'top';
8710
+
8711
+ var titleFontSize = vm.titleFontSize;
8712
+ var titleSpacing = vm.titleSpacing;
8713
+
8714
+ ctx.fillStyle = mergeOpacity(vm.titleFontColor, opacity);
8715
+ ctx.font = helpers.fontString(titleFontSize, vm._titleFontStyle, vm._titleFontFamily);
8716
+
8717
+ var i, len;
8718
+ for (i = 0, len = title.length; i < len; ++i) {
8719
+ ctx.fillText(title[i], pt.x, pt.y);
8720
+ pt.y += titleFontSize + titleSpacing; // Line Height and spacing
8721
+
8722
+ if (i + 1 === title.length) {
8723
+ pt.y += vm.titleMarginBottom - titleSpacing; // If Last, add margin, remove spacing
8724
+ }
8725
+ }
8726
+ }
8727
+ },
8728
+ drawBody: function(pt, vm, ctx, opacity) {
8729
+ var bodyFontSize = vm.bodyFontSize;
8730
+ var bodySpacing = vm.bodySpacing;
8731
+ var body = vm.body;
8732
+
8733
+ ctx.textAlign = vm._bodyAlign;
8734
+ ctx.textBaseline = 'top';
8735
+ ctx.font = helpers.fontString(bodyFontSize, vm._bodyFontStyle, vm._bodyFontFamily);
8736
+
8737
+ // Before Body
8738
+ var xLinePadding = 0;
8739
+ var fillLineOfText = function(line) {
8740
+ ctx.fillText(line, pt.x + xLinePadding, pt.y);
8741
+ pt.y += bodyFontSize + bodySpacing;
8742
+ };
8743
+
8744
+ // Before body lines
8745
+ ctx.fillStyle = mergeOpacity(vm.bodyFontColor, opacity);
8746
+ helpers.each(vm.beforeBody, fillLineOfText);
8747
+
8748
+ var drawColorBoxes = vm.displayColors;
8749
+ xLinePadding = drawColorBoxes ? (bodyFontSize + 2) : 0;
8750
+
8751
+ // Draw body lines now
8752
+ helpers.each(body, function(bodyItem, i) {
8753
+ var textColor = mergeOpacity(vm.labelTextColors[i], opacity);
8754
+ ctx.fillStyle = textColor;
8755
+ helpers.each(bodyItem.before, fillLineOfText);
8756
+
8757
+ helpers.each(bodyItem.lines, function(line) {
8758
+ // Draw Legend-like boxes if needed
8759
+ if (drawColorBoxes) {
8760
+ // Fill a white rect so that colours merge nicely if the opacity is < 1
8761
+ ctx.fillStyle = mergeOpacity(vm.legendColorBackground, opacity);
8762
+ ctx.fillRect(pt.x, pt.y, bodyFontSize, bodyFontSize);
8763
+
8764
+ // Border
8765
+ ctx.lineWidth = 1;
8766
+ ctx.strokeStyle = mergeOpacity(vm.labelColors[i].borderColor, opacity);
8767
+ ctx.strokeRect(pt.x, pt.y, bodyFontSize, bodyFontSize);
8768
+
8769
+ // Inner square
8770
+ ctx.fillStyle = mergeOpacity(vm.labelColors[i].backgroundColor, opacity);
8771
+ ctx.fillRect(pt.x + 1, pt.y + 1, bodyFontSize - 2, bodyFontSize - 2);
8772
+ ctx.fillStyle = textColor;
8773
+ }
8774
+
8775
+ fillLineOfText(line);
8776
+ });
8777
+
8778
+ helpers.each(bodyItem.after, fillLineOfText);
8779
+ });
8780
+
8781
+ // Reset back to 0 for after body
8782
+ xLinePadding = 0;
8783
+
8784
+ // After body lines
8785
+ helpers.each(vm.afterBody, fillLineOfText);
8786
+ pt.y -= bodySpacing; // Remove last body spacing
8787
+ },
8788
+ drawFooter: function(pt, vm, ctx, opacity) {
8789
+ var footer = vm.footer;
8790
+
8791
+ if (footer.length) {
8792
+ pt.y += vm.footerMarginTop;
8793
+
8794
+ ctx.textAlign = vm._footerAlign;
8795
+ ctx.textBaseline = 'top';
8796
+
8797
+ ctx.fillStyle = mergeOpacity(vm.footerFontColor, opacity);
8798
+ ctx.font = helpers.fontString(vm.footerFontSize, vm._footerFontStyle, vm._footerFontFamily);
8799
+
8800
+ helpers.each(footer, function(line) {
8801
+ ctx.fillText(line, pt.x, pt.y);
8802
+ pt.y += vm.footerFontSize + vm.footerSpacing;
8803
+ });
8804
+ }
8805
+ },
8806
+ drawBackground: function(pt, vm, ctx, tooltipSize, opacity) {
8807
+ ctx.fillStyle = mergeOpacity(vm.backgroundColor, opacity);
8808
+ ctx.strokeStyle = mergeOpacity(vm.borderColor, opacity);
8809
+ ctx.lineWidth = vm.borderWidth;
8810
+ var xAlign = vm.xAlign;
8811
+ var yAlign = vm.yAlign;
8812
+ var x = pt.x;
8813
+ var y = pt.y;
8814
+ var width = tooltipSize.width;
8815
+ var height = tooltipSize.height;
8816
+ var radius = vm.cornerRadius;
8817
+
8818
+ ctx.beginPath();
8819
+ ctx.moveTo(x + radius, y);
8820
+ if (yAlign === 'top') {
8821
+ this.drawCaret(pt, tooltipSize);
8822
+ }
8823
+ ctx.lineTo(x + width - radius, y);
8824
+ ctx.quadraticCurveTo(x + width, y, x + width, y + radius);
8825
+ if (yAlign === 'center' && xAlign === 'right') {
8826
+ this.drawCaret(pt, tooltipSize);
8827
+ }
8828
+ ctx.lineTo(x + width, y + height - radius);
8829
+ ctx.quadraticCurveTo(x + width, y + height, x + width - radius, y + height);
8830
+ if (yAlign === 'bottom') {
8831
+ this.drawCaret(pt, tooltipSize);
8832
+ }
8833
+ ctx.lineTo(x + radius, y + height);
8834
+ ctx.quadraticCurveTo(x, y + height, x, y + height - radius);
8835
+ if (yAlign === 'center' && xAlign === 'left') {
8836
+ this.drawCaret(pt, tooltipSize);
8837
+ }
8838
+ ctx.lineTo(x, y + radius);
8839
+ ctx.quadraticCurveTo(x, y, x + radius, y);
8840
+ ctx.closePath();
8841
+
8842
+ ctx.fill();
8843
+
8844
+ if (vm.borderWidth > 0) {
8845
+ ctx.stroke();
8846
+ }
8847
+ },
8848
+ draw: function() {
8849
+ var ctx = this._chart.ctx;
8850
+ var vm = this._view;
8851
+
8852
+ if (vm.opacity === 0) {
8853
+ return;
8854
+ }
8855
+
8856
+ var tooltipSize = {
8857
+ width: vm.width,
8858
+ height: vm.height
8859
+ };
8860
+ var pt = {
8861
+ x: vm.x,
8862
+ y: vm.y
8863
+ };
8864
+
8865
+ // IE11/Edge does not like very small opacities, so snap to 0
8866
+ var opacity = Math.abs(vm.opacity < 1e-3) ? 0 : vm.opacity;
8867
+
8868
+ // Truthy/falsey value for empty tooltip
8869
+ var hasTooltipContent = vm.title.length || vm.beforeBody.length || vm.body.length || vm.afterBody.length || vm.footer.length;
8870
+
8871
+ if (this._options.enabled && hasTooltipContent) {
8872
+ // Draw Background
8873
+ this.drawBackground(pt, vm, ctx, tooltipSize, opacity);
8874
+
8875
+ // Draw Title, Body, and Footer
8876
+ pt.x += vm.xPadding;
8877
+ pt.y += vm.yPadding;
8878
+
8879
+ // Titles
8880
+ this.drawTitle(pt, vm, ctx, opacity);
8881
+
8882
+ // Body
8883
+ this.drawBody(pt, vm, ctx, opacity);
8884
+
8885
+ // Footer
8886
+ this.drawFooter(pt, vm, ctx, opacity);
8887
+ }
8888
+ },
8889
+
8890
+ /**
8891
+ * Handle an event
8892
+ * @private
8893
+ * @param {IEvent} event - The event to handle
8894
+ * @returns {Boolean} true if the tooltip changed
8895
+ */
8896
+ handleEvent: function(e) {
8897
+ var me = this;
8898
+ var options = me._options;
8899
+ var changed = false;
8900
+
8901
+ me._lastActive = me._lastActive || [];
8902
+
8903
+ // Find Active Elements for tooltips
8904
+ if (e.type === 'mouseout') {
8905
+ me._active = [];
8906
+ } else {
8907
+ me._active = me._chart.getElementsAtEventForMode(e, options.mode, options);
8908
+ }
8909
+
8910
+ // Remember Last Actives
8911
+ changed = !helpers.arrayEquals(me._active, me._lastActive);
8912
+
8913
+ // If tooltip didn't change, do not handle the target event
8914
+ if (!changed) {
8915
+ return false;
8916
+ }
8917
+
8918
+ me._lastActive = me._active;
8919
+
8920
+ if (options.enabled || options.custom) {
8921
+ me._eventPosition = {
8922
+ x: e.x,
8923
+ y: e.y
8924
+ };
8925
+
8926
+ var model = me._model;
8927
+ me.update(true);
8928
+ me.pivot();
8929
+
8930
+ // See if our tooltip position changed
8931
+ changed |= (model.x !== me._model.x) || (model.y !== me._model.y);
8932
+ }
8933
+
8934
+ return changed;
8935
+ }
8936
+ });
8937
+
8938
+ /**
8939
+ * @namespace Chart.Tooltip.positioners
8940
+ */
8941
+ Chart.Tooltip.positioners = {
8942
+ /**
8943
+ * Average mode places the tooltip at the average position of the elements shown
8944
+ * @function Chart.Tooltip.positioners.average
8945
+ * @param elements {ChartElement[]} the elements being displayed in the tooltip
8946
+ * @returns {Point} tooltip position
8947
+ */
8948
+ average: function(elements) {
8949
+ if (!elements.length) {
8950
+ return false;
8951
+ }
8952
+
8953
+ var i, len;
8954
+ var x = 0;
8955
+ var y = 0;
8956
+ var count = 0;
8957
+
8958
+ for (i = 0, len = elements.length; i < len; ++i) {
8959
+ var el = elements[i];
8960
+ if (el && el.hasValue()) {
8961
+ var pos = el.tooltipPosition();
8962
+ x += pos.x;
8963
+ y += pos.y;
8964
+ ++count;
8965
+ }
8966
+ }
8967
+
8968
+ return {
8969
+ x: Math.round(x / count),
8970
+ y: Math.round(y / count)
8971
+ };
8972
+ },
8973
+
8974
+ /**
8975
+ * Gets the tooltip position nearest of the item nearest to the event position
8976
+ * @function Chart.Tooltip.positioners.nearest
8977
+ * @param elements {Chart.Element[]} the tooltip elements
8978
+ * @param eventPosition {Point} the position of the event in canvas coordinates
8979
+ * @returns {Point} the tooltip position
8980
+ */
8981
+ nearest: function(elements, eventPosition) {
8982
+ var x = eventPosition.x;
8983
+ var y = eventPosition.y;
8984
+ var minDistance = Number.POSITIVE_INFINITY;
8985
+ var i, len, nearestElement;
8986
+
8987
+ for (i = 0, len = elements.length; i < len; ++i) {
8988
+ var el = elements[i];
8989
+ if (el && el.hasValue()) {
8990
+ var center = el.getCenterPoint();
8991
+ var d = helpers.distanceBetweenPoints(eventPosition, center);
8992
+
8993
+ if (d < minDistance) {
8994
+ minDistance = d;
8995
+ nearestElement = el;
8996
+ }
8997
+ }
8998
+ }
8999
+
9000
+ if (nearestElement) {
9001
+ var tp = nearestElement.tooltipPosition();
9002
+ x = tp.x;
9003
+ y = tp.y;
9004
+ }
9005
+
9006
+ return {
9007
+ x: x,
9008
+ y: y
9009
+ };
9010
+ }
9011
+ };
9012
+ };
9013
+
9014
+ },{"25":25,"26":26,"45":45}],36:[function(require,module,exports){
9015
+ 'use strict';
9016
+
9017
+ var defaults = require(25);
9018
+ var Element = require(26);
9019
+ var helpers = require(45);
9020
+
9021
+ defaults._set('global', {
9022
+ elements: {
9023
+ arc: {
9024
+ backgroundColor: defaults.global.defaultColor,
9025
+ borderColor: '#fff',
9026
+ borderWidth: 2
9027
+ }
9028
+ }
9029
+ });
9030
+
9031
+ module.exports = Element.extend({
9032
+ inLabelRange: function(mouseX) {
9033
+ var vm = this._view;
9034
+
9035
+ if (vm) {
9036
+ return (Math.pow(mouseX - vm.x, 2) < Math.pow(vm.radius + vm.hoverRadius, 2));
9037
+ }
9038
+ return false;
9039
+ },
9040
+
9041
+ inRange: function(chartX, chartY) {
9042
+ var vm = this._view;
9043
+
9044
+ if (vm) {
9045
+ var pointRelativePosition = helpers.getAngleFromPoint(vm, {x: chartX, y: chartY});
9046
+ var angle = pointRelativePosition.angle;
9047
+ var distance = pointRelativePosition.distance;
9048
+
9049
+ // Sanitise angle range
9050
+ var startAngle = vm.startAngle;
9051
+ var endAngle = vm.endAngle;
9052
+ while (endAngle < startAngle) {
9053
+ endAngle += 2.0 * Math.PI;
9054
+ }
9055
+ while (angle > endAngle) {
9056
+ angle -= 2.0 * Math.PI;
9057
+ }
9058
+ while (angle < startAngle) {
9059
+ angle += 2.0 * Math.PI;
9060
+ }
9061
+
9062
+ // Check if within the range of the open/close angle
9063
+ var betweenAngles = (angle >= startAngle && angle <= endAngle);
9064
+ var withinRadius = (distance >= vm.innerRadius && distance <= vm.outerRadius);
9065
+
9066
+ return (betweenAngles && withinRadius);
9067
+ }
9068
+ return false;
9069
+ },
9070
+
9071
+ getCenterPoint: function() {
9072
+ var vm = this._view;
9073
+ var halfAngle = (vm.startAngle + vm.endAngle) / 2;
9074
+ var halfRadius = (vm.innerRadius + vm.outerRadius) / 2;
9075
+ return {
9076
+ x: vm.x + Math.cos(halfAngle) * halfRadius,
9077
+ y: vm.y + Math.sin(halfAngle) * halfRadius
9078
+ };
9079
+ },
9080
+
9081
+ getArea: function() {
9082
+ var vm = this._view;
9083
+ return Math.PI * ((vm.endAngle - vm.startAngle) / (2 * Math.PI)) * (Math.pow(vm.outerRadius, 2) - Math.pow(vm.innerRadius, 2));
9084
+ },
9085
+
9086
+ tooltipPosition: function() {
9087
+ var vm = this._view;
9088
+ var centreAngle = vm.startAngle + ((vm.endAngle - vm.startAngle) / 2);
9089
+ var rangeFromCentre = (vm.outerRadius - vm.innerRadius) / 2 + vm.innerRadius;
9090
+
9091
+ return {
9092
+ x: vm.x + (Math.cos(centreAngle) * rangeFromCentre),
9093
+ y: vm.y + (Math.sin(centreAngle) * rangeFromCentre)
9094
+ };
9095
+ },
9096
+
9097
+ draw: function() {
9098
+ var ctx = this._chart.ctx;
9099
+ var vm = this._view;
9100
+ var sA = vm.startAngle;
9101
+ var eA = vm.endAngle;
9102
+
9103
+ ctx.beginPath();
9104
+
9105
+ ctx.arc(vm.x, vm.y, vm.outerRadius, sA, eA);
9106
+ ctx.arc(vm.x, vm.y, vm.innerRadius, eA, sA, true);
9107
+
9108
+ ctx.closePath();
9109
+ ctx.strokeStyle = vm.borderColor;
9110
+ ctx.lineWidth = vm.borderWidth;
9111
+
9112
+ ctx.fillStyle = vm.backgroundColor;
9113
+
9114
+ ctx.fill();
9115
+ ctx.lineJoin = 'bevel';
9116
+
9117
+ if (vm.borderWidth) {
9118
+ ctx.stroke();
9119
+ }
9120
+ }
9121
+ });
9122
+
9123
+ },{"25":25,"26":26,"45":45}],37:[function(require,module,exports){
9124
+ 'use strict';
9125
+
9126
+ var defaults = require(25);
9127
+ var Element = require(26);
9128
+ var helpers = require(45);
9129
+
9130
+ var globalDefaults = defaults.global;
9131
+
9132
+ defaults._set('global', {
9133
+ elements: {
9134
+ line: {
9135
+ tension: 0.4,
9136
+ backgroundColor: globalDefaults.defaultColor,
9137
+ borderWidth: 3,
9138
+ borderColor: globalDefaults.defaultColor,
9139
+ borderCapStyle: 'butt',
9140
+ borderDash: [],
9141
+ borderDashOffset: 0.0,
9142
+ borderJoinStyle: 'miter',
9143
+ capBezierPoints: true,
9144
+ fill: true, // do we fill in the area between the line and its base axis
9145
+ }
9146
+ }
9147
+ });
9148
+
9149
+ module.exports = Element.extend({
9150
+ draw: function() {
9151
+ var me = this;
9152
+ var vm = me._view;
9153
+ var ctx = me._chart.ctx;
9154
+ var spanGaps = vm.spanGaps;
9155
+ var points = me._children.slice(); // clone array
9156
+ var globalOptionLineElements = globalDefaults.elements.line;
9157
+ var lastDrawnIndex = -1;
9158
+ var index, current, previous, currentVM;
9159
+
9160
+ // If we are looping, adding the first point again
9161
+ if (me._loop && points.length) {
9162
+ points.push(points[0]);
9163
+ }
9164
+
9165
+ ctx.save();
9166
+
9167
+ // Stroke Line Options
9168
+ ctx.lineCap = vm.borderCapStyle || globalOptionLineElements.borderCapStyle;
9169
+
9170
+ // IE 9 and 10 do not support line dash
9171
+ if (ctx.setLineDash) {
9172
+ ctx.setLineDash(vm.borderDash || globalOptionLineElements.borderDash);
9173
+ }
9174
+
9175
+ ctx.lineDashOffset = vm.borderDashOffset || globalOptionLineElements.borderDashOffset;
9176
+ ctx.lineJoin = vm.borderJoinStyle || globalOptionLineElements.borderJoinStyle;
9177
+ ctx.lineWidth = vm.borderWidth || globalOptionLineElements.borderWidth;
9178
+ ctx.strokeStyle = vm.borderColor || globalDefaults.defaultColor;
9179
+
9180
+ // Stroke Line
9181
+ ctx.beginPath();
9182
+ lastDrawnIndex = -1;
9183
+
9184
+ for (index = 0; index < points.length; ++index) {
9185
+ current = points[index];
9186
+ previous = helpers.previousItem(points, index);
9187
+ currentVM = current._view;
9188
+
9189
+ // First point moves to it's starting position no matter what
9190
+ if (index === 0) {
9191
+ if (!currentVM.skip) {
9192
+ ctx.moveTo(currentVM.x, currentVM.y);
9193
+ lastDrawnIndex = index;
9194
+ }
9195
+ } else {
9196
+ previous = lastDrawnIndex === -1 ? previous : points[lastDrawnIndex];
9197
+
9198
+ if (!currentVM.skip) {
9199
+ if ((lastDrawnIndex !== (index - 1) && !spanGaps) || lastDrawnIndex === -1) {
9200
+ // There was a gap and this is the first point after the gap
9201
+ ctx.moveTo(currentVM.x, currentVM.y);
9202
+ } else {
9203
+ // Line to next point
9204
+ helpers.canvas.lineTo(ctx, previous._view, current._view);
9205
+ }
9206
+ lastDrawnIndex = index;
9207
+ }
9208
+ }
9209
+ }
9210
+
9211
+ ctx.stroke();
9212
+ ctx.restore();
9213
+ }
9214
+ });
9215
+
9216
+ },{"25":25,"26":26,"45":45}],38:[function(require,module,exports){
9217
+ 'use strict';
9218
+
9219
+ var defaults = require(25);
9220
+ var Element = require(26);
9221
+ var helpers = require(45);
9222
+
9223
+ var defaultColor = defaults.global.defaultColor;
9224
+
9225
+ defaults._set('global', {
9226
+ elements: {
9227
+ point: {
9228
+ radius: 3,
9229
+ pointStyle: 'circle',
9230
+ backgroundColor: defaultColor,
9231
+ borderColor: defaultColor,
9232
+ borderWidth: 1,
9233
+ // Hover
9234
+ hitRadius: 1,
9235
+ hoverRadius: 4,
9236
+ hoverBorderWidth: 1
9237
+ }
9238
+ }
9239
+ });
9240
+
9241
+ function xRange(mouseX) {
9242
+ var vm = this._view;
9243
+ return vm ? (Math.pow(mouseX - vm.x, 2) < Math.pow(vm.radius + vm.hitRadius, 2)) : false;
9244
+ }
9245
+
9246
+ function yRange(mouseY) {
9247
+ var vm = this._view;
9248
+ return vm ? (Math.pow(mouseY - vm.y, 2) < Math.pow(vm.radius + vm.hitRadius, 2)) : false;
9249
+ }
9250
+
9251
+ module.exports = Element.extend({
9252
+ inRange: function(mouseX, mouseY) {
9253
+ var vm = this._view;
9254
+ return vm ? ((Math.pow(mouseX - vm.x, 2) + Math.pow(mouseY - vm.y, 2)) < Math.pow(vm.hitRadius + vm.radius, 2)) : false;
9255
+ },
9256
+
9257
+ inLabelRange: xRange,
9258
+ inXRange: xRange,
9259
+ inYRange: yRange,
9260
+
9261
+ getCenterPoint: function() {
9262
+ var vm = this._view;
9263
+ return {
9264
+ x: vm.x,
9265
+ y: vm.y
9266
+ };
9267
+ },
9268
+
9269
+ getArea: function() {
9270
+ return Math.PI * Math.pow(this._view.radius, 2);
9271
+ },
9272
+
9273
+ tooltipPosition: function() {
9274
+ var vm = this._view;
9275
+ return {
9276
+ x: vm.x,
9277
+ y: vm.y,
9278
+ padding: vm.radius + vm.borderWidth
9279
+ };
9280
+ },
9281
+
9282
+ draw: function(chartArea) {
9283
+ var vm = this._view;
9284
+ var model = this._model;
9285
+ var ctx = this._chart.ctx;
9286
+ var pointStyle = vm.pointStyle;
9287
+ var radius = vm.radius;
9288
+ var x = vm.x;
9289
+ var y = vm.y;
9290
+ var color = helpers.color;
9291
+ var errMargin = 1.01; // 1.01 is margin for Accumulated error. (Especially Edge, IE.)
9292
+ var ratio = 0;
9293
+
9294
+ if (vm.skip) {
9295
+ return;
9296
+ }
9297
+
9298
+ ctx.strokeStyle = vm.borderColor || defaultColor;
9299
+ ctx.lineWidth = helpers.valueOrDefault(vm.borderWidth, defaults.global.elements.point.borderWidth);
9300
+ ctx.fillStyle = vm.backgroundColor || defaultColor;
9301
+
9302
+ // Cliping for Points.
9303
+ // going out from inner charArea?
9304
+ if ((chartArea !== undefined) && ((model.x < chartArea.left) || (chartArea.right * errMargin < model.x) || (model.y < chartArea.top) || (chartArea.bottom * errMargin < model.y))) {
9305
+ // Point fade out
9306
+ if (model.x < chartArea.left) {
9307
+ ratio = (x - model.x) / (chartArea.left - model.x);
9308
+ } else if (chartArea.right * errMargin < model.x) {
9309
+ ratio = (model.x - x) / (model.x - chartArea.right);
9310
+ } else if (model.y < chartArea.top) {
9311
+ ratio = (y - model.y) / (chartArea.top - model.y);
9312
+ } else if (chartArea.bottom * errMargin < model.y) {
9313
+ ratio = (model.y - y) / (model.y - chartArea.bottom);
9314
+ }
9315
+ ratio = Math.round(ratio * 100) / 100;
9316
+ ctx.strokeStyle = color(ctx.strokeStyle).alpha(ratio).rgbString();
9317
+ ctx.fillStyle = color(ctx.fillStyle).alpha(ratio).rgbString();
9318
+ }
9319
+
9320
+ helpers.canvas.drawPoint(ctx, pointStyle, radius, x, y);
9321
+ }
9322
+ });
9323
+
9324
+ },{"25":25,"26":26,"45":45}],39:[function(require,module,exports){
9325
+ 'use strict';
9326
+
9327
+ var defaults = require(25);
9328
+ var Element = require(26);
9329
+
9330
+ defaults._set('global', {
9331
+ elements: {
9332
+ rectangle: {
9333
+ backgroundColor: defaults.global.defaultColor,
9334
+ borderColor: defaults.global.defaultColor,
9335
+ borderSkipped: 'bottom',
9336
+ borderWidth: 0
9337
+ }
9338
+ }
9339
+ });
9340
+
9341
+ function isVertical(bar) {
9342
+ return bar._view.width !== undefined;
9343
+ }
9344
+
9345
+ /**
9346
+ * Helper function to get the bounds of the bar regardless of the orientation
9347
+ * @param bar {Chart.Element.Rectangle} the bar
9348
+ * @return {Bounds} bounds of the bar
9349
+ * @private
9350
+ */
9351
+ function getBarBounds(bar) {
9352
+ var vm = bar._view;
9353
+ var x1, x2, y1, y2;
9354
+
9355
+ if (isVertical(bar)) {
9356
+ // vertical
9357
+ var halfWidth = vm.width / 2;
9358
+ x1 = vm.x - halfWidth;
9359
+ x2 = vm.x + halfWidth;
9360
+ y1 = Math.min(vm.y, vm.base);
9361
+ y2 = Math.max(vm.y, vm.base);
9362
+ } else {
9363
+ // horizontal bar
9364
+ var halfHeight = vm.height / 2;
9365
+ x1 = Math.min(vm.x, vm.base);
9366
+ x2 = Math.max(vm.x, vm.base);
9367
+ y1 = vm.y - halfHeight;
9368
+ y2 = vm.y + halfHeight;
9369
+ }
9370
+
9371
+ return {
9372
+ left: x1,
9373
+ top: y1,
9374
+ right: x2,
9375
+ bottom: y2
9376
+ };
9377
+ }
9378
+
9379
+ module.exports = Element.extend({
9380
+ draw: function() {
9381
+ var ctx = this._chart.ctx;
9382
+ var vm = this._view;
9383
+ var left, right, top, bottom, signX, signY, borderSkipped;
9384
+ var borderWidth = vm.borderWidth;
9385
+
9386
+ if (!vm.horizontal) {
9387
+ // bar
9388
+ left = vm.x - vm.width / 2;
9389
+ right = vm.x + vm.width / 2;
9390
+ top = vm.y;
9391
+ bottom = vm.base;
9392
+ signX = 1;
9393
+ signY = bottom > top ? 1 : -1;
9394
+ borderSkipped = vm.borderSkipped || 'bottom';
9395
+ } else {
9396
+ // horizontal bar
9397
+ left = vm.base;
9398
+ right = vm.x;
9399
+ top = vm.y - vm.height / 2;
9400
+ bottom = vm.y + vm.height / 2;
9401
+ signX = right > left ? 1 : -1;
9402
+ signY = 1;
9403
+ borderSkipped = vm.borderSkipped || 'left';
9404
+ }
9405
+
9406
+ // Canvas doesn't allow us to stroke inside the width so we can
9407
+ // adjust the sizes to fit if we're setting a stroke on the line
9408
+ if (borderWidth) {
9409
+ // borderWidth shold be less than bar width and bar height.
9410
+ var barSize = Math.min(Math.abs(left - right), Math.abs(top - bottom));
9411
+ borderWidth = borderWidth > barSize ? barSize : borderWidth;
9412
+ var halfStroke = borderWidth / 2;
9413
+ // Adjust borderWidth when bar top position is near vm.base(zero).
9414
+ var borderLeft = left + (borderSkipped !== 'left' ? halfStroke * signX : 0);
9415
+ var borderRight = right + (borderSkipped !== 'right' ? -halfStroke * signX : 0);
9416
+ var borderTop = top + (borderSkipped !== 'top' ? halfStroke * signY : 0);
9417
+ var borderBottom = bottom + (borderSkipped !== 'bottom' ? -halfStroke * signY : 0);
9418
+ // not become a vertical line?
9419
+ if (borderLeft !== borderRight) {
9420
+ top = borderTop;
9421
+ bottom = borderBottom;
9422
+ }
9423
+ // not become a horizontal line?
9424
+ if (borderTop !== borderBottom) {
9425
+ left = borderLeft;
9426
+ right = borderRight;
9427
+ }
9428
+ }
9429
+
9430
+ ctx.beginPath();
9431
+ ctx.fillStyle = vm.backgroundColor;
9432
+ ctx.strokeStyle = vm.borderColor;
9433
+ ctx.lineWidth = borderWidth;
9434
+
9435
+ // Corner points, from bottom-left to bottom-right clockwise
9436
+ // | 1 2 |
9437
+ // | 0 3 |
9438
+ var corners = [
9439
+ [left, bottom],
9440
+ [left, top],
9441
+ [right, top],
9442
+ [right, bottom]
9443
+ ];
9444
+
9445
+ // Find first (starting) corner with fallback to 'bottom'
9446
+ var borders = ['bottom', 'left', 'top', 'right'];
9447
+ var startCorner = borders.indexOf(borderSkipped, 0);
9448
+ if (startCorner === -1) {
9449
+ startCorner = 0;
9450
+ }
9451
+
9452
+ function cornerAt(index) {
9453
+ return corners[(startCorner + index) % 4];
9454
+ }
9455
+
9456
+ // Draw rectangle from 'startCorner'
9457
+ var corner = cornerAt(0);
9458
+ ctx.moveTo(corner[0], corner[1]);
9459
+
9460
+ for (var i = 1; i < 4; i++) {
9461
+ corner = cornerAt(i);
9462
+ ctx.lineTo(corner[0], corner[1]);
9463
+ }
9464
+
9465
+ ctx.fill();
9466
+ if (borderWidth) {
9467
+ ctx.stroke();
9468
+ }
9469
+ },
9470
+
9471
+ height: function() {
9472
+ var vm = this._view;
9473
+ return vm.base - vm.y;
9474
+ },
9475
+
9476
+ inRange: function(mouseX, mouseY) {
9477
+ var inRange = false;
9478
+
9479
+ if (this._view) {
9480
+ var bounds = getBarBounds(this);
9481
+ inRange = mouseX >= bounds.left && mouseX <= bounds.right && mouseY >= bounds.top && mouseY <= bounds.bottom;
9482
+ }
9483
+
9484
+ return inRange;
9485
+ },
9486
+
9487
+ inLabelRange: function(mouseX, mouseY) {
9488
+ var me = this;
9489
+ if (!me._view) {
9490
+ return false;
9491
+ }
9492
+
9493
+ var inRange = false;
9494
+ var bounds = getBarBounds(me);
9495
+
9496
+ if (isVertical(me)) {
9497
+ inRange = mouseX >= bounds.left && mouseX <= bounds.right;
9498
+ } else {
9499
+ inRange = mouseY >= bounds.top && mouseY <= bounds.bottom;
9500
+ }
9501
+
9502
+ return inRange;
9503
+ },
9504
+
9505
+ inXRange: function(mouseX) {
9506
+ var bounds = getBarBounds(this);
9507
+ return mouseX >= bounds.left && mouseX <= bounds.right;
9508
+ },
9509
+
9510
+ inYRange: function(mouseY) {
9511
+ var bounds = getBarBounds(this);
9512
+ return mouseY >= bounds.top && mouseY <= bounds.bottom;
9513
+ },
9514
+
9515
+ getCenterPoint: function() {
9516
+ var vm = this._view;
9517
+ var x, y;
9518
+ if (isVertical(this)) {
9519
+ x = vm.x;
9520
+ y = (vm.y + vm.base) / 2;
9521
+ } else {
9522
+ x = (vm.x + vm.base) / 2;
9523
+ y = vm.y;
9524
+ }
9525
+
9526
+ return {x: x, y: y};
9527
+ },
9528
+
9529
+ getArea: function() {
9530
+ var vm = this._view;
9531
+ return vm.width * Math.abs(vm.y - vm.base);
9532
+ },
9533
+
9534
+ tooltipPosition: function() {
9535
+ var vm = this._view;
9536
+ return {
9537
+ x: vm.x,
9538
+ y: vm.y
9539
+ };
9540
+ }
9541
+ });
9542
+
9543
+ },{"25":25,"26":26}],40:[function(require,module,exports){
9544
+ 'use strict';
9545
+
9546
+ module.exports = {};
9547
+ module.exports.Arc = require(36);
9548
+ module.exports.Line = require(37);
9549
+ module.exports.Point = require(38);
9550
+ module.exports.Rectangle = require(39);
9551
+
9552
+ },{"36":36,"37":37,"38":38,"39":39}],41:[function(require,module,exports){
9553
+ 'use strict';
9554
+
9555
+ var helpers = require(42);
9556
+
9557
+ /**
9558
+ * @namespace Chart.helpers.canvas
9559
+ */
9560
+ var exports = module.exports = {
9561
+ /**
9562
+ * Clears the entire canvas associated to the given `chart`.
9563
+ * @param {Chart} chart - The chart for which to clear the canvas.
9564
+ */
9565
+ clear: function(chart) {
9566
+ chart.ctx.clearRect(0, 0, chart.width, chart.height);
9567
+ },
9568
+
9569
+ /**
9570
+ * Creates a "path" for a rectangle with rounded corners at position (x, y) with a
9571
+ * given size (width, height) and the same `radius` for all corners.
9572
+ * @param {CanvasRenderingContext2D} ctx - The canvas 2D Context.
9573
+ * @param {Number} x - The x axis of the coordinate for the rectangle starting point.
9574
+ * @param {Number} y - The y axis of the coordinate for the rectangle starting point.
9575
+ * @param {Number} width - The rectangle's width.
9576
+ * @param {Number} height - The rectangle's height.
9577
+ * @param {Number} radius - The rounded amount (in pixels) for the four corners.
9578
+ * @todo handle `radius` as top-left, top-right, bottom-right, bottom-left array/object?
9579
+ */
9580
+ roundedRect: function(ctx, x, y, width, height, radius) {
9581
+ if (radius) {
9582
+ var rx = Math.min(radius, width / 2);
9583
+ var ry = Math.min(radius, height / 2);
9584
+
9585
+ ctx.moveTo(x + rx, y);
9586
+ ctx.lineTo(x + width - rx, y);
9587
+ ctx.quadraticCurveTo(x + width, y, x + width, y + ry);
9588
+ ctx.lineTo(x + width, y + height - ry);
9589
+ ctx.quadraticCurveTo(x + width, y + height, x + width - rx, y + height);
9590
+ ctx.lineTo(x + rx, y + height);
9591
+ ctx.quadraticCurveTo(x, y + height, x, y + height - ry);
9592
+ ctx.lineTo(x, y + ry);
9593
+ ctx.quadraticCurveTo(x, y, x + rx, y);
9594
+ } else {
9595
+ ctx.rect(x, y, width, height);
9596
+ }
9597
+ },
9598
+
9599
+ drawPoint: function(ctx, style, radius, x, y) {
9600
+ var type, edgeLength, xOffset, yOffset, height, size;
9601
+
9602
+ if (style && typeof style === 'object') {
9603
+ type = style.toString();
9604
+ if (type === '[object HTMLImageElement]' || type === '[object HTMLCanvasElement]') {
9605
+ ctx.drawImage(style, x - style.width / 2, y - style.height / 2, style.width, style.height);
9606
+ return;
9607
+ }
9608
+ }
9609
+
9610
+ if (isNaN(radius) || radius <= 0) {
9611
+ return;
9612
+ }
9613
+
9614
+ switch (style) {
9615
+ // Default includes circle
9616
+ default:
9617
+ ctx.beginPath();
9618
+ ctx.arc(x, y, radius, 0, Math.PI * 2);
9619
+ ctx.closePath();
9620
+ ctx.fill();
9621
+ break;
9622
+ case 'triangle':
9623
+ ctx.beginPath();
9624
+ edgeLength = 3 * radius / Math.sqrt(3);
9625
+ height = edgeLength * Math.sqrt(3) / 2;
9626
+ ctx.moveTo(x - edgeLength / 2, y + height / 3);
9627
+ ctx.lineTo(x + edgeLength / 2, y + height / 3);
9628
+ ctx.lineTo(x, y - 2 * height / 3);
9629
+ ctx.closePath();
9630
+ ctx.fill();
9631
+ break;
9632
+ case 'rect':
9633
+ size = 1 / Math.SQRT2 * radius;
9634
+ ctx.beginPath();
9635
+ ctx.fillRect(x - size, y - size, 2 * size, 2 * size);
9636
+ ctx.strokeRect(x - size, y - size, 2 * size, 2 * size);
9637
+ break;
9638
+ case 'rectRounded':
9639
+ var offset = radius / Math.SQRT2;
9640
+ var leftX = x - offset;
9641
+ var topY = y - offset;
9642
+ var sideSize = Math.SQRT2 * radius;
9643
+ ctx.beginPath();
9644
+ this.roundedRect(ctx, leftX, topY, sideSize, sideSize, radius / 2);
9645
+ ctx.closePath();
9646
+ ctx.fill();
9647
+ break;
9648
+ case 'rectRot':
9649
+ size = 1 / Math.SQRT2 * radius;
9650
+ ctx.beginPath();
9651
+ ctx.moveTo(x - size, y);
9652
+ ctx.lineTo(x, y + size);
9653
+ ctx.lineTo(x + size, y);
9654
+ ctx.lineTo(x, y - size);
9655
+ ctx.closePath();
9656
+ ctx.fill();
9657
+ break;
9658
+ case 'cross':
9659
+ ctx.beginPath();
9660
+ ctx.moveTo(x, y + radius);
9661
+ ctx.lineTo(x, y - radius);
9662
+ ctx.moveTo(x - radius, y);
9663
+ ctx.lineTo(x + radius, y);
9664
+ ctx.closePath();
9665
+ break;
9666
+ case 'crossRot':
9667
+ ctx.beginPath();
9668
+ xOffset = Math.cos(Math.PI / 4) * radius;
9669
+ yOffset = Math.sin(Math.PI / 4) * radius;
9670
+ ctx.moveTo(x - xOffset, y - yOffset);
9671
+ ctx.lineTo(x + xOffset, y + yOffset);
9672
+ ctx.moveTo(x - xOffset, y + yOffset);
9673
+ ctx.lineTo(x + xOffset, y - yOffset);
9674
+ ctx.closePath();
9675
+ break;
9676
+ case 'star':
9677
+ ctx.beginPath();
9678
+ ctx.moveTo(x, y + radius);
9679
+ ctx.lineTo(x, y - radius);
9680
+ ctx.moveTo(x - radius, y);
9681
+ ctx.lineTo(x + radius, y);
9682
+ xOffset = Math.cos(Math.PI / 4) * radius;
9683
+ yOffset = Math.sin(Math.PI / 4) * radius;
9684
+ ctx.moveTo(x - xOffset, y - yOffset);
9685
+ ctx.lineTo(x + xOffset, y + yOffset);
9686
+ ctx.moveTo(x - xOffset, y + yOffset);
9687
+ ctx.lineTo(x + xOffset, y - yOffset);
9688
+ ctx.closePath();
9689
+ break;
9690
+ case 'line':
9691
+ ctx.beginPath();
9692
+ ctx.moveTo(x - radius, y);
9693
+ ctx.lineTo(x + radius, y);
9694
+ ctx.closePath();
9695
+ break;
9696
+ case 'dash':
9697
+ ctx.beginPath();
9698
+ ctx.moveTo(x, y);
9699
+ ctx.lineTo(x + radius, y);
9700
+ ctx.closePath();
9701
+ break;
9702
+ }
9703
+
9704
+ ctx.stroke();
9705
+ },
9706
+
9707
+ clipArea: function(ctx, area) {
9708
+ ctx.save();
9709
+ ctx.beginPath();
9710
+ ctx.rect(area.left, area.top, area.right - area.left, area.bottom - area.top);
9711
+ ctx.clip();
9712
+ },
9713
+
9714
+ unclipArea: function(ctx) {
9715
+ ctx.restore();
9716
+ },
9717
+
9718
+ lineTo: function(ctx, previous, target, flip) {
9719
+ if (target.steppedLine) {
9720
+ if ((target.steppedLine === 'after' && !flip) || (target.steppedLine !== 'after' && flip)) {
9721
+ ctx.lineTo(previous.x, target.y);
9722
+ } else {
9723
+ ctx.lineTo(target.x, previous.y);
9724
+ }
9725
+ ctx.lineTo(target.x, target.y);
9726
+ return;
9727
+ }
9728
+
9729
+ if (!target.tension) {
9730
+ ctx.lineTo(target.x, target.y);
9731
+ return;
9732
+ }
9733
+
9734
+ ctx.bezierCurveTo(
9735
+ flip ? previous.controlPointPreviousX : previous.controlPointNextX,
9736
+ flip ? previous.controlPointPreviousY : previous.controlPointNextY,
9737
+ flip ? target.controlPointNextX : target.controlPointPreviousX,
9738
+ flip ? target.controlPointNextY : target.controlPointPreviousY,
9739
+ target.x,
9740
+ target.y);
9741
+ }
9742
+ };
9743
+
9744
+ // DEPRECATIONS
9745
+
9746
+ /**
9747
+ * Provided for backward compatibility, use Chart.helpers.canvas.clear instead.
9748
+ * @namespace Chart.helpers.clear
9749
+ * @deprecated since version 2.7.0
9750
+ * @todo remove at version 3
9751
+ * @private
9752
+ */
9753
+ helpers.clear = exports.clear;
9754
+
9755
+ /**
9756
+ * Provided for backward compatibility, use Chart.helpers.canvas.roundedRect instead.
9757
+ * @namespace Chart.helpers.drawRoundedRectangle
9758
+ * @deprecated since version 2.7.0
9759
+ * @todo remove at version 3
9760
+ * @private
9761
+ */
9762
+ helpers.drawRoundedRectangle = function(ctx) {
9763
+ ctx.beginPath();
9764
+ exports.roundedRect.apply(exports, arguments);
9765
+ ctx.closePath();
9766
+ };
9767
+
9768
+ },{"42":42}],42:[function(require,module,exports){
9769
+ 'use strict';
9770
+
9771
+ /**
9772
+ * @namespace Chart.helpers
9773
+ */
9774
+ var helpers = {
9775
+ /**
9776
+ * An empty function that can be used, for example, for optional callback.
9777
+ */
9778
+ noop: function() {},
9779
+
9780
+ /**
9781
+ * Returns a unique id, sequentially generated from a global variable.
9782
+ * @returns {Number}
9783
+ * @function
9784
+ */
9785
+ uid: (function() {
9786
+ var id = 0;
9787
+ return function() {
9788
+ return id++;
9789
+ };
9790
+ }()),
9791
+
9792
+ /**
9793
+ * Returns true if `value` is neither null nor undefined, else returns false.
9794
+ * @param {*} value - The value to test.
9795
+ * @returns {Boolean}
9796
+ * @since 2.7.0
9797
+ */
9798
+ isNullOrUndef: function(value) {
9799
+ return value === null || typeof value === 'undefined';
9800
+ },
9801
+
9802
+ /**
9803
+ * Returns true if `value` is an array, else returns false.
9804
+ * @param {*} value - The value to test.
9805
+ * @returns {Boolean}
9806
+ * @function
9807
+ */
9808
+ isArray: Array.isArray ? Array.isArray : function(value) {
9809
+ return Object.prototype.toString.call(value) === '[object Array]';
9810
+ },
9811
+
9812
+ /**
9813
+ * Returns true if `value` is an object (excluding null), else returns false.
9814
+ * @param {*} value - The value to test.
9815
+ * @returns {Boolean}
9816
+ * @since 2.7.0
9817
+ */
9818
+ isObject: function(value) {
9819
+ return value !== null && Object.prototype.toString.call(value) === '[object Object]';
9820
+ },
9821
+
9822
+ /**
9823
+ * Returns `value` if defined, else returns `defaultValue`.
9824
+ * @param {*} value - The value to return if defined.
9825
+ * @param {*} defaultValue - The value to return if `value` is undefined.
9826
+ * @returns {*}
9827
+ */
9828
+ valueOrDefault: function(value, defaultValue) {
9829
+ return typeof value === 'undefined' ? defaultValue : value;
9830
+ },
9831
+
9832
+ /**
9833
+ * Returns value at the given `index` in array if defined, else returns `defaultValue`.
9834
+ * @param {Array} value - The array to lookup for value at `index`.
9835
+ * @param {Number} index - The index in `value` to lookup for value.
9836
+ * @param {*} defaultValue - The value to return if `value[index]` is undefined.
9837
+ * @returns {*}
9838
+ */
9839
+ valueAtIndexOrDefault: function(value, index, defaultValue) {
9840
+ return helpers.valueOrDefault(helpers.isArray(value) ? value[index] : value, defaultValue);
9841
+ },
9842
+
9843
+ /**
9844
+ * Calls `fn` with the given `args` in the scope defined by `thisArg` and returns the
9845
+ * value returned by `fn`. If `fn` is not a function, this method returns undefined.
9846
+ * @param {Function} fn - The function to call.
9847
+ * @param {Array|undefined|null} args - The arguments with which `fn` should be called.
9848
+ * @param {Object} [thisArg] - The value of `this` provided for the call to `fn`.
9849
+ * @returns {*}
9850
+ */
9851
+ callback: function(fn, args, thisArg) {
9852
+ if (fn && typeof fn.call === 'function') {
9853
+ return fn.apply(thisArg, args);
9854
+ }
9855
+ },
9856
+
9857
+ /**
9858
+ * Note(SB) for performance sake, this method should only be used when loopable type
9859
+ * is unknown or in none intensive code (not called often and small loopable). Else
9860
+ * it's preferable to use a regular for() loop and save extra function calls.
9861
+ * @param {Object|Array} loopable - The object or array to be iterated.
9862
+ * @param {Function} fn - The function to call for each item.
9863
+ * @param {Object} [thisArg] - The value of `this` provided for the call to `fn`.
9864
+ * @param {Boolean} [reverse] - If true, iterates backward on the loopable.
9865
+ */
9866
+ each: function(loopable, fn, thisArg, reverse) {
9867
+ var i, len, keys;
9868
+ if (helpers.isArray(loopable)) {
9869
+ len = loopable.length;
9870
+ if (reverse) {
9871
+ for (i = len - 1; i >= 0; i--) {
9872
+ fn.call(thisArg, loopable[i], i);
9873
+ }
9874
+ } else {
9875
+ for (i = 0; i < len; i++) {
9876
+ fn.call(thisArg, loopable[i], i);
9877
+ }
9878
+ }
9879
+ } else if (helpers.isObject(loopable)) {
9880
+ keys = Object.keys(loopable);
9881
+ len = keys.length;
9882
+ for (i = 0; i < len; i++) {
9883
+ fn.call(thisArg, loopable[keys[i]], keys[i]);
9884
+ }
9885
+ }
9886
+ },
9887
+
9888
+ /**
9889
+ * Returns true if the `a0` and `a1` arrays have the same content, else returns false.
9890
+ * @see http://stackoverflow.com/a/14853974
9891
+ * @param {Array} a0 - The array to compare
9892
+ * @param {Array} a1 - The array to compare
9893
+ * @returns {Boolean}
9894
+ */
9895
+ arrayEquals: function(a0, a1) {
9896
+ var i, ilen, v0, v1;
9897
+
9898
+ if (!a0 || !a1 || a0.length !== a1.length) {
9899
+ return false;
9900
+ }
9901
+
9902
+ for (i = 0, ilen = a0.length; i < ilen; ++i) {
9903
+ v0 = a0[i];
9904
+ v1 = a1[i];
9905
+
9906
+ if (v0 instanceof Array && v1 instanceof Array) {
9907
+ if (!helpers.arrayEquals(v0, v1)) {
9908
+ return false;
9909
+ }
9910
+ } else if (v0 !== v1) {
9911
+ // NOTE: two different object instances will never be equal: {x:20} != {x:20}
9912
+ return false;
9913
+ }
9914
+ }
9915
+
9916
+ return true;
9917
+ },
9918
+
9919
+ /**
9920
+ * Returns a deep copy of `source` without keeping references on objects and arrays.
9921
+ * @param {*} source - The value to clone.
9922
+ * @returns {*}
9923
+ */
9924
+ clone: function(source) {
9925
+ if (helpers.isArray(source)) {
9926
+ return source.map(helpers.clone);
9927
+ }
9928
+
9929
+ if (helpers.isObject(source)) {
9930
+ var target = {};
9931
+ var keys = Object.keys(source);
9932
+ var klen = keys.length;
9933
+ var k = 0;
9934
+
9935
+ for (; k < klen; ++k) {
9936
+ target[keys[k]] = helpers.clone(source[keys[k]]);
9937
+ }
9938
+
9939
+ return target;
9940
+ }
9941
+
9942
+ return source;
9943
+ },
9944
+
9945
+ /**
9946
+ * The default merger when Chart.helpers.merge is called without merger option.
9947
+ * Note(SB): this method is also used by configMerge and scaleMerge as fallback.
9948
+ * @private
9949
+ */
9950
+ _merger: function(key, target, source, options) {
9951
+ var tval = target[key];
9952
+ var sval = source[key];
9953
+
9954
+ if (helpers.isObject(tval) && helpers.isObject(sval)) {
9955
+ helpers.merge(tval, sval, options);
9956
+ } else {
9957
+ target[key] = helpers.clone(sval);
9958
+ }
9959
+ },
9960
+
9961
+ /**
9962
+ * Merges source[key] in target[key] only if target[key] is undefined.
9963
+ * @private
9964
+ */
9965
+ _mergerIf: function(key, target, source) {
9966
+ var tval = target[key];
9967
+ var sval = source[key];
9968
+
9969
+ if (helpers.isObject(tval) && helpers.isObject(sval)) {
9970
+ helpers.mergeIf(tval, sval);
9971
+ } else if (!target.hasOwnProperty(key)) {
9972
+ target[key] = helpers.clone(sval);
9973
+ }
9974
+ },
9975
+
9976
+ /**
9977
+ * Recursively deep copies `source` properties into `target` with the given `options`.
9978
+ * IMPORTANT: `target` is not cloned and will be updated with `source` properties.
9979
+ * @param {Object} target - The target object in which all sources are merged into.
9980
+ * @param {Object|Array(Object)} source - Object(s) to merge into `target`.
9981
+ * @param {Object} [options] - Merging options:
9982
+ * @param {Function} [options.merger] - The merge method (key, target, source, options)
9983
+ * @returns {Object} The `target` object.
9984
+ */
9985
+ merge: function(target, source, options) {
9986
+ var sources = helpers.isArray(source) ? source : [source];
9987
+ var ilen = sources.length;
9988
+ var merge, i, keys, klen, k;
9989
+
9990
+ if (!helpers.isObject(target)) {
9991
+ return target;
9992
+ }
9993
+
9994
+ options = options || {};
9995
+ merge = options.merger || helpers._merger;
9996
+
9997
+ for (i = 0; i < ilen; ++i) {
9998
+ source = sources[i];
9999
+ if (!helpers.isObject(source)) {
10000
+ continue;
10001
+ }
10002
+
10003
+ keys = Object.keys(source);
10004
+ for (k = 0, klen = keys.length; k < klen; ++k) {
10005
+ merge(keys[k], target, source, options);
10006
+ }
10007
+ }
10008
+
10009
+ return target;
10010
+ },
10011
+
10012
+ /**
10013
+ * Recursively deep copies `source` properties into `target` *only* if not defined in target.
10014
+ * IMPORTANT: `target` is not cloned and will be updated with `source` properties.
10015
+ * @param {Object} target - The target object in which all sources are merged into.
10016
+ * @param {Object|Array(Object)} source - Object(s) to merge into `target`.
10017
+ * @returns {Object} The `target` object.
10018
+ */
10019
+ mergeIf: function(target, source) {
10020
+ return helpers.merge(target, source, {merger: helpers._mergerIf});
10021
+ },
10022
+
10023
+ /**
10024
+ * Applies the contents of two or more objects together into the first object.
10025
+ * @param {Object} target - The target object in which all objects are merged into.
10026
+ * @param {Object} arg1 - Object containing additional properties to merge in target.
10027
+ * @param {Object} argN - Additional objects containing properties to merge in target.
10028
+ * @returns {Object} The `target` object.
10029
+ */
10030
+ extend: function(target) {
10031
+ var setFn = function(value, key) {
10032
+ target[key] = value;
10033
+ };
10034
+ for (var i = 1, ilen = arguments.length; i < ilen; ++i) {
10035
+ helpers.each(arguments[i], setFn);
10036
+ }
10037
+ return target;
10038
+ },
10039
+
10040
+ /**
10041
+ * Basic javascript inheritance based on the model created in Backbone.js
10042
+ */
10043
+ inherits: function(extensions) {
10044
+ var me = this;
10045
+ var ChartElement = (extensions && extensions.hasOwnProperty('constructor')) ? extensions.constructor : function() {
10046
+ return me.apply(this, arguments);
10047
+ };
10048
+
10049
+ var Surrogate = function() {
10050
+ this.constructor = ChartElement;
10051
+ };
10052
+
10053
+ Surrogate.prototype = me.prototype;
10054
+ ChartElement.prototype = new Surrogate();
10055
+ ChartElement.extend = helpers.inherits;
10056
+
10057
+ if (extensions) {
10058
+ helpers.extend(ChartElement.prototype, extensions);
10059
+ }
10060
+
10061
+ ChartElement.__super__ = me.prototype;
10062
+ return ChartElement;
10063
+ }
10064
+ };
10065
+
10066
+ module.exports = helpers;
10067
+
10068
+ // DEPRECATIONS
10069
+
10070
+ /**
10071
+ * Provided for backward compatibility, use Chart.helpers.callback instead.
10072
+ * @function Chart.helpers.callCallback
10073
+ * @deprecated since version 2.6.0
10074
+ * @todo remove at version 3
10075
+ * @private
10076
+ */
10077
+ helpers.callCallback = helpers.callback;
10078
+
10079
+ /**
10080
+ * Provided for backward compatibility, use Array.prototype.indexOf instead.
10081
+ * Array.prototype.indexOf compatibility: Chrome, Opera, Safari, FF1.5+, IE9+
10082
+ * @function Chart.helpers.indexOf
10083
+ * @deprecated since version 2.7.0
10084
+ * @todo remove at version 3
10085
+ * @private
10086
+ */
10087
+ helpers.indexOf = function(array, item, fromIndex) {
10088
+ return Array.prototype.indexOf.call(array, item, fromIndex);
10089
+ };
10090
+
10091
+ /**
10092
+ * Provided for backward compatibility, use Chart.helpers.valueOrDefault instead.
10093
+ * @function Chart.helpers.getValueOrDefault
10094
+ * @deprecated since version 2.7.0
10095
+ * @todo remove at version 3
10096
+ * @private
10097
+ */
10098
+ helpers.getValueOrDefault = helpers.valueOrDefault;
10099
+
10100
+ /**
10101
+ * Provided for backward compatibility, use Chart.helpers.valueAtIndexOrDefault instead.
10102
+ * @function Chart.helpers.getValueAtIndexOrDefault
10103
+ * @deprecated since version 2.7.0
10104
+ * @todo remove at version 3
10105
+ * @private
10106
+ */
10107
+ helpers.getValueAtIndexOrDefault = helpers.valueAtIndexOrDefault;
10108
+
10109
+ },{}],43:[function(require,module,exports){
10110
+ 'use strict';
10111
+
10112
+ var helpers = require(42);
10113
+
10114
+ /**
10115
+ * Easing functions adapted from Robert Penner's easing equations.
10116
+ * @namespace Chart.helpers.easingEffects
10117
+ * @see http://www.robertpenner.com/easing/
10118
+ */
10119
+ var effects = {
10120
+ linear: function(t) {
10121
+ return t;
10122
+ },
10123
+
10124
+ easeInQuad: function(t) {
10125
+ return t * t;
10126
+ },
10127
+
10128
+ easeOutQuad: function(t) {
10129
+ return -t * (t - 2);
10130
+ },
10131
+
10132
+ easeInOutQuad: function(t) {
10133
+ if ((t /= 0.5) < 1) {
10134
+ return 0.5 * t * t;
10135
+ }
10136
+ return -0.5 * ((--t) * (t - 2) - 1);
10137
+ },
10138
+
10139
+ easeInCubic: function(t) {
10140
+ return t * t * t;
10141
+ },
10142
+
10143
+ easeOutCubic: function(t) {
10144
+ return (t = t - 1) * t * t + 1;
10145
+ },
10146
+
10147
+ easeInOutCubic: function(t) {
10148
+ if ((t /= 0.5) < 1) {
10149
+ return 0.5 * t * t * t;
10150
+ }
10151
+ return 0.5 * ((t -= 2) * t * t + 2);
10152
+ },
10153
+
10154
+ easeInQuart: function(t) {
10155
+ return t * t * t * t;
10156
+ },
10157
+
10158
+ easeOutQuart: function(t) {
10159
+ return -((t = t - 1) * t * t * t - 1);
10160
+ },
10161
+
10162
+ easeInOutQuart: function(t) {
10163
+ if ((t /= 0.5) < 1) {
10164
+ return 0.5 * t * t * t * t;
10165
+ }
10166
+ return -0.5 * ((t -= 2) * t * t * t - 2);
10167
+ },
10168
+
10169
+ easeInQuint: function(t) {
10170
+ return t * t * t * t * t;
10171
+ },
10172
+
10173
+ easeOutQuint: function(t) {
10174
+ return (t = t - 1) * t * t * t * t + 1;
10175
+ },
10176
+
10177
+ easeInOutQuint: function(t) {
10178
+ if ((t /= 0.5) < 1) {
10179
+ return 0.5 * t * t * t * t * t;
10180
+ }
10181
+ return 0.5 * ((t -= 2) * t * t * t * t + 2);
10182
+ },
10183
+
10184
+ easeInSine: function(t) {
10185
+ return -Math.cos(t * (Math.PI / 2)) + 1;
10186
+ },
10187
+
10188
+ easeOutSine: function(t) {
10189
+ return Math.sin(t * (Math.PI / 2));
10190
+ },
10191
+
10192
+ easeInOutSine: function(t) {
10193
+ return -0.5 * (Math.cos(Math.PI * t) - 1);
10194
+ },
10195
+
10196
+ easeInExpo: function(t) {
10197
+ return (t === 0) ? 0 : Math.pow(2, 10 * (t - 1));
10198
+ },
10199
+
10200
+ easeOutExpo: function(t) {
10201
+ return (t === 1) ? 1 : -Math.pow(2, -10 * t) + 1;
10202
+ },
10203
+
10204
+ easeInOutExpo: function(t) {
10205
+ if (t === 0) {
10206
+ return 0;
10207
+ }
10208
+ if (t === 1) {
10209
+ return 1;
10210
+ }
10211
+ if ((t /= 0.5) < 1) {
10212
+ return 0.5 * Math.pow(2, 10 * (t - 1));
10213
+ }
10214
+ return 0.5 * (-Math.pow(2, -10 * --t) + 2);
10215
+ },
10216
+
10217
+ easeInCirc: function(t) {
10218
+ if (t >= 1) {
10219
+ return t;
10220
+ }
10221
+ return -(Math.sqrt(1 - t * t) - 1);
10222
+ },
10223
+
10224
+ easeOutCirc: function(t) {
10225
+ return Math.sqrt(1 - (t = t - 1) * t);
10226
+ },
10227
+
10228
+ easeInOutCirc: function(t) {
10229
+ if ((t /= 0.5) < 1) {
10230
+ return -0.5 * (Math.sqrt(1 - t * t) - 1);
10231
+ }
10232
+ return 0.5 * (Math.sqrt(1 - (t -= 2) * t) + 1);
10233
+ },
10234
+
10235
+ easeInElastic: function(t) {
10236
+ var s = 1.70158;
10237
+ var p = 0;
10238
+ var a = 1;
10239
+ if (t === 0) {
10240
+ return 0;
10241
+ }
10242
+ if (t === 1) {
10243
+ return 1;
10244
+ }
10245
+ if (!p) {
10246
+ p = 0.3;
10247
+ }
10248
+ if (a < 1) {
10249
+ a = 1;
10250
+ s = p / 4;
10251
+ } else {
10252
+ s = p / (2 * Math.PI) * Math.asin(1 / a);
10253
+ }
10254
+ return -(a * Math.pow(2, 10 * (t -= 1)) * Math.sin((t - s) * (2 * Math.PI) / p));
10255
+ },
10256
+
10257
+ easeOutElastic: function(t) {
10258
+ var s = 1.70158;
10259
+ var p = 0;
10260
+ var a = 1;
10261
+ if (t === 0) {
10262
+ return 0;
10263
+ }
10264
+ if (t === 1) {
10265
+ return 1;
10266
+ }
10267
+ if (!p) {
10268
+ p = 0.3;
10269
+ }
10270
+ if (a < 1) {
10271
+ a = 1;
10272
+ s = p / 4;
10273
+ } else {
10274
+ s = p / (2 * Math.PI) * Math.asin(1 / a);
10275
+ }
10276
+ return a * Math.pow(2, -10 * t) * Math.sin((t - s) * (2 * Math.PI) / p) + 1;
10277
+ },
10278
+
10279
+ easeInOutElastic: function(t) {
10280
+ var s = 1.70158;
10281
+ var p = 0;
10282
+ var a = 1;
10283
+ if (t === 0) {
10284
+ return 0;
10285
+ }
10286
+ if ((t /= 0.5) === 2) {
10287
+ return 1;
10288
+ }
10289
+ if (!p) {
10290
+ p = 0.45;
10291
+ }
10292
+ if (a < 1) {
10293
+ a = 1;
10294
+ s = p / 4;
10295
+ } else {
10296
+ s = p / (2 * Math.PI) * Math.asin(1 / a);
10297
+ }
10298
+ if (t < 1) {
10299
+ return -0.5 * (a * Math.pow(2, 10 * (t -= 1)) * Math.sin((t - s) * (2 * Math.PI) / p));
10300
+ }
10301
+ return a * Math.pow(2, -10 * (t -= 1)) * Math.sin((t - s) * (2 * Math.PI) / p) * 0.5 + 1;
10302
+ },
10303
+ easeInBack: function(t) {
10304
+ var s = 1.70158;
10305
+ return t * t * ((s + 1) * t - s);
10306
+ },
10307
+
10308
+ easeOutBack: function(t) {
10309
+ var s = 1.70158;
10310
+ return (t = t - 1) * t * ((s + 1) * t + s) + 1;
10311
+ },
10312
+
10313
+ easeInOutBack: function(t) {
10314
+ var s = 1.70158;
10315
+ if ((t /= 0.5) < 1) {
10316
+ return 0.5 * (t * t * (((s *= (1.525)) + 1) * t - s));
10317
+ }
10318
+ return 0.5 * ((t -= 2) * t * (((s *= (1.525)) + 1) * t + s) + 2);
10319
+ },
10320
+
10321
+ easeInBounce: function(t) {
10322
+ return 1 - effects.easeOutBounce(1 - t);
10323
+ },
10324
+
10325
+ easeOutBounce: function(t) {
10326
+ if (t < (1 / 2.75)) {
10327
+ return 7.5625 * t * t;
10328
+ }
10329
+ if (t < (2 / 2.75)) {
10330
+ return 7.5625 * (t -= (1.5 / 2.75)) * t + 0.75;
10331
+ }
10332
+ if (t < (2.5 / 2.75)) {
10333
+ return 7.5625 * (t -= (2.25 / 2.75)) * t + 0.9375;
10334
+ }
10335
+ return 7.5625 * (t -= (2.625 / 2.75)) * t + 0.984375;
10336
+ },
10337
+
10338
+ easeInOutBounce: function(t) {
10339
+ if (t < 0.5) {
10340
+ return effects.easeInBounce(t * 2) * 0.5;
10341
+ }
10342
+ return effects.easeOutBounce(t * 2 - 1) * 0.5 + 0.5;
10343
+ }
10344
+ };
10345
+
10346
+ module.exports = {
10347
+ effects: effects
10348
+ };
10349
+
10350
+ // DEPRECATIONS
10351
+
10352
+ /**
10353
+ * Provided for backward compatibility, use Chart.helpers.easing.effects instead.
10354
+ * @function Chart.helpers.easingEffects
10355
+ * @deprecated since version 2.7.0
10356
+ * @todo remove at version 3
10357
+ * @private
10358
+ */
10359
+ helpers.easingEffects = effects;
10360
+
10361
+ },{"42":42}],44:[function(require,module,exports){
10362
+ 'use strict';
10363
+
10364
+ var helpers = require(42);
10365
+
10366
+ /**
10367
+ * @alias Chart.helpers.options
10368
+ * @namespace
10369
+ */
10370
+ module.exports = {
10371
+ /**
10372
+ * Converts the given line height `value` in pixels for a specific font `size`.
10373
+ * @param {Number|String} value - The lineHeight to parse (eg. 1.6, '14px', '75%', '1.6em').
10374
+ * @param {Number} size - The font size (in pixels) used to resolve relative `value`.
10375
+ * @returns {Number} The effective line height in pixels (size * 1.2 if value is invalid).
10376
+ * @see https://developer.mozilla.org/en-US/docs/Web/CSS/line-height
10377
+ * @since 2.7.0
10378
+ */
10379
+ toLineHeight: function(value, size) {
10380
+ var matches = ('' + value).match(/^(normal|(\d+(?:\.\d+)?)(px|em|%)?)$/);
10381
+ if (!matches || matches[1] === 'normal') {
10382
+ return size * 1.2;
10383
+ }
10384
+
10385
+ value = +matches[2];
10386
+
10387
+ switch (matches[3]) {
10388
+ case 'px':
10389
+ return value;
10390
+ case '%':
10391
+ value /= 100;
10392
+ break;
10393
+ default:
10394
+ break;
10395
+ }
10396
+
10397
+ return size * value;
10398
+ },
10399
+
10400
+ /**
10401
+ * Converts the given value into a padding object with pre-computed width/height.
10402
+ * @param {Number|Object} value - If a number, set the value to all TRBL component,
10403
+ * else, if and object, use defined properties and sets undefined ones to 0.
10404
+ * @returns {Object} The padding values (top, right, bottom, left, width, height)
10405
+ * @since 2.7.0
10406
+ */
10407
+ toPadding: function(value) {
10408
+ var t, r, b, l;
10409
+
10410
+ if (helpers.isObject(value)) {
10411
+ t = +value.top || 0;
10412
+ r = +value.right || 0;
10413
+ b = +value.bottom || 0;
10414
+ l = +value.left || 0;
10415
+ } else {
10416
+ t = r = b = l = +value || 0;
10417
+ }
10418
+
10419
+ return {
10420
+ top: t,
10421
+ right: r,
10422
+ bottom: b,
10423
+ left: l,
10424
+ height: t + b,
10425
+ width: l + r
10426
+ };
10427
+ },
10428
+
10429
+ /**
10430
+ * Evaluates the given `inputs` sequentially and returns the first defined value.
10431
+ * @param {Array[]} inputs - An array of values, falling back to the last value.
10432
+ * @param {Object} [context] - If defined and the current value is a function, the value
10433
+ * is called with `context` as first argument and the result becomes the new input.
10434
+ * @param {Number} [index] - If defined and the current value is an array, the value
10435
+ * at `index` become the new input.
10436
+ * @since 2.7.0
10437
+ */
10438
+ resolve: function(inputs, context, index) {
10439
+ var i, ilen, value;
10440
+
10441
+ for (i = 0, ilen = inputs.length; i < ilen; ++i) {
10442
+ value = inputs[i];
10443
+ if (value === undefined) {
10444
+ continue;
10445
+ }
10446
+ if (context !== undefined && typeof value === 'function') {
10447
+ value = value(context);
10448
+ }
10449
+ if (index !== undefined && helpers.isArray(value)) {
10450
+ value = value[index];
10451
+ }
10452
+ if (value !== undefined) {
10453
+ return value;
10454
+ }
10455
+ }
10456
+ }
10457
+ };
10458
+
10459
+ },{"42":42}],45:[function(require,module,exports){
10460
+ 'use strict';
10461
+
10462
+ module.exports = require(42);
10463
+ module.exports.easing = require(43);
10464
+ module.exports.canvas = require(41);
10465
+ module.exports.options = require(44);
10466
+
10467
+ },{"41":41,"42":42,"43":43,"44":44}],46:[function(require,module,exports){
10468
+ /**
10469
+ * Platform fallback implementation (minimal).
10470
+ * @see https://github.com/chartjs/Chart.js/pull/4591#issuecomment-319575939
10471
+ */
10472
+
10473
+ module.exports = {
10474
+ acquireContext: function(item) {
10475
+ if (item && item.canvas) {
10476
+ // Support for any object associated to a canvas (including a context2d)
10477
+ item = item.canvas;
10478
+ }
10479
+
10480
+ return item && item.getContext('2d') || null;
10481
+ }
10482
+ };
10483
+
10484
+ },{}],47:[function(require,module,exports){
10485
+ /**
10486
+ * Chart.Platform implementation for targeting a web browser
10487
+ */
10488
+
10489
+ 'use strict';
10490
+
10491
+ var helpers = require(45);
10492
+
10493
+ var EXPANDO_KEY = '$chartjs';
10494
+ var CSS_PREFIX = 'chartjs-';
10495
+ var CSS_RENDER_MONITOR = CSS_PREFIX + 'render-monitor';
10496
+ var CSS_RENDER_ANIMATION = CSS_PREFIX + 'render-animation';
10497
+ var ANIMATION_START_EVENTS = ['animationstart', 'webkitAnimationStart'];
10498
+
10499
+ /**
10500
+ * DOM event types -> Chart.js event types.
10501
+ * Note: only events with different types are mapped.
10502
+ * @see https://developer.mozilla.org/en-US/docs/Web/Events
10503
+ */
10504
+ var EVENT_TYPES = {
10505
+ touchstart: 'mousedown',
10506
+ touchmove: 'mousemove',
10507
+ touchend: 'mouseup',
10508
+ pointerenter: 'mouseenter',
10509
+ pointerdown: 'mousedown',
10510
+ pointermove: 'mousemove',
10511
+ pointerup: 'mouseup',
10512
+ pointerleave: 'mouseout',
10513
+ pointerout: 'mouseout'
10514
+ };
10515
+
10516
+ /**
10517
+ * The "used" size is the final value of a dimension property after all calculations have
10518
+ * been performed. This method uses the computed style of `element` but returns undefined
10519
+ * if the computed style is not expressed in pixels. That can happen in some cases where
10520
+ * `element` has a size relative to its parent and this last one is not yet displayed,
10521
+ * for example because of `display: none` on a parent node.
10522
+ * @see https://developer.mozilla.org/en-US/docs/Web/CSS/used_value
10523
+ * @returns {Number} Size in pixels or undefined if unknown.
10524
+ */
10525
+ function readUsedSize(element, property) {
10526
+ var value = helpers.getStyle(element, property);
10527
+ var matches = value && value.match(/^(\d+)(\.\d+)?px$/);
10528
+ return matches ? Number(matches[1]) : undefined;
10529
+ }
10530
+
10531
+ /**
10532
+ * Initializes the canvas style and render size without modifying the canvas display size,
10533
+ * since responsiveness is handled by the controller.resize() method. The config is used
10534
+ * to determine the aspect ratio to apply in case no explicit height has been specified.
10535
+ */
10536
+ function initCanvas(canvas, config) {
10537
+ var style = canvas.style;
10538
+
10539
+ // NOTE(SB) canvas.getAttribute('width') !== canvas.width: in the first case it
10540
+ // returns null or '' if no explicit value has been set to the canvas attribute.
10541
+ var renderHeight = canvas.getAttribute('height');
10542
+ var renderWidth = canvas.getAttribute('width');
10543
+
10544
+ // Chart.js modifies some canvas values that we want to restore on destroy
10545
+ canvas[EXPANDO_KEY] = {
10546
+ initial: {
10547
+ height: renderHeight,
10548
+ width: renderWidth,
10549
+ style: {
10550
+ display: style.display,
10551
+ height: style.height,
10552
+ width: style.width
10553
+ }
10554
+ }
10555
+ };
10556
+
10557
+ // Force canvas to display as block to avoid extra space caused by inline
10558
+ // elements, which would interfere with the responsive resize process.
10559
+ // https://github.com/chartjs/Chart.js/issues/2538
10560
+ style.display = style.display || 'block';
10561
+
10562
+ if (renderWidth === null || renderWidth === '') {
10563
+ var displayWidth = readUsedSize(canvas, 'width');
10564
+ if (displayWidth !== undefined) {
10565
+ canvas.width = displayWidth;
10566
+ }
10567
+ }
10568
+
10569
+ if (renderHeight === null || renderHeight === '') {
10570
+ if (canvas.style.height === '') {
10571
+ // If no explicit render height and style height, let's apply the aspect ratio,
10572
+ // which one can be specified by the user but also by charts as default option
10573
+ // (i.e. options.aspectRatio). If not specified, use canvas aspect ratio of 2.
10574
+ canvas.height = canvas.width / (config.options.aspectRatio || 2);
10575
+ } else {
10576
+ var displayHeight = readUsedSize(canvas, 'height');
10577
+ if (displayWidth !== undefined) {
10578
+ canvas.height = displayHeight;
10579
+ }
10580
+ }
10581
+ }
10582
+
10583
+ return canvas;
10584
+ }
10585
+
10586
+ /**
10587
+ * Detects support for options object argument in addEventListener.
10588
+ * https://developer.mozilla.org/en-US/docs/Web/API/EventTarget/addEventListener#Safely_detecting_option_support
10589
+ * @private
10590
+ */
10591
+ var supportsEventListenerOptions = (function() {
10592
+ var supports = false;
10593
+ try {
10594
+ var options = Object.defineProperty({}, 'passive', {
10595
+ get: function() {
10596
+ supports = true;
10597
+ }
10598
+ });
10599
+ window.addEventListener('e', null, options);
10600
+ } catch (e) {
10601
+ // continue regardless of error
10602
+ }
10603
+ return supports;
10604
+ }());
10605
+
10606
+ // Default passive to true as expected by Chrome for 'touchstart' and 'touchend' events.
10607
+ // https://github.com/chartjs/Chart.js/issues/4287
10608
+ var eventListenerOptions = supportsEventListenerOptions ? {passive: true} : false;
10609
+
10610
+ function addEventListener(node, type, listener) {
10611
+ node.addEventListener(type, listener, eventListenerOptions);
10612
+ }
10613
+
10614
+ function removeEventListener(node, type, listener) {
10615
+ node.removeEventListener(type, listener, eventListenerOptions);
10616
+ }
10617
+
10618
+ function createEvent(type, chart, x, y, nativeEvent) {
10619
+ return {
10620
+ type: type,
10621
+ chart: chart,
10622
+ native: nativeEvent || null,
10623
+ x: x !== undefined ? x : null,
10624
+ y: y !== undefined ? y : null,
10625
+ };
10626
+ }
10627
+
10628
+ function fromNativeEvent(event, chart) {
10629
+ var type = EVENT_TYPES[event.type] || event.type;
10630
+ var pos = helpers.getRelativePosition(event, chart);
10631
+ return createEvent(type, chart, pos.x, pos.y, event);
10632
+ }
10633
+
10634
+ function throttled(fn, thisArg) {
10635
+ var ticking = false;
10636
+ var args = [];
10637
+
10638
+ return function() {
10639
+ args = Array.prototype.slice.call(arguments);
10640
+ thisArg = thisArg || this;
10641
+
10642
+ if (!ticking) {
10643
+ ticking = true;
10644
+ helpers.requestAnimFrame.call(window, function() {
10645
+ ticking = false;
10646
+ fn.apply(thisArg, args);
10647
+ });
10648
+ }
10649
+ };
10650
+ }
10651
+
10652
+ // Implementation based on https://github.com/marcj/css-element-queries
10653
+ function createResizer(handler) {
10654
+ var resizer = document.createElement('div');
10655
+ var cls = CSS_PREFIX + 'size-monitor';
10656
+ var maxSize = 1000000;
10657
+ var style =
10658
+ 'position:absolute;' +
10659
+ 'left:0;' +
10660
+ 'top:0;' +
10661
+ 'right:0;' +
10662
+ 'bottom:0;' +
10663
+ 'overflow:hidden;' +
10664
+ 'pointer-events:none;' +
10665
+ 'visibility:hidden;' +
10666
+ 'z-index:-1;';
10667
+
10668
+ resizer.style.cssText = style;
10669
+ resizer.className = cls;
10670
+ resizer.innerHTML =
10671
+ '<div class="' + cls + '-expand" style="' + style + '">' +
10672
+ '<div style="' +
10673
+ 'position:absolute;' +
10674
+ 'width:' + maxSize + 'px;' +
10675
+ 'height:' + maxSize + 'px;' +
10676
+ 'left:0;' +
10677
+ 'top:0">' +
10678
+ '</div>' +
10679
+ '</div>' +
10680
+ '<div class="' + cls + '-shrink" style="' + style + '">' +
10681
+ '<div style="' +
10682
+ 'position:absolute;' +
10683
+ 'width:200%;' +
10684
+ 'height:200%;' +
10685
+ 'left:0; ' +
10686
+ 'top:0">' +
10687
+ '</div>' +
10688
+ '</div>';
10689
+
10690
+ var expand = resizer.childNodes[0];
10691
+ var shrink = resizer.childNodes[1];
10692
+
10693
+ resizer._reset = function() {
10694
+ expand.scrollLeft = maxSize;
10695
+ expand.scrollTop = maxSize;
10696
+ shrink.scrollLeft = maxSize;
10697
+ shrink.scrollTop = maxSize;
10698
+ };
10699
+ var onScroll = function() {
10700
+ resizer._reset();
10701
+ handler();
10702
+ };
10703
+
10704
+ addEventListener(expand, 'scroll', onScroll.bind(expand, 'expand'));
10705
+ addEventListener(shrink, 'scroll', onScroll.bind(shrink, 'shrink'));
10706
+
10707
+ return resizer;
10708
+ }
10709
+
10710
+ // https://davidwalsh.name/detect-node-insertion
10711
+ function watchForRender(node, handler) {
10712
+ var expando = node[EXPANDO_KEY] || (node[EXPANDO_KEY] = {});
10713
+ var proxy = expando.renderProxy = function(e) {
10714
+ if (e.animationName === CSS_RENDER_ANIMATION) {
10715
+ handler();
10716
+ }
10717
+ };
10718
+
10719
+ helpers.each(ANIMATION_START_EVENTS, function(type) {
10720
+ addEventListener(node, type, proxy);
10721
+ });
10722
+
10723
+ // #4737: Chrome might skip the CSS animation when the CSS_RENDER_MONITOR class
10724
+ // is removed then added back immediately (same animation frame?). Accessing the
10725
+ // `offsetParent` property will force a reflow and re-evaluate the CSS animation.
10726
+ // https://gist.github.com/paulirish/5d52fb081b3570c81e3a#box-metrics
10727
+ // https://github.com/chartjs/Chart.js/issues/4737
10728
+ expando.reflow = !!node.offsetParent;
10729
+
10730
+ node.classList.add(CSS_RENDER_MONITOR);
10731
+ }
10732
+
10733
+ function unwatchForRender(node) {
10734
+ var expando = node[EXPANDO_KEY] || {};
10735
+ var proxy = expando.renderProxy;
10736
+
10737
+ if (proxy) {
10738
+ helpers.each(ANIMATION_START_EVENTS, function(type) {
10739
+ removeEventListener(node, type, proxy);
10740
+ });
10741
+
10742
+ delete expando.renderProxy;
10743
+ }
10744
+
10745
+ node.classList.remove(CSS_RENDER_MONITOR);
10746
+ }
10747
+
10748
+ function addResizeListener(node, listener, chart) {
10749
+ var expando = node[EXPANDO_KEY] || (node[EXPANDO_KEY] = {});
10750
+
10751
+ // Let's keep track of this added resizer and thus avoid DOM query when removing it.
10752
+ var resizer = expando.resizer = createResizer(throttled(function() {
10753
+ if (expando.resizer) {
10754
+ return listener(createEvent('resize', chart));
10755
+ }
10756
+ }));
10757
+
10758
+ // The resizer needs to be attached to the node parent, so we first need to be
10759
+ // sure that `node` is attached to the DOM before injecting the resizer element.
10760
+ watchForRender(node, function() {
10761
+ if (expando.resizer) {
10762
+ var container = node.parentNode;
10763
+ if (container && container !== resizer.parentNode) {
10764
+ container.insertBefore(resizer, container.firstChild);
10765
+ }
10766
+
10767
+ // The container size might have changed, let's reset the resizer state.
10768
+ resizer._reset();
10769
+ }
10770
+ });
10771
+ }
10772
+
10773
+ function removeResizeListener(node) {
10774
+ var expando = node[EXPANDO_KEY] || {};
10775
+ var resizer = expando.resizer;
10776
+
10777
+ delete expando.resizer;
10778
+ unwatchForRender(node);
10779
+
10780
+ if (resizer && resizer.parentNode) {
10781
+ resizer.parentNode.removeChild(resizer);
10782
+ }
10783
+ }
10784
+
10785
+ function injectCSS(platform, css) {
10786
+ // http://stackoverflow.com/q/3922139
10787
+ var style = platform._style || document.createElement('style');
10788
+ if (!platform._style) {
10789
+ platform._style = style;
10790
+ css = '/* Chart.js */\n' + css;
10791
+ style.setAttribute('type', 'text/css');
10792
+ document.getElementsByTagName('head')[0].appendChild(style);
10793
+ }
10794
+
10795
+ style.appendChild(document.createTextNode(css));
10796
+ }
10797
+
10798
+ module.exports = {
10799
+ /**
10800
+ * This property holds whether this platform is enabled for the current environment.
10801
+ * Currently used by platform.js to select the proper implementation.
10802
+ * @private
10803
+ */
10804
+ _enabled: typeof window !== 'undefined' && typeof document !== 'undefined',
10805
+
10806
+ initialize: function() {
10807
+ var keyframes = 'from{opacity:0.99}to{opacity:1}';
10808
+
10809
+ injectCSS(this,
10810
+ // DOM rendering detection
10811
+ // https://davidwalsh.name/detect-node-insertion
10812
+ '@-webkit-keyframes ' + CSS_RENDER_ANIMATION + '{' + keyframes + '}' +
10813
+ '@keyframes ' + CSS_RENDER_ANIMATION + '{' + keyframes + '}' +
10814
+ '.' + CSS_RENDER_MONITOR + '{' +
10815
+ '-webkit-animation:' + CSS_RENDER_ANIMATION + ' 0.001s;' +
10816
+ 'animation:' + CSS_RENDER_ANIMATION + ' 0.001s;' +
10817
+ '}'
10818
+ );
10819
+ },
10820
+
10821
+ acquireContext: function(item, config) {
10822
+ if (typeof item === 'string') {
10823
+ item = document.getElementById(item);
10824
+ } else if (item.length) {
10825
+ // Support for array based queries (such as jQuery)
10826
+ item = item[0];
10827
+ }
10828
+
10829
+ if (item && item.canvas) {
10830
+ // Support for any object associated to a canvas (including a context2d)
10831
+ item = item.canvas;
10832
+ }
10833
+
10834
+ // To prevent canvas fingerprinting, some add-ons undefine the getContext
10835
+ // method, for example: https://github.com/kkapsner/CanvasBlocker
10836
+ // https://github.com/chartjs/Chart.js/issues/2807
10837
+ var context = item && item.getContext && item.getContext('2d');
10838
+
10839
+ // `instanceof HTMLCanvasElement/CanvasRenderingContext2D` fails when the item is
10840
+ // inside an iframe or when running in a protected environment. We could guess the
10841
+ // types from their toString() value but let's keep things flexible and assume it's
10842
+ // a sufficient condition if the item has a context2D which has item as `canvas`.
10843
+ // https://github.com/chartjs/Chart.js/issues/3887
10844
+ // https://github.com/chartjs/Chart.js/issues/4102
10845
+ // https://github.com/chartjs/Chart.js/issues/4152
10846
+ if (context && context.canvas === item) {
10847
+ initCanvas(item, config);
10848
+ return context;
10849
+ }
10850
+
10851
+ return null;
10852
+ },
10853
+
10854
+ releaseContext: function(context) {
10855
+ var canvas = context.canvas;
10856
+ if (!canvas[EXPANDO_KEY]) {
10857
+ return;
10858
+ }
10859
+
10860
+ var initial = canvas[EXPANDO_KEY].initial;
10861
+ ['height', 'width'].forEach(function(prop) {
10862
+ var value = initial[prop];
10863
+ if (helpers.isNullOrUndef(value)) {
10864
+ canvas.removeAttribute(prop);
10865
+ } else {
10866
+ canvas.setAttribute(prop, value);
10867
+ }
10868
+ });
10869
+
10870
+ helpers.each(initial.style || {}, function(value, key) {
10871
+ canvas.style[key] = value;
10872
+ });
10873
+
10874
+ // The canvas render size might have been changed (and thus the state stack discarded),
10875
+ // we can't use save() and restore() to restore the initial state. So make sure that at
10876
+ // least the canvas context is reset to the default state by setting the canvas width.
10877
+ // https://www.w3.org/TR/2011/WD-html5-20110525/the-canvas-element.html
10878
+ canvas.width = canvas.width;
10879
+
10880
+ delete canvas[EXPANDO_KEY];
10881
+ },
10882
+
10883
+ addEventListener: function(chart, type, listener) {
10884
+ var canvas = chart.canvas;
10885
+ if (type === 'resize') {
10886
+ // Note: the resize event is not supported on all browsers.
10887
+ addResizeListener(canvas, listener, chart);
10888
+ return;
10889
+ }
10890
+
10891
+ var expando = listener[EXPANDO_KEY] || (listener[EXPANDO_KEY] = {});
10892
+ var proxies = expando.proxies || (expando.proxies = {});
10893
+ var proxy = proxies[chart.id + '_' + type] = function(event) {
10894
+ listener(fromNativeEvent(event, chart));
10895
+ };
10896
+
10897
+ addEventListener(canvas, type, proxy);
10898
+ },
10899
+
10900
+ removeEventListener: function(chart, type, listener) {
10901
+ var canvas = chart.canvas;
10902
+ if (type === 'resize') {
10903
+ // Note: the resize event is not supported on all browsers.
10904
+ removeResizeListener(canvas, listener);
10905
+ return;
10906
+ }
10907
+
10908
+ var expando = listener[EXPANDO_KEY] || {};
10909
+ var proxies = expando.proxies || {};
10910
+ var proxy = proxies[chart.id + '_' + type];
10911
+ if (!proxy) {
10912
+ return;
10913
+ }
10914
+
10915
+ removeEventListener(canvas, type, proxy);
10916
+ }
10917
+ };
10918
+
10919
+ // DEPRECATIONS
10920
+
10921
+ /**
10922
+ * Provided for backward compatibility, use EventTarget.addEventListener instead.
10923
+ * EventTarget.addEventListener compatibility: Chrome, Opera 7, Safari, FF1.5+, IE9+
10924
+ * @see https://developer.mozilla.org/en-US/docs/Web/API/EventTarget/addEventListener
10925
+ * @function Chart.helpers.addEvent
10926
+ * @deprecated since version 2.7.0
10927
+ * @todo remove at version 3
10928
+ * @private
10929
+ */
10930
+ helpers.addEvent = addEventListener;
10931
+
10932
+ /**
10933
+ * Provided for backward compatibility, use EventTarget.removeEventListener instead.
10934
+ * EventTarget.removeEventListener compatibility: Chrome, Opera 7, Safari, FF1.5+, IE9+
10935
+ * @see https://developer.mozilla.org/en-US/docs/Web/API/EventTarget/removeEventListener
10936
+ * @function Chart.helpers.removeEvent
10937
+ * @deprecated since version 2.7.0
10938
+ * @todo remove at version 3
10939
+ * @private
10940
+ */
10941
+ helpers.removeEvent = removeEventListener;
10942
+
10943
+ },{"45":45}],48:[function(require,module,exports){
10944
+ 'use strict';
10945
+
10946
+ var helpers = require(45);
10947
+ var basic = require(46);
10948
+ var dom = require(47);
10949
+
10950
+ // @TODO Make possible to select another platform at build time.
10951
+ var implementation = dom._enabled ? dom : basic;
10952
+
10953
+ /**
10954
+ * @namespace Chart.platform
10955
+ * @see https://chartjs.gitbooks.io/proposals/content/Platform.html
10956
+ * @since 2.4.0
10957
+ */
10958
+ module.exports = helpers.extend({
10959
+ /**
10960
+ * @since 2.7.0
10961
+ */
10962
+ initialize: function() {},
10963
+
10964
+ /**
10965
+ * Called at chart construction time, returns a context2d instance implementing
10966
+ * the [W3C Canvas 2D Context API standard]{@link https://www.w3.org/TR/2dcontext/}.
10967
+ * @param {*} item - The native item from which to acquire context (platform specific)
10968
+ * @param {Object} options - The chart options
10969
+ * @returns {CanvasRenderingContext2D} context2d instance
10970
+ */
10971
+ acquireContext: function() {},
10972
+
10973
+ /**
10974
+ * Called at chart destruction time, releases any resources associated to the context
10975
+ * previously returned by the acquireContext() method.
10976
+ * @param {CanvasRenderingContext2D} context - The context2d instance
10977
+ * @returns {Boolean} true if the method succeeded, else false
10978
+ */
10979
+ releaseContext: function() {},
10980
+
10981
+ /**
10982
+ * Registers the specified listener on the given chart.
10983
+ * @param {Chart} chart - Chart from which to listen for event
10984
+ * @param {String} type - The ({@link IEvent}) type to listen for
10985
+ * @param {Function} listener - Receives a notification (an object that implements
10986
+ * the {@link IEvent} interface) when an event of the specified type occurs.
10987
+ */
10988
+ addEventListener: function() {},
10989
+
10990
+ /**
10991
+ * Removes the specified listener previously registered with addEventListener.
10992
+ * @param {Chart} chart -Chart from which to remove the listener
10993
+ * @param {String} type - The ({@link IEvent}) type to remove
10994
+ * @param {Function} listener - The listener function to remove from the event target.
10995
+ */
10996
+ removeEventListener: function() {}
10997
+
10998
+ }, implementation);
10999
+
11000
+ /**
11001
+ * @interface IPlatform
11002
+ * Allows abstracting platform dependencies away from the chart
11003
+ * @borrows Chart.platform.acquireContext as acquireContext
11004
+ * @borrows Chart.platform.releaseContext as releaseContext
11005
+ * @borrows Chart.platform.addEventListener as addEventListener
11006
+ * @borrows Chart.platform.removeEventListener as removeEventListener
11007
+ */
11008
+
11009
+ /**
11010
+ * @interface IEvent
11011
+ * @prop {String} type - The event type name, possible values are:
11012
+ * 'contextmenu', 'mouseenter', 'mousedown', 'mousemove', 'mouseup', 'mouseout',
11013
+ * 'click', 'dblclick', 'keydown', 'keypress', 'keyup' and 'resize'
11014
+ * @prop {*} native - The original native event (null for emulated events, e.g. 'resize')
11015
+ * @prop {Number} x - The mouse x position, relative to the canvas (null for incompatible events)
11016
+ * @prop {Number} y - The mouse y position, relative to the canvas (null for incompatible events)
11017
+ */
11018
+
11019
+ },{"45":45,"46":46,"47":47}],49:[function(require,module,exports){
11020
+ /**
11021
+ * Plugin based on discussion from the following Chart.js issues:
11022
+ * @see https://github.com/chartjs/Chart.js/issues/2380#issuecomment-279961569
11023
+ * @see https://github.com/chartjs/Chart.js/issues/2440#issuecomment-256461897
11024
+ */
11025
+
11026
+ 'use strict';
11027
+
11028
+ var defaults = require(25);
11029
+ var elements = require(40);
11030
+ var helpers = require(45);
11031
+
11032
+ defaults._set('global', {
11033
+ plugins: {
11034
+ filler: {
11035
+ propagate: true
11036
+ }
11037
+ }
11038
+ });
11039
+
11040
+ module.exports = function() {
11041
+
11042
+ var mappers = {
11043
+ dataset: function(source) {
11044
+ var index = source.fill;
11045
+ var chart = source.chart;
11046
+ var meta = chart.getDatasetMeta(index);
11047
+ var visible = meta && chart.isDatasetVisible(index);
11048
+ var points = (visible && meta.dataset._children) || [];
11049
+ var length = points.length || 0;
11050
+
11051
+ return !length ? null : function(point, i) {
11052
+ return (i < length && points[i]._view) || null;
11053
+ };
11054
+ },
11055
+
11056
+ boundary: function(source) {
11057
+ var boundary = source.boundary;
11058
+ var x = boundary ? boundary.x : null;
11059
+ var y = boundary ? boundary.y : null;
11060
+
11061
+ return function(point) {
11062
+ return {
11063
+ x: x === null ? point.x : x,
11064
+ y: y === null ? point.y : y,
11065
+ };
11066
+ };
11067
+ }
11068
+ };
11069
+
11070
+ // @todo if (fill[0] === '#')
11071
+ function decodeFill(el, index, count) {
11072
+ var model = el._model || {};
11073
+ var fill = model.fill;
11074
+ var target;
11075
+
11076
+ if (fill === undefined) {
11077
+ fill = !!model.backgroundColor;
11078
+ }
11079
+
11080
+ if (fill === false || fill === null) {
11081
+ return false;
11082
+ }
11083
+
11084
+ if (fill === true) {
11085
+ return 'origin';
11086
+ }
11087
+
11088
+ target = parseFloat(fill, 10);
11089
+ if (isFinite(target) && Math.floor(target) === target) {
11090
+ if (fill[0] === '-' || fill[0] === '+') {
11091
+ target = index + target;
11092
+ }
11093
+
11094
+ if (target === index || target < 0 || target >= count) {
11095
+ return false;
11096
+ }
11097
+
11098
+ return target;
11099
+ }
11100
+
11101
+ switch (fill) {
11102
+ // compatibility
11103
+ case 'bottom':
11104
+ return 'start';
11105
+ case 'top':
11106
+ return 'end';
11107
+ case 'zero':
11108
+ return 'origin';
11109
+ // supported boundaries
11110
+ case 'origin':
11111
+ case 'start':
11112
+ case 'end':
11113
+ return fill;
11114
+ // invalid fill values
11115
+ default:
11116
+ return false;
11117
+ }
11118
+ }
11119
+
11120
+ function computeBoundary(source) {
11121
+ var model = source.el._model || {};
11122
+ var scale = source.el._scale || {};
11123
+ var fill = source.fill;
11124
+ var target = null;
11125
+ var horizontal;
11126
+
11127
+ if (isFinite(fill)) {
11128
+ return null;
11129
+ }
11130
+
11131
+ // Backward compatibility: until v3, we still need to support boundary values set on
11132
+ // the model (scaleTop, scaleBottom and scaleZero) because some external plugins and
11133
+ // controllers might still use it (e.g. the Smith chart).
11134
+
11135
+ if (fill === 'start') {
11136
+ target = model.scaleBottom === undefined ? scale.bottom : model.scaleBottom;
11137
+ } else if (fill === 'end') {
11138
+ target = model.scaleTop === undefined ? scale.top : model.scaleTop;
11139
+ } else if (model.scaleZero !== undefined) {
11140
+ target = model.scaleZero;
11141
+ } else if (scale.getBasePosition) {
11142
+ target = scale.getBasePosition();
11143
+ } else if (scale.getBasePixel) {
11144
+ target = scale.getBasePixel();
11145
+ }
11146
+
11147
+ if (target !== undefined && target !== null) {
11148
+ if (target.x !== undefined && target.y !== undefined) {
11149
+ return target;
11150
+ }
11151
+
11152
+ if (typeof target === 'number' && isFinite(target)) {
11153
+ horizontal = scale.isHorizontal();
11154
+ return {
11155
+ x: horizontal ? target : null,
11156
+ y: horizontal ? null : target
11157
+ };
11158
+ }
11159
+ }
11160
+
11161
+ return null;
11162
+ }
11163
+
11164
+ function resolveTarget(sources, index, propagate) {
11165
+ var source = sources[index];
11166
+ var fill = source.fill;
11167
+ var visited = [index];
11168
+ var target;
11169
+
11170
+ if (!propagate) {
11171
+ return fill;
11172
+ }
11173
+
11174
+ while (fill !== false && visited.indexOf(fill) === -1) {
11175
+ if (!isFinite(fill)) {
11176
+ return fill;
11177
+ }
11178
+
11179
+ target = sources[fill];
11180
+ if (!target) {
11181
+ return false;
11182
+ }
11183
+
11184
+ if (target.visible) {
11185
+ return fill;
11186
+ }
11187
+
11188
+ visited.push(fill);
11189
+ fill = target.fill;
11190
+ }
11191
+
11192
+ return false;
11193
+ }
11194
+
11195
+ function createMapper(source) {
11196
+ var fill = source.fill;
11197
+ var type = 'dataset';
11198
+
11199
+ if (fill === false) {
11200
+ return null;
11201
+ }
11202
+
11203
+ if (!isFinite(fill)) {
11204
+ type = 'boundary';
11205
+ }
11206
+
11207
+ return mappers[type](source);
11208
+ }
11209
+
11210
+ function isDrawable(point) {
11211
+ return point && !point.skip;
11212
+ }
11213
+
11214
+ function drawArea(ctx, curve0, curve1, len0, len1) {
11215
+ var i;
11216
+
11217
+ if (!len0 || !len1) {
11218
+ return;
11219
+ }
11220
+
11221
+ // building first area curve (normal)
11222
+ ctx.moveTo(curve0[0].x, curve0[0].y);
11223
+ for (i = 1; i < len0; ++i) {
11224
+ helpers.canvas.lineTo(ctx, curve0[i - 1], curve0[i]);
11225
+ }
11226
+
11227
+ // joining the two area curves
11228
+ ctx.lineTo(curve1[len1 - 1].x, curve1[len1 - 1].y);
11229
+
11230
+ // building opposite area curve (reverse)
11231
+ for (i = len1 - 1; i > 0; --i) {
11232
+ helpers.canvas.lineTo(ctx, curve1[i], curve1[i - 1], true);
11233
+ }
11234
+ }
11235
+
11236
+ function doFill(ctx, points, mapper, view, color, loop) {
11237
+ var count = points.length;
11238
+ var span = view.spanGaps;
11239
+ var curve0 = [];
11240
+ var curve1 = [];
11241
+ var len0 = 0;
11242
+ var len1 = 0;
11243
+ var i, ilen, index, p0, p1, d0, d1;
11244
+
11245
+ ctx.beginPath();
11246
+
11247
+ for (i = 0, ilen = (count + !!loop); i < ilen; ++i) {
11248
+ index = i % count;
11249
+ p0 = points[index]._view;
11250
+ p1 = mapper(p0, index, view);
11251
+ d0 = isDrawable(p0);
11252
+ d1 = isDrawable(p1);
11253
+
11254
+ if (d0 && d1) {
11255
+ len0 = curve0.push(p0);
11256
+ len1 = curve1.push(p1);
11257
+ } else if (len0 && len1) {
11258
+ if (!span) {
11259
+ drawArea(ctx, curve0, curve1, len0, len1);
11260
+ len0 = len1 = 0;
11261
+ curve0 = [];
11262
+ curve1 = [];
11263
+ } else {
11264
+ if (d0) {
11265
+ curve0.push(p0);
11266
+ }
11267
+ if (d1) {
11268
+ curve1.push(p1);
11269
+ }
11270
+ }
11271
+ }
11272
+ }
11273
+
11274
+ drawArea(ctx, curve0, curve1, len0, len1);
11275
+
11276
+ ctx.closePath();
11277
+ ctx.fillStyle = color;
11278
+ ctx.fill();
11279
+ }
11280
+
11281
+ return {
11282
+ id: 'filler',
11283
+
11284
+ afterDatasetsUpdate: function(chart, options) {
11285
+ var count = (chart.data.datasets || []).length;
11286
+ var propagate = options.propagate;
11287
+ var sources = [];
11288
+ var meta, i, el, source;
11289
+
11290
+ for (i = 0; i < count; ++i) {
11291
+ meta = chart.getDatasetMeta(i);
11292
+ el = meta.dataset;
11293
+ source = null;
11294
+
11295
+ if (el && el._model && el instanceof elements.Line) {
11296
+ source = {
11297
+ visible: chart.isDatasetVisible(i),
11298
+ fill: decodeFill(el, i, count),
11299
+ chart: chart,
11300
+ el: el
11301
+ };
11302
+ }
11303
+
11304
+ meta.$filler = source;
11305
+ sources.push(source);
11306
+ }
11307
+
11308
+ for (i = 0; i < count; ++i) {
11309
+ source = sources[i];
11310
+ if (!source) {
11311
+ continue;
11312
+ }
11313
+
11314
+ source.fill = resolveTarget(sources, i, propagate);
11315
+ source.boundary = computeBoundary(source);
11316
+ source.mapper = createMapper(source);
11317
+ }
11318
+ },
11319
+
11320
+ beforeDatasetDraw: function(chart, args) {
11321
+ var meta = args.meta.$filler;
11322
+ if (!meta) {
11323
+ return;
11324
+ }
11325
+
11326
+ var ctx = chart.ctx;
11327
+ var el = meta.el;
11328
+ var view = el._view;
11329
+ var points = el._children || [];
11330
+ var mapper = meta.mapper;
11331
+ var color = view.backgroundColor || defaults.global.defaultColor;
11332
+
11333
+ if (mapper && color && points.length) {
11334
+ helpers.canvas.clipArea(ctx, chart.chartArea);
11335
+ doFill(ctx, points, mapper, view, color, el._loop);
11336
+ helpers.canvas.unclipArea(ctx);
11337
+ }
11338
+ }
11339
+ };
11340
+ };
11341
+
11342
+ },{"25":25,"40":40,"45":45}],50:[function(require,module,exports){
11343
+ 'use strict';
11344
+
11345
+ var defaults = require(25);
11346
+ var Element = require(26);
11347
+ var helpers = require(45);
11348
+
11349
+ defaults._set('global', {
11350
+ legend: {
11351
+ display: true,
11352
+ position: 'top',
11353
+ fullWidth: true,
11354
+ reverse: false,
11355
+ weight: 1000,
11356
+
11357
+ // a callback that will handle
11358
+ onClick: function(e, legendItem) {
11359
+ var index = legendItem.datasetIndex;
11360
+ var ci = this.chart;
11361
+ var meta = ci.getDatasetMeta(index);
11362
+
11363
+ // See controller.isDatasetVisible comment
11364
+ meta.hidden = meta.hidden === null ? !ci.data.datasets[index].hidden : null;
11365
+
11366
+ // We hid a dataset ... rerender the chart
11367
+ ci.update();
11368
+ },
11369
+
11370
+ onHover: null,
11371
+
11372
+ labels: {
11373
+ boxWidth: 40,
11374
+ padding: 10,
11375
+ // Generates labels shown in the legend
11376
+ // Valid properties to return:
11377
+ // text : text to display
11378
+ // fillStyle : fill of coloured box
11379
+ // strokeStyle: stroke of coloured box
11380
+ // hidden : if this legend item refers to a hidden item
11381
+ // lineCap : cap style for line
11382
+ // lineDash
11383
+ // lineDashOffset :
11384
+ // lineJoin :
11385
+ // lineWidth :
11386
+ generateLabels: function(chart) {
11387
+ var data = chart.data;
11388
+ return helpers.isArray(data.datasets) ? data.datasets.map(function(dataset, i) {
11389
+ return {
11390
+ text: dataset.label,
11391
+ fillStyle: (!helpers.isArray(dataset.backgroundColor) ? dataset.backgroundColor : dataset.backgroundColor[0]),
11392
+ hidden: !chart.isDatasetVisible(i),
11393
+ lineCap: dataset.borderCapStyle,
11394
+ lineDash: dataset.borderDash,
11395
+ lineDashOffset: dataset.borderDashOffset,
11396
+ lineJoin: dataset.borderJoinStyle,
11397
+ lineWidth: dataset.borderWidth,
11398
+ strokeStyle: dataset.borderColor,
11399
+ pointStyle: dataset.pointStyle,
11400
+
11401
+ // Below is extra data used for toggling the datasets
11402
+ datasetIndex: i
11403
+ };
11404
+ }, this) : [];
11405
+ }
11406
+ }
11407
+ },
11408
+
11409
+ legendCallback: function(chart) {
11410
+ var text = [];
11411
+ text.push('<ul class="' + chart.id + '-legend">');
11412
+ for (var i = 0; i < chart.data.datasets.length; i++) {
11413
+ text.push('<li><span style="background-color:' + chart.data.datasets[i].backgroundColor + '"></span>');
11414
+ if (chart.data.datasets[i].label) {
11415
+ text.push(chart.data.datasets[i].label);
11416
+ }
11417
+ text.push('</li>');
11418
+ }
11419
+ text.push('</ul>');
11420
+ return text.join('');
11421
+ }
11422
+ });
11423
+
11424
+ module.exports = function(Chart) {
11425
+
11426
+ var layout = Chart.layoutService;
11427
+ var noop = helpers.noop;
11428
+
11429
+ /**
11430
+ * Helper function to get the box width based on the usePointStyle option
11431
+ * @param labelopts {Object} the label options on the legend
11432
+ * @param fontSize {Number} the label font size
11433
+ * @return {Number} width of the color box area
11434
+ */
11435
+ function getBoxWidth(labelOpts, fontSize) {
11436
+ return labelOpts.usePointStyle ?
11437
+ fontSize * Math.SQRT2 :
11438
+ labelOpts.boxWidth;
11439
+ }
11440
+
11441
+ Chart.Legend = Element.extend({
11442
+
11443
+ initialize: function(config) {
11444
+ helpers.extend(this, config);
11445
+
11446
+ // Contains hit boxes for each dataset (in dataset order)
11447
+ this.legendHitBoxes = [];
11448
+
11449
+ // Are we in doughnut mode which has a different data type
11450
+ this.doughnutMode = false;
11451
+ },
11452
+
11453
+ // These methods are ordered by lifecycle. Utilities then follow.
11454
+ // Any function defined here is inherited by all legend types.
11455
+ // Any function can be extended by the legend type
11456
+
11457
+ beforeUpdate: noop,
11458
+ update: function(maxWidth, maxHeight, margins) {
11459
+ var me = this;
11460
+
11461
+ // Update Lifecycle - Probably don't want to ever extend or overwrite this function ;)
11462
+ me.beforeUpdate();
11463
+
11464
+ // Absorb the master measurements
11465
+ me.maxWidth = maxWidth;
11466
+ me.maxHeight = maxHeight;
11467
+ me.margins = margins;
11468
+
11469
+ // Dimensions
11470
+ me.beforeSetDimensions();
11471
+ me.setDimensions();
11472
+ me.afterSetDimensions();
11473
+ // Labels
11474
+ me.beforeBuildLabels();
11475
+ me.buildLabels();
11476
+ me.afterBuildLabels();
11477
+
11478
+ // Fit
11479
+ me.beforeFit();
11480
+ me.fit();
11481
+ me.afterFit();
11482
+ //
11483
+ me.afterUpdate();
11484
+
11485
+ return me.minSize;
11486
+ },
11487
+ afterUpdate: noop,
11488
+
11489
+ //
11490
+
11491
+ beforeSetDimensions: noop,
11492
+ setDimensions: function() {
11493
+ var me = this;
11494
+ // Set the unconstrained dimension before label rotation
11495
+ if (me.isHorizontal()) {
11496
+ // Reset position before calculating rotation
11497
+ me.width = me.maxWidth;
11498
+ me.left = 0;
11499
+ me.right = me.width;
11500
+ } else {
11501
+ me.height = me.maxHeight;
11502
+
11503
+ // Reset position before calculating rotation
11504
+ me.top = 0;
11505
+ me.bottom = me.height;
11506
+ }
11507
+
11508
+ // Reset padding
11509
+ me.paddingLeft = 0;
11510
+ me.paddingTop = 0;
11511
+ me.paddingRight = 0;
11512
+ me.paddingBottom = 0;
11513
+
11514
+ // Reset minSize
11515
+ me.minSize = {
11516
+ width: 0,
11517
+ height: 0
11518
+ };
11519
+ },
11520
+ afterSetDimensions: noop,
11521
+
11522
+ //
11523
+
11524
+ beforeBuildLabels: noop,
11525
+ buildLabels: function() {
11526
+ var me = this;
11527
+ var labelOpts = me.options.labels || {};
11528
+ var legendItems = helpers.callback(labelOpts.generateLabels, [me.chart], me) || [];
11529
+
11530
+ if (labelOpts.filter) {
11531
+ legendItems = legendItems.filter(function(item) {
11532
+ return labelOpts.filter(item, me.chart.data);
11533
+ });
11534
+ }
11535
+
11536
+ if (me.options.reverse) {
11537
+ legendItems.reverse();
11538
+ }
11539
+
11540
+ me.legendItems = legendItems;
11541
+ },
11542
+ afterBuildLabels: noop,
11543
+
11544
+ //
11545
+
11546
+ beforeFit: noop,
11547
+ fit: function() {
11548
+ var me = this;
11549
+ var opts = me.options;
11550
+ var labelOpts = opts.labels;
11551
+ var display = opts.display;
11552
+
11553
+ var ctx = me.ctx;
11554
+
11555
+ var globalDefault = defaults.global;
11556
+ var valueOrDefault = helpers.valueOrDefault;
11557
+ var fontSize = valueOrDefault(labelOpts.fontSize, globalDefault.defaultFontSize);
11558
+ var fontStyle = valueOrDefault(labelOpts.fontStyle, globalDefault.defaultFontStyle);
11559
+ var fontFamily = valueOrDefault(labelOpts.fontFamily, globalDefault.defaultFontFamily);
11560
+ var labelFont = helpers.fontString(fontSize, fontStyle, fontFamily);
11561
+
11562
+ // Reset hit boxes
11563
+ var hitboxes = me.legendHitBoxes = [];
11564
+
11565
+ var minSize = me.minSize;
11566
+ var isHorizontal = me.isHorizontal();
11567
+
11568
+ if (isHorizontal) {
11569
+ minSize.width = me.maxWidth; // fill all the width
11570
+ minSize.height = display ? 10 : 0;
11571
+ } else {
11572
+ minSize.width = display ? 10 : 0;
11573
+ minSize.height = me.maxHeight; // fill all the height
11574
+ }
11575
+
11576
+ // Increase sizes here
11577
+ if (display) {
11578
+ ctx.font = labelFont;
11579
+
11580
+ if (isHorizontal) {
11581
+ // Labels
11582
+
11583
+ // Width of each line of legend boxes. Labels wrap onto multiple lines when there are too many to fit on one
11584
+ var lineWidths = me.lineWidths = [0];
11585
+ var totalHeight = me.legendItems.length ? fontSize + (labelOpts.padding) : 0;
11586
+
11587
+ ctx.textAlign = 'left';
11588
+ ctx.textBaseline = 'top';
11589
+
11590
+ helpers.each(me.legendItems, function(legendItem, i) {
11591
+ var boxWidth = getBoxWidth(labelOpts, fontSize);
11592
+ var width = boxWidth + (fontSize / 2) + ctx.measureText(legendItem.text).width;
11593
+
11594
+ if (lineWidths[lineWidths.length - 1] + width + labelOpts.padding >= me.width) {
11595
+ totalHeight += fontSize + (labelOpts.padding);
11596
+ lineWidths[lineWidths.length] = me.left;
11597
+ }
11598
+
11599
+ // Store the hitbox width and height here. Final position will be updated in `draw`
11600
+ hitboxes[i] = {
11601
+ left: 0,
11602
+ top: 0,
11603
+ width: width,
11604
+ height: fontSize
11605
+ };
11606
+
11607
+ lineWidths[lineWidths.length - 1] += width + labelOpts.padding;
11608
+ });
11609
+
11610
+ minSize.height += totalHeight;
11611
+
11612
+ } else {
11613
+ var vPadding = labelOpts.padding;
11614
+ var columnWidths = me.columnWidths = [];
11615
+ var totalWidth = labelOpts.padding;
11616
+ var currentColWidth = 0;
11617
+ var currentColHeight = 0;
11618
+ var itemHeight = fontSize + vPadding;
11619
+
11620
+ helpers.each(me.legendItems, function(legendItem, i) {
11621
+ var boxWidth = getBoxWidth(labelOpts, fontSize);
11622
+ var itemWidth = boxWidth + (fontSize / 2) + ctx.measureText(legendItem.text).width;
11623
+
11624
+ // If too tall, go to new column
11625
+ if (currentColHeight + itemHeight > minSize.height) {
11626
+ totalWidth += currentColWidth + labelOpts.padding;
11627
+ columnWidths.push(currentColWidth); // previous column width
11628
+
11629
+ currentColWidth = 0;
11630
+ currentColHeight = 0;
11631
+ }
11632
+
11633
+ // Get max width
11634
+ currentColWidth = Math.max(currentColWidth, itemWidth);
11635
+ currentColHeight += itemHeight;
11636
+
11637
+ // Store the hitbox width and height here. Final position will be updated in `draw`
11638
+ hitboxes[i] = {
11639
+ left: 0,
11640
+ top: 0,
11641
+ width: itemWidth,
11642
+ height: fontSize
11643
+ };
11644
+ });
11645
+
11646
+ totalWidth += currentColWidth;
11647
+ columnWidths.push(currentColWidth);
11648
+ minSize.width += totalWidth;
11649
+ }
11650
+ }
11651
+
11652
+ me.width = minSize.width;
11653
+ me.height = minSize.height;
11654
+ },
11655
+ afterFit: noop,
11656
+
11657
+ // Shared Methods
11658
+ isHorizontal: function() {
11659
+ return this.options.position === 'top' || this.options.position === 'bottom';
11660
+ },
11661
+
11662
+ // Actually draw the legend on the canvas
11663
+ draw: function() {
11664
+ var me = this;
11665
+ var opts = me.options;
11666
+ var labelOpts = opts.labels;
11667
+ var globalDefault = defaults.global;
11668
+ var lineDefault = globalDefault.elements.line;
11669
+ var legendWidth = me.width;
11670
+ var lineWidths = me.lineWidths;
11671
+
11672
+ if (opts.display) {
11673
+ var ctx = me.ctx;
11674
+ var valueOrDefault = helpers.valueOrDefault;
11675
+ var fontColor = valueOrDefault(labelOpts.fontColor, globalDefault.defaultFontColor);
11676
+ var fontSize = valueOrDefault(labelOpts.fontSize, globalDefault.defaultFontSize);
11677
+ var fontStyle = valueOrDefault(labelOpts.fontStyle, globalDefault.defaultFontStyle);
11678
+ var fontFamily = valueOrDefault(labelOpts.fontFamily, globalDefault.defaultFontFamily);
11679
+ var labelFont = helpers.fontString(fontSize, fontStyle, fontFamily);
11680
+ var cursor;
11681
+
11682
+ // Canvas setup
11683
+ ctx.textAlign = 'left';
11684
+ ctx.textBaseline = 'middle';
11685
+ ctx.lineWidth = 0.5;
11686
+ ctx.strokeStyle = fontColor; // for strikethrough effect
11687
+ ctx.fillStyle = fontColor; // render in correct colour
11688
+ ctx.font = labelFont;
11689
+
11690
+ var boxWidth = getBoxWidth(labelOpts, fontSize);
11691
+ var hitboxes = me.legendHitBoxes;
11692
+
11693
+ // current position
11694
+ var drawLegendBox = function(x, y, legendItem) {
11695
+ if (isNaN(boxWidth) || boxWidth <= 0) {
11696
+ return;
11697
+ }
11698
+
11699
+ // Set the ctx for the box
11700
+ ctx.save();
11701
+
11702
+ ctx.fillStyle = valueOrDefault(legendItem.fillStyle, globalDefault.defaultColor);
11703
+ ctx.lineCap = valueOrDefault(legendItem.lineCap, lineDefault.borderCapStyle);
11704
+ ctx.lineDashOffset = valueOrDefault(legendItem.lineDashOffset, lineDefault.borderDashOffset);
11705
+ ctx.lineJoin = valueOrDefault(legendItem.lineJoin, lineDefault.borderJoinStyle);
11706
+ ctx.lineWidth = valueOrDefault(legendItem.lineWidth, lineDefault.borderWidth);
11707
+ ctx.strokeStyle = valueOrDefault(legendItem.strokeStyle, globalDefault.defaultColor);
11708
+ var isLineWidthZero = (valueOrDefault(legendItem.lineWidth, lineDefault.borderWidth) === 0);
11709
+
11710
+ if (ctx.setLineDash) {
11711
+ // IE 9 and 10 do not support line dash
11712
+ ctx.setLineDash(valueOrDefault(legendItem.lineDash, lineDefault.borderDash));
11713
+ }
11714
+
11715
+ if (opts.labels && opts.labels.usePointStyle) {
11716
+ // Recalculate x and y for drawPoint() because its expecting
11717
+ // x and y to be center of figure (instead of top left)
11718
+ var radius = fontSize * Math.SQRT2 / 2;
11719
+ var offSet = radius / Math.SQRT2;
11720
+ var centerX = x + offSet;
11721
+ var centerY = y + offSet;
11722
+
11723
+ // Draw pointStyle as legend symbol
11724
+ helpers.canvas.drawPoint(ctx, legendItem.pointStyle, radius, centerX, centerY);
11725
+ } else {
11726
+ // Draw box as legend symbol
11727
+ if (!isLineWidthZero) {
11728
+ ctx.strokeRect(x, y, boxWidth, fontSize);
11729
+ }
11730
+ ctx.fillRect(x, y, boxWidth, fontSize);
11731
+ }
11732
+
11733
+ ctx.restore();
11734
+ };
11735
+ var fillText = function(x, y, legendItem, textWidth) {
11736
+ var halfFontSize = fontSize / 2;
11737
+ var xLeft = boxWidth + halfFontSize + x;
11738
+ var yMiddle = y + halfFontSize;
11739
+
11740
+ ctx.fillText(legendItem.text, xLeft, yMiddle);
11741
+
11742
+ if (legendItem.hidden) {
11743
+ // Strikethrough the text if hidden
11744
+ ctx.beginPath();
11745
+ ctx.lineWidth = 2;
11746
+ ctx.moveTo(xLeft, yMiddle);
11747
+ ctx.lineTo(xLeft + textWidth, yMiddle);
11748
+ ctx.stroke();
11749
+ }
11750
+ };
11751
+
11752
+ // Horizontal
11753
+ var isHorizontal = me.isHorizontal();
11754
+ if (isHorizontal) {
11755
+ cursor = {
11756
+ x: me.left + ((legendWidth - lineWidths[0]) / 2),
11757
+ y: me.top + labelOpts.padding,
11758
+ line: 0
11759
+ };
11760
+ } else {
11761
+ cursor = {
11762
+ x: me.left + labelOpts.padding,
11763
+ y: me.top + labelOpts.padding,
11764
+ line: 0
11765
+ };
11766
+ }
11767
+
11768
+ var itemHeight = fontSize + labelOpts.padding;
11769
+ helpers.each(me.legendItems, function(legendItem, i) {
11770
+ var textWidth = ctx.measureText(legendItem.text).width;
11771
+ var width = boxWidth + (fontSize / 2) + textWidth;
11772
+ var x = cursor.x;
11773
+ var y = cursor.y;
11774
+
11775
+ if (isHorizontal) {
11776
+ if (x + width >= legendWidth) {
11777
+ y = cursor.y += itemHeight;
11778
+ cursor.line++;
11779
+ x = cursor.x = me.left + ((legendWidth - lineWidths[cursor.line]) / 2);
11780
+ }
11781
+ } else if (y + itemHeight > me.bottom) {
11782
+ x = cursor.x = x + me.columnWidths[cursor.line] + labelOpts.padding;
11783
+ y = cursor.y = me.top + labelOpts.padding;
11784
+ cursor.line++;
11785
+ }
11786
+
11787
+ drawLegendBox(x, y, legendItem);
11788
+
11789
+ hitboxes[i].left = x;
11790
+ hitboxes[i].top = y;
11791
+
11792
+ // Fill the actual label
11793
+ fillText(x, y, legendItem, textWidth);
11794
+
11795
+ if (isHorizontal) {
11796
+ cursor.x += width + (labelOpts.padding);
11797
+ } else {
11798
+ cursor.y += itemHeight;
11799
+ }
11800
+
11801
+ });
11802
+ }
11803
+ },
11804
+
11805
+ /**
11806
+ * Handle an event
11807
+ * @private
11808
+ * @param {IEvent} event - The event to handle
11809
+ * @return {Boolean} true if a change occured
11810
+ */
11811
+ handleEvent: function(e) {
11812
+ var me = this;
11813
+ var opts = me.options;
11814
+ var type = e.type === 'mouseup' ? 'click' : e.type;
11815
+ var changed = false;
11816
+
11817
+ if (type === 'mousemove') {
11818
+ if (!opts.onHover) {
11819
+ return;
11820
+ }
11821
+ } else if (type === 'click') {
11822
+ if (!opts.onClick) {
11823
+ return;
11824
+ }
11825
+ } else {
11826
+ return;
11827
+ }
11828
+
11829
+ // Chart event already has relative position in it
11830
+ var x = e.x;
11831
+ var y = e.y;
11832
+
11833
+ if (x >= me.left && x <= me.right && y >= me.top && y <= me.bottom) {
11834
+ // See if we are touching one of the dataset boxes
11835
+ var lh = me.legendHitBoxes;
11836
+ for (var i = 0; i < lh.length; ++i) {
11837
+ var hitBox = lh[i];
11838
+
11839
+ if (x >= hitBox.left && x <= hitBox.left + hitBox.width && y >= hitBox.top && y <= hitBox.top + hitBox.height) {
11840
+ // Touching an element
11841
+ if (type === 'click') {
11842
+ // use e.native for backwards compatibility
11843
+ opts.onClick.call(me, e.native, me.legendItems[i]);
11844
+ changed = true;
11845
+ break;
11846
+ } else if (type === 'mousemove') {
11847
+ // use e.native for backwards compatibility
11848
+ opts.onHover.call(me, e.native, me.legendItems[i]);
11849
+ changed = true;
11850
+ break;
11851
+ }
11852
+ }
11853
+ }
11854
+ }
11855
+
11856
+ return changed;
11857
+ }
11858
+ });
11859
+
11860
+ function createNewLegendAndAttach(chart, legendOpts) {
11861
+ var legend = new Chart.Legend({
11862
+ ctx: chart.ctx,
11863
+ options: legendOpts,
11864
+ chart: chart
11865
+ });
11866
+
11867
+ layout.configure(chart, legend, legendOpts);
11868
+ layout.addBox(chart, legend);
11869
+ chart.legend = legend;
11870
+ }
11871
+
11872
+ return {
11873
+ id: 'legend',
11874
+
11875
+ beforeInit: function(chart) {
11876
+ var legendOpts = chart.options.legend;
11877
+
11878
+ if (legendOpts) {
11879
+ createNewLegendAndAttach(chart, legendOpts);
11880
+ }
11881
+ },
11882
+
11883
+ beforeUpdate: function(chart) {
11884
+ var legendOpts = chart.options.legend;
11885
+ var legend = chart.legend;
11886
+
11887
+ if (legendOpts) {
11888
+ helpers.mergeIf(legendOpts, defaults.global.legend);
11889
+
11890
+ if (legend) {
11891
+ layout.configure(chart, legend, legendOpts);
11892
+ legend.options = legendOpts;
11893
+ } else {
11894
+ createNewLegendAndAttach(chart, legendOpts);
11895
+ }
11896
+ } else if (legend) {
11897
+ layout.removeBox(chart, legend);
11898
+ delete chart.legend;
11899
+ }
11900
+ },
11901
+
11902
+ afterEvent: function(chart, e) {
11903
+ var legend = chart.legend;
11904
+ if (legend) {
11905
+ legend.handleEvent(e);
11906
+ }
11907
+ }
11908
+ };
11909
+ };
11910
+
11911
+ },{"25":25,"26":26,"45":45}],51:[function(require,module,exports){
11912
+ 'use strict';
11913
+
11914
+ var defaults = require(25);
11915
+ var Element = require(26);
11916
+ var helpers = require(45);
11917
+
11918
+ defaults._set('global', {
11919
+ title: {
11920
+ display: false,
11921
+ fontStyle: 'bold',
11922
+ fullWidth: true,
11923
+ lineHeight: 1.2,
11924
+ padding: 10,
11925
+ position: 'top',
11926
+ text: '',
11927
+ weight: 2000 // by default greater than legend (1000) to be above
11928
+ }
11929
+ });
11930
+
11931
+ module.exports = function(Chart) {
11932
+
11933
+ var layout = Chart.layoutService;
11934
+ var noop = helpers.noop;
11935
+
11936
+ Chart.Title = Element.extend({
11937
+ initialize: function(config) {
11938
+ var me = this;
11939
+ helpers.extend(me, config);
11940
+
11941
+ // Contains hit boxes for each dataset (in dataset order)
11942
+ me.legendHitBoxes = [];
11943
+ },
11944
+
11945
+ // These methods are ordered by lifecycle. Utilities then follow.
11946
+
11947
+ beforeUpdate: noop,
11948
+ update: function(maxWidth, maxHeight, margins) {
11949
+ var me = this;
11950
+
11951
+ // Update Lifecycle - Probably don't want to ever extend or overwrite this function ;)
11952
+ me.beforeUpdate();
11953
+
11954
+ // Absorb the master measurements
11955
+ me.maxWidth = maxWidth;
11956
+ me.maxHeight = maxHeight;
11957
+ me.margins = margins;
11958
+
11959
+ // Dimensions
11960
+ me.beforeSetDimensions();
11961
+ me.setDimensions();
11962
+ me.afterSetDimensions();
11963
+ // Labels
11964
+ me.beforeBuildLabels();
11965
+ me.buildLabels();
11966
+ me.afterBuildLabels();
11967
+
11968
+ // Fit
11969
+ me.beforeFit();
11970
+ me.fit();
11971
+ me.afterFit();
11972
+ //
11973
+ me.afterUpdate();
11974
+
11975
+ return me.minSize;
11976
+
11977
+ },
11978
+ afterUpdate: noop,
11979
+
11980
+ //
11981
+
11982
+ beforeSetDimensions: noop,
11983
+ setDimensions: function() {
11984
+ var me = this;
11985
+ // Set the unconstrained dimension before label rotation
11986
+ if (me.isHorizontal()) {
11987
+ // Reset position before calculating rotation
11988
+ me.width = me.maxWidth;
11989
+ me.left = 0;
11990
+ me.right = me.width;
11991
+ } else {
11992
+ me.height = me.maxHeight;
11993
+
11994
+ // Reset position before calculating rotation
11995
+ me.top = 0;
11996
+ me.bottom = me.height;
11997
+ }
11998
+
11999
+ // Reset padding
12000
+ me.paddingLeft = 0;
12001
+ me.paddingTop = 0;
12002
+ me.paddingRight = 0;
12003
+ me.paddingBottom = 0;
12004
+
12005
+ // Reset minSize
12006
+ me.minSize = {
12007
+ width: 0,
12008
+ height: 0
12009
+ };
12010
+ },
12011
+ afterSetDimensions: noop,
12012
+
12013
+ //
12014
+
12015
+ beforeBuildLabels: noop,
12016
+ buildLabels: noop,
12017
+ afterBuildLabels: noop,
12018
+
12019
+ //
12020
+
12021
+ beforeFit: noop,
12022
+ fit: function() {
12023
+ var me = this;
12024
+ var valueOrDefault = helpers.valueOrDefault;
12025
+ var opts = me.options;
12026
+ var display = opts.display;
12027
+ var fontSize = valueOrDefault(opts.fontSize, defaults.global.defaultFontSize);
12028
+ var minSize = me.minSize;
12029
+ var lineCount = helpers.isArray(opts.text) ? opts.text.length : 1;
12030
+ var lineHeight = helpers.options.toLineHeight(opts.lineHeight, fontSize);
12031
+ var textSize = display ? (lineCount * lineHeight) + (opts.padding * 2) : 0;
12032
+
12033
+ if (me.isHorizontal()) {
12034
+ minSize.width = me.maxWidth; // fill all the width
12035
+ minSize.height = textSize;
12036
+ } else {
12037
+ minSize.width = textSize;
12038
+ minSize.height = me.maxHeight; // fill all the height
12039
+ }
12040
+
12041
+ me.width = minSize.width;
12042
+ me.height = minSize.height;
12043
+
12044
+ },
12045
+ afterFit: noop,
12046
+
12047
+ // Shared Methods
12048
+ isHorizontal: function() {
12049
+ var pos = this.options.position;
12050
+ return pos === 'top' || pos === 'bottom';
12051
+ },
12052
+
12053
+ // Actually draw the title block on the canvas
12054
+ draw: function() {
12055
+ var me = this;
12056
+ var ctx = me.ctx;
12057
+ var valueOrDefault = helpers.valueOrDefault;
12058
+ var opts = me.options;
12059
+ var globalDefaults = defaults.global;
12060
+
12061
+ if (opts.display) {
12062
+ var fontSize = valueOrDefault(opts.fontSize, globalDefaults.defaultFontSize);
12063
+ var fontStyle = valueOrDefault(opts.fontStyle, globalDefaults.defaultFontStyle);
12064
+ var fontFamily = valueOrDefault(opts.fontFamily, globalDefaults.defaultFontFamily);
12065
+ var titleFont = helpers.fontString(fontSize, fontStyle, fontFamily);
12066
+ var lineHeight = helpers.options.toLineHeight(opts.lineHeight, fontSize);
12067
+ var offset = lineHeight / 2 + opts.padding;
12068
+ var rotation = 0;
12069
+ var top = me.top;
12070
+ var left = me.left;
12071
+ var bottom = me.bottom;
12072
+ var right = me.right;
12073
+ var maxWidth, titleX, titleY;
12074
+
12075
+ ctx.fillStyle = valueOrDefault(opts.fontColor, globalDefaults.defaultFontColor); // render in correct colour
12076
+ ctx.font = titleFont;
12077
+
12078
+ // Horizontal
12079
+ if (me.isHorizontal()) {
12080
+ titleX = left + ((right - left) / 2); // midpoint of the width
12081
+ titleY = top + offset;
12082
+ maxWidth = right - left;
12083
+ } else {
12084
+ titleX = opts.position === 'left' ? left + offset : right - offset;
12085
+ titleY = top + ((bottom - top) / 2);
12086
+ maxWidth = bottom - top;
12087
+ rotation = Math.PI * (opts.position === 'left' ? -0.5 : 0.5);
12088
+ }
12089
+
12090
+ ctx.save();
12091
+ ctx.translate(titleX, titleY);
12092
+ ctx.rotate(rotation);
12093
+ ctx.textAlign = 'center';
12094
+ ctx.textBaseline = 'middle';
12095
+
12096
+ var text = opts.text;
12097
+ if (helpers.isArray(text)) {
12098
+ var y = 0;
12099
+ for (var i = 0; i < text.length; ++i) {
12100
+ ctx.fillText(text[i], 0, y, maxWidth);
12101
+ y += lineHeight;
12102
+ }
12103
+ } else {
12104
+ ctx.fillText(text, 0, 0, maxWidth);
12105
+ }
12106
+
12107
+ ctx.restore();
12108
+ }
12109
+ }
12110
+ });
12111
+
12112
+ function createNewTitleBlockAndAttach(chart, titleOpts) {
12113
+ var title = new Chart.Title({
12114
+ ctx: chart.ctx,
12115
+ options: titleOpts,
12116
+ chart: chart
12117
+ });
12118
+
12119
+ layout.configure(chart, title, titleOpts);
12120
+ layout.addBox(chart, title);
12121
+ chart.titleBlock = title;
12122
+ }
12123
+
12124
+ return {
12125
+ id: 'title',
12126
+
12127
+ beforeInit: function(chart) {
12128
+ var titleOpts = chart.options.title;
12129
+
12130
+ if (titleOpts) {
12131
+ createNewTitleBlockAndAttach(chart, titleOpts);
12132
+ }
12133
+ },
12134
+
12135
+ beforeUpdate: function(chart) {
12136
+ var titleOpts = chart.options.title;
12137
+ var titleBlock = chart.titleBlock;
12138
+
12139
+ if (titleOpts) {
12140
+ helpers.mergeIf(titleOpts, defaults.global.title);
12141
+
12142
+ if (titleBlock) {
12143
+ layout.configure(chart, titleBlock, titleOpts);
12144
+ titleBlock.options = titleOpts;
12145
+ } else {
12146
+ createNewTitleBlockAndAttach(chart, titleOpts);
12147
+ }
12148
+ } else if (titleBlock) {
12149
+ Chart.layoutService.removeBox(chart, titleBlock);
12150
+ delete chart.titleBlock;
12151
+ }
12152
+ }
12153
+ };
12154
+ };
12155
+
12156
+ },{"25":25,"26":26,"45":45}],52:[function(require,module,exports){
12157
+ 'use strict';
12158
+
12159
+ module.exports = function(Chart) {
12160
+
12161
+ // Default config for a category scale
12162
+ var defaultConfig = {
12163
+ position: 'bottom'
12164
+ };
12165
+
12166
+ var DatasetScale = Chart.Scale.extend({
12167
+ /**
12168
+ * Internal function to get the correct labels. If data.xLabels or data.yLabels are defined, use those
12169
+ * else fall back to data.labels
12170
+ * @private
12171
+ */
12172
+ getLabels: function() {
12173
+ var data = this.chart.data;
12174
+ return this.options.labels || (this.isHorizontal() ? data.xLabels : data.yLabels) || data.labels;
12175
+ },
12176
+
12177
+ determineDataLimits: function() {
12178
+ var me = this;
12179
+ var labels = me.getLabels();
12180
+ me.minIndex = 0;
12181
+ me.maxIndex = labels.length - 1;
12182
+ var findIndex;
12183
+
12184
+ if (me.options.ticks.min !== undefined) {
12185
+ // user specified min value
12186
+ findIndex = labels.indexOf(me.options.ticks.min);
12187
+ me.minIndex = findIndex !== -1 ? findIndex : me.minIndex;
12188
+ }
12189
+
12190
+ if (me.options.ticks.max !== undefined) {
12191
+ // user specified max value
12192
+ findIndex = labels.indexOf(me.options.ticks.max);
12193
+ me.maxIndex = findIndex !== -1 ? findIndex : me.maxIndex;
12194
+ }
12195
+
12196
+ me.min = labels[me.minIndex];
12197
+ me.max = labels[me.maxIndex];
12198
+ },
12199
+
12200
+ buildTicks: function() {
12201
+ var me = this;
12202
+ var labels = me.getLabels();
12203
+ // If we are viewing some subset of labels, slice the original array
12204
+ me.ticks = (me.minIndex === 0 && me.maxIndex === labels.length - 1) ? labels : labels.slice(me.minIndex, me.maxIndex + 1);
12205
+ },
12206
+
12207
+ getLabelForIndex: function(index, datasetIndex) {
12208
+ var me = this;
12209
+ var data = me.chart.data;
12210
+ var isHorizontal = me.isHorizontal();
12211
+
12212
+ if (data.yLabels && !isHorizontal) {
12213
+ return me.getRightValue(data.datasets[datasetIndex].data[index]);
12214
+ }
12215
+ return me.ticks[index - me.minIndex];
12216
+ },
12217
+
12218
+ // Used to get data value locations. Value can either be an index or a numerical value
12219
+ getPixelForValue: function(value, index) {
12220
+ var me = this;
12221
+ var offset = me.options.offset;
12222
+ // 1 is added because we need the length but we have the indexes
12223
+ var offsetAmt = Math.max((me.maxIndex + 1 - me.minIndex - (offset ? 0 : 1)), 1);
12224
+
12225
+ // If value is a data object, then index is the index in the data array,
12226
+ // not the index of the scale. We need to change that.
12227
+ var valueCategory;
12228
+ if (value !== undefined && value !== null) {
12229
+ valueCategory = me.isHorizontal() ? value.x : value.y;
12230
+ }
12231
+ if (valueCategory !== undefined || (value !== undefined && isNaN(index))) {
12232
+ var labels = me.getLabels();
12233
+ value = valueCategory || value;
12234
+ var idx = labels.indexOf(value);
12235
+ index = idx !== -1 ? idx : index;
12236
+ }
12237
+
12238
+ if (me.isHorizontal()) {
12239
+ var valueWidth = me.width / offsetAmt;
12240
+ var widthOffset = (valueWidth * (index - me.minIndex));
12241
+
12242
+ if (offset) {
12243
+ widthOffset += (valueWidth / 2);
12244
+ }
12245
+
12246
+ return me.left + Math.round(widthOffset);
12247
+ }
12248
+ var valueHeight = me.height / offsetAmt;
12249
+ var heightOffset = (valueHeight * (index - me.minIndex));
12250
+
12251
+ if (offset) {
12252
+ heightOffset += (valueHeight / 2);
12253
+ }
12254
+
12255
+ return me.top + Math.round(heightOffset);
12256
+ },
12257
+ getPixelForTick: function(index) {
12258
+ return this.getPixelForValue(this.ticks[index], index + this.minIndex, null);
12259
+ },
12260
+ getValueForPixel: function(pixel) {
12261
+ var me = this;
12262
+ var offset = me.options.offset;
12263
+ var value;
12264
+ var offsetAmt = Math.max((me._ticks.length - (offset ? 0 : 1)), 1);
12265
+ var horz = me.isHorizontal();
12266
+ var valueDimension = (horz ? me.width : me.height) / offsetAmt;
12267
+
12268
+ pixel -= horz ? me.left : me.top;
12269
+
12270
+ if (offset) {
12271
+ pixel -= (valueDimension / 2);
12272
+ }
12273
+
12274
+ if (pixel <= 0) {
12275
+ value = 0;
12276
+ } else {
12277
+ value = Math.round(pixel / valueDimension);
12278
+ }
12279
+
12280
+ return value + me.minIndex;
12281
+ },
12282
+ getBasePixel: function() {
12283
+ return this.bottom;
12284
+ }
12285
+ });
12286
+
12287
+ Chart.scaleService.registerScaleType('category', DatasetScale, defaultConfig);
12288
+
12289
+ };
12290
+
12291
+ },{}],53:[function(require,module,exports){
12292
+ 'use strict';
12293
+
12294
+ var defaults = require(25);
12295
+ var helpers = require(45);
12296
+ var Ticks = require(34);
12297
+
12298
+ module.exports = function(Chart) {
12299
+
12300
+ var defaultConfig = {
12301
+ position: 'left',
12302
+ ticks: {
12303
+ callback: Ticks.formatters.linear
12304
+ }
12305
+ };
12306
+
12307
+ var LinearScale = Chart.LinearScaleBase.extend({
12308
+
12309
+ determineDataLimits: function() {
12310
+ var me = this;
12311
+ var opts = me.options;
12312
+ var chart = me.chart;
12313
+ var data = chart.data;
12314
+ var datasets = data.datasets;
12315
+ var isHorizontal = me.isHorizontal();
12316
+ var DEFAULT_MIN = 0;
12317
+ var DEFAULT_MAX = 1;
12318
+
12319
+ function IDMatches(meta) {
12320
+ return isHorizontal ? meta.xAxisID === me.id : meta.yAxisID === me.id;
12321
+ }
12322
+
12323
+ // First Calculate the range
12324
+ me.min = null;
12325
+ me.max = null;
12326
+
12327
+ var hasStacks = opts.stacked;
12328
+ if (hasStacks === undefined) {
12329
+ helpers.each(datasets, function(dataset, datasetIndex) {
12330
+ if (hasStacks) {
12331
+ return;
12332
+ }
12333
+
12334
+ var meta = chart.getDatasetMeta(datasetIndex);
12335
+ if (chart.isDatasetVisible(datasetIndex) && IDMatches(meta) &&
12336
+ meta.stack !== undefined) {
12337
+ hasStacks = true;
12338
+ }
12339
+ });
12340
+ }
12341
+
12342
+ if (opts.stacked || hasStacks) {
12343
+ var valuesPerStack = {};
12344
+
12345
+ helpers.each(datasets, function(dataset, datasetIndex) {
12346
+ var meta = chart.getDatasetMeta(datasetIndex);
12347
+ var key = [
12348
+ meta.type,
12349
+ // we have a separate stack for stack=undefined datasets when the opts.stacked is undefined
12350
+ ((opts.stacked === undefined && meta.stack === undefined) ? datasetIndex : ''),
12351
+ meta.stack
12352
+ ].join('.');
12353
+
12354
+ if (valuesPerStack[key] === undefined) {
12355
+ valuesPerStack[key] = {
12356
+ positiveValues: [],
12357
+ negativeValues: []
12358
+ };
12359
+ }
12360
+
12361
+ // Store these per type
12362
+ var positiveValues = valuesPerStack[key].positiveValues;
12363
+ var negativeValues = valuesPerStack[key].negativeValues;
12364
+
12365
+ if (chart.isDatasetVisible(datasetIndex) && IDMatches(meta)) {
12366
+ helpers.each(dataset.data, function(rawValue, index) {
12367
+ var value = +me.getRightValue(rawValue);
12368
+ if (isNaN(value) || meta.data[index].hidden) {
12369
+ return;
12370
+ }
12371
+
12372
+ positiveValues[index] = positiveValues[index] || 0;
12373
+ negativeValues[index] = negativeValues[index] || 0;
12374
+
12375
+ if (opts.relativePoints) {
12376
+ positiveValues[index] = 100;
12377
+ } else if (value < 0) {
12378
+ negativeValues[index] += value;
12379
+ } else {
12380
+ positiveValues[index] += value;
12381
+ }
12382
+ });
12383
+ }
12384
+ });
12385
+
12386
+ helpers.each(valuesPerStack, function(valuesForType) {
12387
+ var values = valuesForType.positiveValues.concat(valuesForType.negativeValues);
12388
+ var minVal = helpers.min(values);
12389
+ var maxVal = helpers.max(values);
12390
+ me.min = me.min === null ? minVal : Math.min(me.min, minVal);
12391
+ me.max = me.max === null ? maxVal : Math.max(me.max, maxVal);
12392
+ });
12393
+
12394
+ } else {
12395
+ helpers.each(datasets, function(dataset, datasetIndex) {
12396
+ var meta = chart.getDatasetMeta(datasetIndex);
12397
+ if (chart.isDatasetVisible(datasetIndex) && IDMatches(meta)) {
12398
+ helpers.each(dataset.data, function(rawValue, index) {
12399
+ var value = +me.getRightValue(rawValue);
12400
+ if (isNaN(value) || meta.data[index].hidden) {
12401
+ return;
12402
+ }
12403
+
12404
+ if (me.min === null) {
12405
+ me.min = value;
12406
+ } else if (value < me.min) {
12407
+ me.min = value;
12408
+ }
12409
+
12410
+ if (me.max === null) {
12411
+ me.max = value;
12412
+ } else if (value > me.max) {
12413
+ me.max = value;
12414
+ }
12415
+ });
12416
+ }
12417
+ });
12418
+ }
12419
+
12420
+ me.min = isFinite(me.min) && !isNaN(me.min) ? me.min : DEFAULT_MIN;
12421
+ me.max = isFinite(me.max) && !isNaN(me.max) ? me.max : DEFAULT_MAX;
12422
+
12423
+ // Common base implementation to handle ticks.min, ticks.max, ticks.beginAtZero
12424
+ this.handleTickRangeOptions();
12425
+ },
12426
+ getTickLimit: function() {
12427
+ var maxTicks;
12428
+ var me = this;
12429
+ var tickOpts = me.options.ticks;
12430
+
12431
+ if (me.isHorizontal()) {
12432
+ maxTicks = Math.min(tickOpts.maxTicksLimit ? tickOpts.maxTicksLimit : 11, Math.ceil(me.width / 50));
12433
+ } else {
12434
+ // The factor of 2 used to scale the font size has been experimentally determined.
12435
+ var tickFontSize = helpers.valueOrDefault(tickOpts.fontSize, defaults.global.defaultFontSize);
12436
+ maxTicks = Math.min(tickOpts.maxTicksLimit ? tickOpts.maxTicksLimit : 11, Math.ceil(me.height / (2 * tickFontSize)));
12437
+ }
12438
+
12439
+ return maxTicks;
12440
+ },
12441
+ // Called after the ticks are built. We need
12442
+ handleDirectionalChanges: function() {
12443
+ if (!this.isHorizontal()) {
12444
+ // We are in a vertical orientation. The top value is the highest. So reverse the array
12445
+ this.ticks.reverse();
12446
+ }
12447
+ },
12448
+ getLabelForIndex: function(index, datasetIndex) {
12449
+ return +this.getRightValue(this.chart.data.datasets[datasetIndex].data[index]);
12450
+ },
12451
+ // Utils
12452
+ getPixelForValue: function(value) {
12453
+ // This must be called after fit has been run so that
12454
+ // this.left, this.top, this.right, and this.bottom have been defined
12455
+ var me = this;
12456
+ var start = me.start;
12457
+
12458
+ var rightValue = +me.getRightValue(value);
12459
+ var pixel;
12460
+ var range = me.end - start;
12461
+
12462
+ if (me.isHorizontal()) {
12463
+ pixel = me.left + (me.width / range * (rightValue - start));
12464
+ return Math.round(pixel);
12465
+ }
12466
+
12467
+ pixel = me.bottom - (me.height / range * (rightValue - start));
12468
+ return Math.round(pixel);
12469
+ },
12470
+ getValueForPixel: function(pixel) {
12471
+ var me = this;
12472
+ var isHorizontal = me.isHorizontal();
12473
+ var innerDimension = isHorizontal ? me.width : me.height;
12474
+ var offset = (isHorizontal ? pixel - me.left : me.bottom - pixel) / innerDimension;
12475
+ return me.start + ((me.end - me.start) * offset);
12476
+ },
12477
+ getPixelForTick: function(index) {
12478
+ return this.getPixelForValue(this.ticksAsNumbers[index]);
12479
+ }
12480
+ });
12481
+ Chart.scaleService.registerScaleType('linear', LinearScale, defaultConfig);
12482
+
12483
+ };
12484
+
12485
+ },{"25":25,"34":34,"45":45}],54:[function(require,module,exports){
12486
+ 'use strict';
12487
+
12488
+ var helpers = require(45);
12489
+ var Ticks = require(34);
12490
+
12491
+ module.exports = function(Chart) {
12492
+
12493
+ var noop = helpers.noop;
12494
+
12495
+ Chart.LinearScaleBase = Chart.Scale.extend({
12496
+ getRightValue: function(value) {
12497
+ if (typeof value === 'string') {
12498
+ return +value;
12499
+ }
12500
+ return Chart.Scale.prototype.getRightValue.call(this, value);
12501
+ },
12502
+
12503
+ handleTickRangeOptions: function() {
12504
+ var me = this;
12505
+ var opts = me.options;
12506
+ var tickOpts = opts.ticks;
12507
+
12508
+ // If we are forcing it to begin at 0, but 0 will already be rendered on the chart,
12509
+ // do nothing since that would make the chart weird. If the user really wants a weird chart
12510
+ // axis, they can manually override it
12511
+ if (tickOpts.beginAtZero) {
12512
+ var minSign = helpers.sign(me.min);
12513
+ var maxSign = helpers.sign(me.max);
12514
+
12515
+ if (minSign < 0 && maxSign < 0) {
12516
+ // move the top up to 0
12517
+ me.max = 0;
12518
+ } else if (minSign > 0 && maxSign > 0) {
12519
+ // move the bottom down to 0
12520
+ me.min = 0;
12521
+ }
12522
+ }
12523
+
12524
+ var setMin = tickOpts.min !== undefined || tickOpts.suggestedMin !== undefined;
12525
+ var setMax = tickOpts.max !== undefined || tickOpts.suggestedMax !== undefined;
12526
+
12527
+ if (tickOpts.min !== undefined) {
12528
+ me.min = tickOpts.min;
12529
+ } else if (tickOpts.suggestedMin !== undefined) {
12530
+ if (me.min === null) {
12531
+ me.min = tickOpts.suggestedMin;
12532
+ } else {
12533
+ me.min = Math.min(me.min, tickOpts.suggestedMin);
12534
+ }
12535
+ }
12536
+
12537
+ if (tickOpts.max !== undefined) {
12538
+ me.max = tickOpts.max;
12539
+ } else if (tickOpts.suggestedMax !== undefined) {
12540
+ if (me.max === null) {
12541
+ me.max = tickOpts.suggestedMax;
12542
+ } else {
12543
+ me.max = Math.max(me.max, tickOpts.suggestedMax);
12544
+ }
12545
+ }
12546
+
12547
+ if (setMin !== setMax) {
12548
+ // We set the min or the max but not both.
12549
+ // So ensure that our range is good
12550
+ // Inverted or 0 length range can happen when
12551
+ // ticks.min is set, and no datasets are visible
12552
+ if (me.min >= me.max) {
12553
+ if (setMin) {
12554
+ me.max = me.min + 1;
12555
+ } else {
12556
+ me.min = me.max - 1;
12557
+ }
12558
+ }
12559
+ }
12560
+
12561
+ if (me.min === me.max) {
12562
+ me.max++;
12563
+
12564
+ if (!tickOpts.beginAtZero) {
12565
+ me.min--;
12566
+ }
12567
+ }
12568
+ },
12569
+ getTickLimit: noop,
12570
+ handleDirectionalChanges: noop,
12571
+
12572
+ buildTicks: function() {
12573
+ var me = this;
12574
+ var opts = me.options;
12575
+ var tickOpts = opts.ticks;
12576
+
12577
+ // Figure out what the max number of ticks we can support it is based on the size of
12578
+ // the axis area. For now, we say that the minimum tick spacing in pixels must be 50
12579
+ // We also limit the maximum number of ticks to 11 which gives a nice 10 squares on
12580
+ // the graph. Make sure we always have at least 2 ticks
12581
+ var maxTicks = me.getTickLimit();
12582
+ maxTicks = Math.max(2, maxTicks);
12583
+
12584
+ var numericGeneratorOptions = {
12585
+ maxTicks: maxTicks,
12586
+ min: tickOpts.min,
12587
+ max: tickOpts.max,
12588
+ stepSize: helpers.valueOrDefault(tickOpts.fixedStepSize, tickOpts.stepSize)
12589
+ };
12590
+ var ticks = me.ticks = Ticks.generators.linear(numericGeneratorOptions, me);
12591
+
12592
+ me.handleDirectionalChanges();
12593
+
12594
+ // At this point, we need to update our max and min given the tick values since we have expanded the
12595
+ // range of the scale
12596
+ me.max = helpers.max(ticks);
12597
+ me.min = helpers.min(ticks);
12598
+
12599
+ if (tickOpts.reverse) {
12600
+ ticks.reverse();
12601
+
12602
+ me.start = me.max;
12603
+ me.end = me.min;
12604
+ } else {
12605
+ me.start = me.min;
12606
+ me.end = me.max;
12607
+ }
12608
+ },
12609
+ convertTicksToLabels: function() {
12610
+ var me = this;
12611
+ me.ticksAsNumbers = me.ticks.slice();
12612
+ me.zeroLineIndex = me.ticks.indexOf(0);
12613
+
12614
+ Chart.Scale.prototype.convertTicksToLabels.call(me);
12615
+ }
12616
+ });
12617
+ };
12618
+
12619
+ },{"34":34,"45":45}],55:[function(require,module,exports){
12620
+ 'use strict';
12621
+
12622
+ var helpers = require(45);
12623
+ var Ticks = require(34);
12624
+
12625
+ module.exports = function(Chart) {
12626
+
12627
+ var defaultConfig = {
12628
+ position: 'left',
12629
+
12630
+ // label settings
12631
+ ticks: {
12632
+ callback: Ticks.formatters.logarithmic
12633
+ }
12634
+ };
12635
+
12636
+ var LogarithmicScale = Chart.Scale.extend({
12637
+ determineDataLimits: function() {
12638
+ var me = this;
12639
+ var opts = me.options;
12640
+ var tickOpts = opts.ticks;
12641
+ var chart = me.chart;
12642
+ var data = chart.data;
12643
+ var datasets = data.datasets;
12644
+ var valueOrDefault = helpers.valueOrDefault;
12645
+ var isHorizontal = me.isHorizontal();
12646
+ function IDMatches(meta) {
12647
+ return isHorizontal ? meta.xAxisID === me.id : meta.yAxisID === me.id;
12648
+ }
12649
+
12650
+ // Calculate Range
12651
+ me.min = null;
12652
+ me.max = null;
12653
+ me.minNotZero = null;
12654
+
12655
+ var hasStacks = opts.stacked;
12656
+ if (hasStacks === undefined) {
12657
+ helpers.each(datasets, function(dataset, datasetIndex) {
12658
+ if (hasStacks) {
12659
+ return;
12660
+ }
12661
+
12662
+ var meta = chart.getDatasetMeta(datasetIndex);
12663
+ if (chart.isDatasetVisible(datasetIndex) && IDMatches(meta) &&
12664
+ meta.stack !== undefined) {
12665
+ hasStacks = true;
12666
+ }
12667
+ });
12668
+ }
12669
+
12670
+ if (opts.stacked || hasStacks) {
12671
+ var valuesPerStack = {};
12672
+
12673
+ helpers.each(datasets, function(dataset, datasetIndex) {
12674
+ var meta = chart.getDatasetMeta(datasetIndex);
12675
+ var key = [
12676
+ meta.type,
12677
+ // we have a separate stack for stack=undefined datasets when the opts.stacked is undefined
12678
+ ((opts.stacked === undefined && meta.stack === undefined) ? datasetIndex : ''),
12679
+ meta.stack
12680
+ ].join('.');
12681
+
12682
+ if (chart.isDatasetVisible(datasetIndex) && IDMatches(meta)) {
12683
+ if (valuesPerStack[key] === undefined) {
12684
+ valuesPerStack[key] = [];
12685
+ }
12686
+
12687
+ helpers.each(dataset.data, function(rawValue, index) {
12688
+ var values = valuesPerStack[key];
12689
+ var value = +me.getRightValue(rawValue);
12690
+ if (isNaN(value) || meta.data[index].hidden) {
12691
+ return;
12692
+ }
12693
+
12694
+ values[index] = values[index] || 0;
12695
+
12696
+ if (opts.relativePoints) {
12697
+ values[index] = 100;
12698
+ } else {
12699
+ // Don't need to split positive and negative since the log scale can't handle a 0 crossing
12700
+ values[index] += value;
12701
+ }
12702
+ });
12703
+ }
12704
+ });
12705
+
12706
+ helpers.each(valuesPerStack, function(valuesForType) {
12707
+ var minVal = helpers.min(valuesForType);
12708
+ var maxVal = helpers.max(valuesForType);
12709
+ me.min = me.min === null ? minVal : Math.min(me.min, minVal);
12710
+ me.max = me.max === null ? maxVal : Math.max(me.max, maxVal);
12711
+ });
12712
+
12713
+ } else {
12714
+ helpers.each(datasets, function(dataset, datasetIndex) {
12715
+ var meta = chart.getDatasetMeta(datasetIndex);
12716
+ if (chart.isDatasetVisible(datasetIndex) && IDMatches(meta)) {
12717
+ helpers.each(dataset.data, function(rawValue, index) {
12718
+ var value = +me.getRightValue(rawValue);
12719
+ if (isNaN(value) || meta.data[index].hidden) {
12720
+ return;
12721
+ }
12722
+
12723
+ if (me.min === null) {
12724
+ me.min = value;
12725
+ } else if (value < me.min) {
12726
+ me.min = value;
12727
+ }
12728
+
12729
+ if (me.max === null) {
12730
+ me.max = value;
12731
+ } else if (value > me.max) {
12732
+ me.max = value;
12733
+ }
12734
+
12735
+ if (value !== 0 && (me.minNotZero === null || value < me.minNotZero)) {
12736
+ me.minNotZero = value;
12737
+ }
12738
+ });
12739
+ }
12740
+ });
12741
+ }
12742
+
12743
+ me.min = valueOrDefault(tickOpts.min, me.min);
12744
+ me.max = valueOrDefault(tickOpts.max, me.max);
12745
+
12746
+ if (me.min === me.max) {
12747
+ if (me.min !== 0 && me.min !== null) {
12748
+ me.min = Math.pow(10, Math.floor(helpers.log10(me.min)) - 1);
12749
+ me.max = Math.pow(10, Math.floor(helpers.log10(me.max)) + 1);
12750
+ } else {
12751
+ me.min = 1;
12752
+ me.max = 10;
12753
+ }
12754
+ }
12755
+ },
12756
+ buildTicks: function() {
12757
+ var me = this;
12758
+ var opts = me.options;
12759
+ var tickOpts = opts.ticks;
12760
+
12761
+ var generationOptions = {
12762
+ min: tickOpts.min,
12763
+ max: tickOpts.max
12764
+ };
12765
+ var ticks = me.ticks = Ticks.generators.logarithmic(generationOptions, me);
12766
+
12767
+ if (!me.isHorizontal()) {
12768
+ // We are in a vertical orientation. The top value is the highest. So reverse the array
12769
+ ticks.reverse();
12770
+ }
12771
+
12772
+ // At this point, we need to update our max and min given the tick values since we have expanded the
12773
+ // range of the scale
12774
+ me.max = helpers.max(ticks);
12775
+ me.min = helpers.min(ticks);
12776
+
12777
+ if (tickOpts.reverse) {
12778
+ ticks.reverse();
12779
+
12780
+ me.start = me.max;
12781
+ me.end = me.min;
12782
+ } else {
12783
+ me.start = me.min;
12784
+ me.end = me.max;
12785
+ }
12786
+ },
12787
+ convertTicksToLabels: function() {
12788
+ this.tickValues = this.ticks.slice();
12789
+
12790
+ Chart.Scale.prototype.convertTicksToLabels.call(this);
12791
+ },
12792
+ // Get the correct tooltip label
12793
+ getLabelForIndex: function(index, datasetIndex) {
12794
+ return +this.getRightValue(this.chart.data.datasets[datasetIndex].data[index]);
12795
+ },
12796
+ getPixelForTick: function(index) {
12797
+ return this.getPixelForValue(this.tickValues[index]);
12798
+ },
12799
+ getPixelForValue: function(value) {
12800
+ var me = this;
12801
+ var start = me.start;
12802
+ var newVal = +me.getRightValue(value);
12803
+ var opts = me.options;
12804
+ var tickOpts = opts.ticks;
12805
+ var innerDimension, pixel, range;
12806
+
12807
+ if (me.isHorizontal()) {
12808
+ range = helpers.log10(me.end) - helpers.log10(start); // todo: if start === 0
12809
+ if (newVal === 0) {
12810
+ pixel = me.left;
12811
+ } else {
12812
+ innerDimension = me.width;
12813
+ pixel = me.left + (innerDimension / range * (helpers.log10(newVal) - helpers.log10(start)));
12814
+ }
12815
+ } else {
12816
+ // Bottom - top since pixels increase downward on a screen
12817
+ innerDimension = me.height;
12818
+ if (start === 0 && !tickOpts.reverse) {
12819
+ range = helpers.log10(me.end) - helpers.log10(me.minNotZero);
12820
+ if (newVal === start) {
12821
+ pixel = me.bottom;
12822
+ } else if (newVal === me.minNotZero) {
12823
+ pixel = me.bottom - innerDimension * 0.02;
12824
+ } else {
12825
+ pixel = me.bottom - innerDimension * 0.02 - (innerDimension * 0.98 / range * (helpers.log10(newVal) - helpers.log10(me.minNotZero)));
12826
+ }
12827
+ } else if (me.end === 0 && tickOpts.reverse) {
12828
+ range = helpers.log10(me.start) - helpers.log10(me.minNotZero);
12829
+ if (newVal === me.end) {
12830
+ pixel = me.top;
12831
+ } else if (newVal === me.minNotZero) {
12832
+ pixel = me.top + innerDimension * 0.02;
12833
+ } else {
12834
+ pixel = me.top + innerDimension * 0.02 + (innerDimension * 0.98 / range * (helpers.log10(newVal) - helpers.log10(me.minNotZero)));
12835
+ }
12836
+ } else if (newVal === 0) {
12837
+ pixel = tickOpts.reverse ? me.top : me.bottom;
12838
+ } else {
12839
+ range = helpers.log10(me.end) - helpers.log10(start);
12840
+ innerDimension = me.height;
12841
+ pixel = me.bottom - (innerDimension / range * (helpers.log10(newVal) - helpers.log10(start)));
12842
+ }
12843
+ }
12844
+ return pixel;
12845
+ },
12846
+ getValueForPixel: function(pixel) {
12847
+ var me = this;
12848
+ var range = helpers.log10(me.end) - helpers.log10(me.start);
12849
+ var value, innerDimension;
12850
+
12851
+ if (me.isHorizontal()) {
12852
+ innerDimension = me.width;
12853
+ value = me.start * Math.pow(10, (pixel - me.left) * range / innerDimension);
12854
+ } else { // todo: if start === 0
12855
+ innerDimension = me.height;
12856
+ value = Math.pow(10, (me.bottom - pixel) * range / innerDimension) / me.start;
12857
+ }
12858
+ return value;
12859
+ }
12860
+ });
12861
+ Chart.scaleService.registerScaleType('logarithmic', LogarithmicScale, defaultConfig);
12862
+
12863
+ };
12864
+
12865
+ },{"34":34,"45":45}],56:[function(require,module,exports){
12866
+ 'use strict';
12867
+
12868
+ var defaults = require(25);
12869
+ var helpers = require(45);
12870
+ var Ticks = require(34);
12871
+
12872
+ module.exports = function(Chart) {
12873
+
12874
+ var globalDefaults = defaults.global;
12875
+
12876
+ var defaultConfig = {
12877
+ display: true,
12878
+
12879
+ // Boolean - Whether to animate scaling the chart from the centre
12880
+ animate: true,
12881
+ position: 'chartArea',
12882
+
12883
+ angleLines: {
12884
+ display: true,
12885
+ color: 'rgba(0, 0, 0, 0.1)',
12886
+ lineWidth: 1
12887
+ },
12888
+
12889
+ gridLines: {
12890
+ circular: false
12891
+ },
12892
+
12893
+ // label settings
12894
+ ticks: {
12895
+ // Boolean - Show a backdrop to the scale label
12896
+ showLabelBackdrop: true,
12897
+
12898
+ // String - The colour of the label backdrop
12899
+ backdropColor: 'rgba(255,255,255,0.75)',
12900
+
12901
+ // Number - The backdrop padding above & below the label in pixels
12902
+ backdropPaddingY: 2,
12903
+
12904
+ // Number - The backdrop padding to the side of the label in pixels
12905
+ backdropPaddingX: 2,
12906
+
12907
+ callback: Ticks.formatters.linear
12908
+ },
12909
+
12910
+ pointLabels: {
12911
+ // Boolean - if true, show point labels
12912
+ display: true,
12913
+
12914
+ // Number - Point label font size in pixels
12915
+ fontSize: 10,
12916
+
12917
+ // Function - Used to convert point labels
12918
+ callback: function(label) {
12919
+ return label;
12920
+ }
12921
+ }
12922
+ };
12923
+
12924
+ function getValueCount(scale) {
12925
+ var opts = scale.options;
12926
+ return opts.angleLines.display || opts.pointLabels.display ? scale.chart.data.labels.length : 0;
12927
+ }
12928
+
12929
+ function getPointLabelFontOptions(scale) {
12930
+ var pointLabelOptions = scale.options.pointLabels;
12931
+ var fontSize = helpers.valueOrDefault(pointLabelOptions.fontSize, globalDefaults.defaultFontSize);
12932
+ var fontStyle = helpers.valueOrDefault(pointLabelOptions.fontStyle, globalDefaults.defaultFontStyle);
12933
+ var fontFamily = helpers.valueOrDefault(pointLabelOptions.fontFamily, globalDefaults.defaultFontFamily);
12934
+ var font = helpers.fontString(fontSize, fontStyle, fontFamily);
12935
+
12936
+ return {
12937
+ size: fontSize,
12938
+ style: fontStyle,
12939
+ family: fontFamily,
12940
+ font: font
12941
+ };
12942
+ }
12943
+
12944
+ function measureLabelSize(ctx, fontSize, label) {
12945
+ if (helpers.isArray(label)) {
12946
+ return {
12947
+ w: helpers.longestText(ctx, ctx.font, label),
12948
+ h: (label.length * fontSize) + ((label.length - 1) * 1.5 * fontSize)
12949
+ };
12950
+ }
12951
+
12952
+ return {
12953
+ w: ctx.measureText(label).width,
12954
+ h: fontSize
12955
+ };
12956
+ }
12957
+
12958
+ function determineLimits(angle, pos, size, min, max) {
12959
+ if (angle === min || angle === max) {
12960
+ return {
12961
+ start: pos - (size / 2),
12962
+ end: pos + (size / 2)
12963
+ };
12964
+ } else if (angle < min || angle > max) {
12965
+ return {
12966
+ start: pos - size - 5,
12967
+ end: pos
12968
+ };
12969
+ }
12970
+
12971
+ return {
12972
+ start: pos,
12973
+ end: pos + size + 5
12974
+ };
12975
+ }
12976
+
12977
+ /**
12978
+ * Helper function to fit a radial linear scale with point labels
12979
+ */
12980
+ function fitWithPointLabels(scale) {
12981
+ /*
12982
+ * Right, this is really confusing and there is a lot of maths going on here
12983
+ * The gist of the problem is here: https://gist.github.com/nnnick/696cc9c55f4b0beb8fe9
12984
+ *
12985
+ * Reaction: https://dl.dropboxusercontent.com/u/34601363/toomuchscience.gif
12986
+ *
12987
+ * Solution:
12988
+ *
12989
+ * We assume the radius of the polygon is half the size of the canvas at first
12990
+ * at each index we check if the text overlaps.
12991
+ *
12992
+ * Where it does, we store that angle and that index.
12993
+ *
12994
+ * After finding the largest index and angle we calculate how much we need to remove
12995
+ * from the shape radius to move the point inwards by that x.
12996
+ *
12997
+ * We average the left and right distances to get the maximum shape radius that can fit in the box
12998
+ * along with labels.
12999
+ *
13000
+ * Once we have that, we can find the centre point for the chart, by taking the x text protrusion
13001
+ * on each side, removing that from the size, halving it and adding the left x protrusion width.
13002
+ *
13003
+ * This will mean we have a shape fitted to the canvas, as large as it can be with the labels
13004
+ * and position it in the most space efficient manner
13005
+ *
13006
+ * https://dl.dropboxusercontent.com/u/34601363/yeahscience.gif
13007
+ */
13008
+
13009
+ var plFont = getPointLabelFontOptions(scale);
13010
+
13011
+ // Get maximum radius of the polygon. Either half the height (minus the text width) or half the width.
13012
+ // Use this to calculate the offset + change. - Make sure L/R protrusion is at least 0 to stop issues with centre points
13013
+ var largestPossibleRadius = Math.min(scale.height / 2, scale.width / 2);
13014
+ var furthestLimits = {
13015
+ r: scale.width,
13016
+ l: 0,
13017
+ t: scale.height,
13018
+ b: 0
13019
+ };
13020
+ var furthestAngles = {};
13021
+ var i, textSize, pointPosition;
13022
+
13023
+ scale.ctx.font = plFont.font;
13024
+ scale._pointLabelSizes = [];
13025
+
13026
+ var valueCount = getValueCount(scale);
13027
+ for (i = 0; i < valueCount; i++) {
13028
+ pointPosition = scale.getPointPosition(i, largestPossibleRadius);
13029
+ textSize = measureLabelSize(scale.ctx, plFont.size, scale.pointLabels[i] || '');
13030
+ scale._pointLabelSizes[i] = textSize;
13031
+
13032
+ // Add quarter circle to make degree 0 mean top of circle
13033
+ var angleRadians = scale.getIndexAngle(i);
13034
+ var angle = helpers.toDegrees(angleRadians) % 360;
13035
+ var hLimits = determineLimits(angle, pointPosition.x, textSize.w, 0, 180);
13036
+ var vLimits = determineLimits(angle, pointPosition.y, textSize.h, 90, 270);
13037
+
13038
+ if (hLimits.start < furthestLimits.l) {
13039
+ furthestLimits.l = hLimits.start;
13040
+ furthestAngles.l = angleRadians;
13041
+ }
13042
+
13043
+ if (hLimits.end > furthestLimits.r) {
13044
+ furthestLimits.r = hLimits.end;
13045
+ furthestAngles.r = angleRadians;
13046
+ }
13047
+
13048
+ if (vLimits.start < furthestLimits.t) {
13049
+ furthestLimits.t = vLimits.start;
13050
+ furthestAngles.t = angleRadians;
13051
+ }
13052
+
13053
+ if (vLimits.end > furthestLimits.b) {
13054
+ furthestLimits.b = vLimits.end;
13055
+ furthestAngles.b = angleRadians;
13056
+ }
13057
+ }
13058
+
13059
+ scale.setReductions(largestPossibleRadius, furthestLimits, furthestAngles);
13060
+ }
13061
+
13062
+ /**
13063
+ * Helper function to fit a radial linear scale with no point labels
13064
+ */
13065
+ function fit(scale) {
13066
+ var largestPossibleRadius = Math.min(scale.height / 2, scale.width / 2);
13067
+ scale.drawingArea = Math.round(largestPossibleRadius);
13068
+ scale.setCenterPoint(0, 0, 0, 0);
13069
+ }
13070
+
13071
+ function getTextAlignForAngle(angle) {
13072
+ if (angle === 0 || angle === 180) {
13073
+ return 'center';
13074
+ } else if (angle < 180) {
13075
+ return 'left';
13076
+ }
13077
+
13078
+ return 'right';
13079
+ }
13080
+
13081
+ function fillText(ctx, text, position, fontSize) {
13082
+ if (helpers.isArray(text)) {
13083
+ var y = position.y;
13084
+ var spacing = 1.5 * fontSize;
13085
+
13086
+ for (var i = 0; i < text.length; ++i) {
13087
+ ctx.fillText(text[i], position.x, y);
13088
+ y += spacing;
13089
+ }
13090
+ } else {
13091
+ ctx.fillText(text, position.x, position.y);
13092
+ }
13093
+ }
13094
+
13095
+ function adjustPointPositionForLabelHeight(angle, textSize, position) {
13096
+ if (angle === 90 || angle === 270) {
13097
+ position.y -= (textSize.h / 2);
13098
+ } else if (angle > 270 || angle < 90) {
13099
+ position.y -= textSize.h;
13100
+ }
13101
+ }
13102
+
13103
+ function drawPointLabels(scale) {
13104
+ var ctx = scale.ctx;
13105
+ var valueOrDefault = helpers.valueOrDefault;
13106
+ var opts = scale.options;
13107
+ var angleLineOpts = opts.angleLines;
13108
+ var pointLabelOpts = opts.pointLabels;
13109
+
13110
+ ctx.lineWidth = angleLineOpts.lineWidth;
13111
+ ctx.strokeStyle = angleLineOpts.color;
13112
+
13113
+ var outerDistance = scale.getDistanceFromCenterForValue(opts.ticks.reverse ? scale.min : scale.max);
13114
+
13115
+ // Point Label Font
13116
+ var plFont = getPointLabelFontOptions(scale);
13117
+
13118
+ ctx.textBaseline = 'top';
13119
+
13120
+ for (var i = getValueCount(scale) - 1; i >= 0; i--) {
13121
+ if (angleLineOpts.display) {
13122
+ var outerPosition = scale.getPointPosition(i, outerDistance);
13123
+ ctx.beginPath();
13124
+ ctx.moveTo(scale.xCenter, scale.yCenter);
13125
+ ctx.lineTo(outerPosition.x, outerPosition.y);
13126
+ ctx.stroke();
13127
+ ctx.closePath();
13128
+ }
13129
+
13130
+ if (pointLabelOpts.display) {
13131
+ // Extra 3px out for some label spacing
13132
+ var pointLabelPosition = scale.getPointPosition(i, outerDistance + 5);
13133
+
13134
+ // Keep this in loop since we may support array properties here
13135
+ var pointLabelFontColor = valueOrDefault(pointLabelOpts.fontColor, globalDefaults.defaultFontColor);
13136
+ ctx.font = plFont.font;
13137
+ ctx.fillStyle = pointLabelFontColor;
13138
+
13139
+ var angleRadians = scale.getIndexAngle(i);
13140
+ var angle = helpers.toDegrees(angleRadians);
13141
+ ctx.textAlign = getTextAlignForAngle(angle);
13142
+ adjustPointPositionForLabelHeight(angle, scale._pointLabelSizes[i], pointLabelPosition);
13143
+ fillText(ctx, scale.pointLabels[i] || '', pointLabelPosition, plFont.size);
13144
+ }
13145
+ }
13146
+ }
13147
+
13148
+ function drawRadiusLine(scale, gridLineOpts, radius, index) {
13149
+ var ctx = scale.ctx;
13150
+ ctx.strokeStyle = helpers.valueAtIndexOrDefault(gridLineOpts.color, index - 1);
13151
+ ctx.lineWidth = helpers.valueAtIndexOrDefault(gridLineOpts.lineWidth, index - 1);
13152
+
13153
+ if (scale.options.gridLines.circular) {
13154
+ // Draw circular arcs between the points
13155
+ ctx.beginPath();
13156
+ ctx.arc(scale.xCenter, scale.yCenter, radius, 0, Math.PI * 2);
13157
+ ctx.closePath();
13158
+ ctx.stroke();
13159
+ } else {
13160
+ // Draw straight lines connecting each index
13161
+ var valueCount = getValueCount(scale);
13162
+
13163
+ if (valueCount === 0) {
13164
+ return;
13165
+ }
13166
+
13167
+ ctx.beginPath();
13168
+ var pointPosition = scale.getPointPosition(0, radius);
13169
+ ctx.moveTo(pointPosition.x, pointPosition.y);
13170
+
13171
+ for (var i = 1; i < valueCount; i++) {
13172
+ pointPosition = scale.getPointPosition(i, radius);
13173
+ ctx.lineTo(pointPosition.x, pointPosition.y);
13174
+ }
13175
+
13176
+ ctx.closePath();
13177
+ ctx.stroke();
13178
+ }
13179
+ }
13180
+
13181
+ function numberOrZero(param) {
13182
+ return helpers.isNumber(param) ? param : 0;
13183
+ }
13184
+
13185
+ var LinearRadialScale = Chart.LinearScaleBase.extend({
13186
+ setDimensions: function() {
13187
+ var me = this;
13188
+ var opts = me.options;
13189
+ var tickOpts = opts.ticks;
13190
+ // Set the unconstrained dimension before label rotation
13191
+ me.width = me.maxWidth;
13192
+ me.height = me.maxHeight;
13193
+ me.xCenter = Math.round(me.width / 2);
13194
+ me.yCenter = Math.round(me.height / 2);
13195
+
13196
+ var minSize = helpers.min([me.height, me.width]);
13197
+ var tickFontSize = helpers.valueOrDefault(tickOpts.fontSize, globalDefaults.defaultFontSize);
13198
+ me.drawingArea = opts.display ? (minSize / 2) - (tickFontSize / 2 + tickOpts.backdropPaddingY) : (minSize / 2);
13199
+ },
13200
+ determineDataLimits: function() {
13201
+ var me = this;
13202
+ var chart = me.chart;
13203
+ var min = Number.POSITIVE_INFINITY;
13204
+ var max = Number.NEGATIVE_INFINITY;
13205
+
13206
+ helpers.each(chart.data.datasets, function(dataset, datasetIndex) {
13207
+ if (chart.isDatasetVisible(datasetIndex)) {
13208
+ var meta = chart.getDatasetMeta(datasetIndex);
13209
+
13210
+ helpers.each(dataset.data, function(rawValue, index) {
13211
+ var value = +me.getRightValue(rawValue);
13212
+ if (isNaN(value) || meta.data[index].hidden) {
13213
+ return;
13214
+ }
13215
+
13216
+ min = Math.min(value, min);
13217
+ max = Math.max(value, max);
13218
+ });
13219
+ }
13220
+ });
13221
+
13222
+ me.min = (min === Number.POSITIVE_INFINITY ? 0 : min);
13223
+ me.max = (max === Number.NEGATIVE_INFINITY ? 0 : max);
13224
+
13225
+ // Common base implementation to handle ticks.min, ticks.max, ticks.beginAtZero
13226
+ me.handleTickRangeOptions();
13227
+ },
13228
+ getTickLimit: function() {
13229
+ var tickOpts = this.options.ticks;
13230
+ var tickFontSize = helpers.valueOrDefault(tickOpts.fontSize, globalDefaults.defaultFontSize);
13231
+ return Math.min(tickOpts.maxTicksLimit ? tickOpts.maxTicksLimit : 11, Math.ceil(this.drawingArea / (1.5 * tickFontSize)));
13232
+ },
13233
+ convertTicksToLabels: function() {
13234
+ var me = this;
13235
+
13236
+ Chart.LinearScaleBase.prototype.convertTicksToLabels.call(me);
13237
+
13238
+ // Point labels
13239
+ me.pointLabels = me.chart.data.labels.map(me.options.pointLabels.callback, me);
13240
+ },
13241
+ getLabelForIndex: function(index, datasetIndex) {
13242
+ return +this.getRightValue(this.chart.data.datasets[datasetIndex].data[index]);
13243
+ },
13244
+ fit: function() {
13245
+ if (this.options.pointLabels.display) {
13246
+ fitWithPointLabels(this);
13247
+ } else {
13248
+ fit(this);
13249
+ }
13250
+ },
13251
+ /**
13252
+ * Set radius reductions and determine new radius and center point
13253
+ * @private
13254
+ */
13255
+ setReductions: function(largestPossibleRadius, furthestLimits, furthestAngles) {
13256
+ var me = this;
13257
+ var radiusReductionLeft = furthestLimits.l / Math.sin(furthestAngles.l);
13258
+ var radiusReductionRight = Math.max(furthestLimits.r - me.width, 0) / Math.sin(furthestAngles.r);
13259
+ var radiusReductionTop = -furthestLimits.t / Math.cos(furthestAngles.t);
13260
+ var radiusReductionBottom = -Math.max(furthestLimits.b - me.height, 0) / Math.cos(furthestAngles.b);
13261
+
13262
+ radiusReductionLeft = numberOrZero(radiusReductionLeft);
13263
+ radiusReductionRight = numberOrZero(radiusReductionRight);
13264
+ radiusReductionTop = numberOrZero(radiusReductionTop);
13265
+ radiusReductionBottom = numberOrZero(radiusReductionBottom);
13266
+
13267
+ me.drawingArea = Math.min(
13268
+ Math.round(largestPossibleRadius - (radiusReductionLeft + radiusReductionRight) / 2),
13269
+ Math.round(largestPossibleRadius - (radiusReductionTop + radiusReductionBottom) / 2));
13270
+ me.setCenterPoint(radiusReductionLeft, radiusReductionRight, radiusReductionTop, radiusReductionBottom);
13271
+ },
13272
+ setCenterPoint: function(leftMovement, rightMovement, topMovement, bottomMovement) {
13273
+ var me = this;
13274
+ var maxRight = me.width - rightMovement - me.drawingArea;
13275
+ var maxLeft = leftMovement + me.drawingArea;
13276
+ var maxTop = topMovement + me.drawingArea;
13277
+ var maxBottom = me.height - bottomMovement - me.drawingArea;
13278
+
13279
+ me.xCenter = Math.round(((maxLeft + maxRight) / 2) + me.left);
13280
+ me.yCenter = Math.round(((maxTop + maxBottom) / 2) + me.top);
13281
+ },
13282
+
13283
+ getIndexAngle: function(index) {
13284
+ var angleMultiplier = (Math.PI * 2) / getValueCount(this);
13285
+ var startAngle = this.chart.options && this.chart.options.startAngle ?
13286
+ this.chart.options.startAngle :
13287
+ 0;
13288
+
13289
+ var startAngleRadians = startAngle * Math.PI * 2 / 360;
13290
+
13291
+ // Start from the top instead of right, so remove a quarter of the circle
13292
+ return index * angleMultiplier + startAngleRadians;
13293
+ },
13294
+ getDistanceFromCenterForValue: function(value) {
13295
+ var me = this;
13296
+
13297
+ if (value === null) {
13298
+ return 0; // null always in center
13299
+ }
13300
+
13301
+ // Take into account half font size + the yPadding of the top value
13302
+ var scalingFactor = me.drawingArea / (me.max - me.min);
13303
+ if (me.options.ticks.reverse) {
13304
+ return (me.max - value) * scalingFactor;
13305
+ }
13306
+ return (value - me.min) * scalingFactor;
13307
+ },
13308
+ getPointPosition: function(index, distanceFromCenter) {
13309
+ var me = this;
13310
+ var thisAngle = me.getIndexAngle(index) - (Math.PI / 2);
13311
+ return {
13312
+ x: Math.round(Math.cos(thisAngle) * distanceFromCenter) + me.xCenter,
13313
+ y: Math.round(Math.sin(thisAngle) * distanceFromCenter) + me.yCenter
13314
+ };
13315
+ },
13316
+ getPointPositionForValue: function(index, value) {
13317
+ return this.getPointPosition(index, this.getDistanceFromCenterForValue(value));
13318
+ },
13319
+
13320
+ getBasePosition: function() {
13321
+ var me = this;
13322
+ var min = me.min;
13323
+ var max = me.max;
13324
+
13325
+ return me.getPointPositionForValue(0,
13326
+ me.beginAtZero ? 0 :
13327
+ min < 0 && max < 0 ? max :
13328
+ min > 0 && max > 0 ? min :
13329
+ 0);
13330
+ },
13331
+
13332
+ draw: function() {
13333
+ var me = this;
13334
+ var opts = me.options;
13335
+ var gridLineOpts = opts.gridLines;
13336
+ var tickOpts = opts.ticks;
13337
+ var valueOrDefault = helpers.valueOrDefault;
13338
+
13339
+ if (opts.display) {
13340
+ var ctx = me.ctx;
13341
+ var startAngle = this.getIndexAngle(0);
13342
+
13343
+ // Tick Font
13344
+ var tickFontSize = valueOrDefault(tickOpts.fontSize, globalDefaults.defaultFontSize);
13345
+ var tickFontStyle = valueOrDefault(tickOpts.fontStyle, globalDefaults.defaultFontStyle);
13346
+ var tickFontFamily = valueOrDefault(tickOpts.fontFamily, globalDefaults.defaultFontFamily);
13347
+ var tickLabelFont = helpers.fontString(tickFontSize, tickFontStyle, tickFontFamily);
13348
+
13349
+ helpers.each(me.ticks, function(label, index) {
13350
+ // Don't draw a centre value (if it is minimum)
13351
+ if (index > 0 || tickOpts.reverse) {
13352
+ var yCenterOffset = me.getDistanceFromCenterForValue(me.ticksAsNumbers[index]);
13353
+
13354
+ // Draw circular lines around the scale
13355
+ if (gridLineOpts.display && index !== 0) {
13356
+ drawRadiusLine(me, gridLineOpts, yCenterOffset, index);
13357
+ }
13358
+
13359
+ if (tickOpts.display) {
13360
+ var tickFontColor = valueOrDefault(tickOpts.fontColor, globalDefaults.defaultFontColor);
13361
+ ctx.font = tickLabelFont;
13362
+
13363
+ ctx.save();
13364
+ ctx.translate(me.xCenter, me.yCenter);
13365
+ ctx.rotate(startAngle);
13366
+
13367
+ if (tickOpts.showLabelBackdrop) {
13368
+ var labelWidth = ctx.measureText(label).width;
13369
+ ctx.fillStyle = tickOpts.backdropColor;
13370
+ ctx.fillRect(
13371
+ -labelWidth / 2 - tickOpts.backdropPaddingX,
13372
+ -yCenterOffset - tickFontSize / 2 - tickOpts.backdropPaddingY,
13373
+ labelWidth + tickOpts.backdropPaddingX * 2,
13374
+ tickFontSize + tickOpts.backdropPaddingY * 2
13375
+ );
13376
+ }
13377
+
13378
+ ctx.textAlign = 'center';
13379
+ ctx.textBaseline = 'middle';
13380
+ ctx.fillStyle = tickFontColor;
13381
+ ctx.fillText(label, 0, -yCenterOffset);
13382
+ ctx.restore();
13383
+ }
13384
+ }
13385
+ });
13386
+
13387
+ if (opts.angleLines.display || opts.pointLabels.display) {
13388
+ drawPointLabels(me);
13389
+ }
13390
+ }
13391
+ }
13392
+ });
13393
+ Chart.scaleService.registerScaleType('radialLinear', LinearRadialScale, defaultConfig);
13394
+
13395
+ };
13396
+
13397
+ },{"25":25,"34":34,"45":45}],57:[function(require,module,exports){
13398
+ /* global window: false */
13399
+ 'use strict';
13400
+
13401
+ var moment = require(1);
13402
+ moment = typeof moment === 'function' ? moment : window.moment;
13403
+
13404
+ var defaults = require(25);
13405
+ var helpers = require(45);
13406
+
13407
+ // Integer constants are from the ES6 spec.
13408
+ var MIN_INTEGER = Number.MIN_SAFE_INTEGER || -9007199254740991;
13409
+ var MAX_INTEGER = Number.MAX_SAFE_INTEGER || 9007199254740991;
13410
+
13411
+ var INTERVALS = {
13412
+ millisecond: {
13413
+ common: true,
13414
+ size: 1,
13415
+ steps: [1, 2, 5, 10, 20, 50, 100, 250, 500]
13416
+ },
13417
+ second: {
13418
+ common: true,
13419
+ size: 1000,
13420
+ steps: [1, 2, 5, 10, 30]
13421
+ },
13422
+ minute: {
13423
+ common: true,
13424
+ size: 60000,
13425
+ steps: [1, 2, 5, 10, 30]
13426
+ },
13427
+ hour: {
13428
+ common: true,
13429
+ size: 3600000,
13430
+ steps: [1, 2, 3, 6, 12]
13431
+ },
13432
+ day: {
13433
+ common: true,
13434
+ size: 86400000,
13435
+ steps: [1, 2, 5]
13436
+ },
13437
+ week: {
13438
+ common: false,
13439
+ size: 604800000,
13440
+ steps: [1, 2, 3, 4]
13441
+ },
13442
+ month: {
13443
+ common: true,
13444
+ size: 2.628e9,
13445
+ steps: [1, 2, 3]
13446
+ },
13447
+ quarter: {
13448
+ common: false,
13449
+ size: 7.884e9,
13450
+ steps: [1, 2, 3, 4]
13451
+ },
13452
+ year: {
13453
+ common: true,
13454
+ size: 3.154e10
13455
+ }
13456
+ };
13457
+
13458
+ var UNITS = Object.keys(INTERVALS);
13459
+
13460
+ function sorter(a, b) {
13461
+ return a - b;
13462
+ }
13463
+
13464
+ function arrayUnique(items) {
13465
+ var hash = {};
13466
+ var out = [];
13467
+ var i, ilen, item;
13468
+
13469
+ for (i = 0, ilen = items.length; i < ilen; ++i) {
13470
+ item = items[i];
13471
+ if (!hash[item]) {
13472
+ hash[item] = true;
13473
+ out.push(item);
13474
+ }
13475
+ }
13476
+
13477
+ return out;
13478
+ }
13479
+
13480
+ /**
13481
+ * Returns an array of {time, pos} objects used to interpolate a specific `time` or position
13482
+ * (`pos`) on the scale, by searching entries before and after the requested value. `pos` is
13483
+ * a decimal between 0 and 1: 0 being the start of the scale (left or top) and 1 the other
13484
+ * extremity (left + width or top + height). Note that it would be more optimized to directly
13485
+ * store pre-computed pixels, but the scale dimensions are not guaranteed at the time we need
13486
+ * to create the lookup table. The table ALWAYS contains at least two items: min and max.
13487
+ *
13488
+ * @param {Number[]} timestamps - timestamps sorted from lowest to highest.
13489
+ * @param {String} distribution - If 'linear', timestamps will be spread linearly along the min
13490
+ * and max range, so basically, the table will contains only two items: {min, 0} and {max, 1}.
13491
+ * If 'series', timestamps will be positioned at the same distance from each other. In this
13492
+ * case, only timestamps that break the time linearity are registered, meaning that in the
13493
+ * best case, all timestamps are linear, the table contains only min and max.
13494
+ */
13495
+ function buildLookupTable(timestamps, min, max, distribution) {
13496
+ if (distribution === 'linear' || !timestamps.length) {
13497
+ return [
13498
+ {time: min, pos: 0},
13499
+ {time: max, pos: 1}
13500
+ ];
13501
+ }
13502
+
13503
+ var table = [];
13504
+ var items = [min];
13505
+ var i, ilen, prev, curr, next;
13506
+
13507
+ for (i = 0, ilen = timestamps.length; i < ilen; ++i) {
13508
+ curr = timestamps[i];
13509
+ if (curr > min && curr < max) {
13510
+ items.push(curr);
13511
+ }
13512
+ }
13513
+
13514
+ items.push(max);
13515
+
13516
+ for (i = 0, ilen = items.length; i < ilen; ++i) {
13517
+ next = items[i + 1];
13518
+ prev = items[i - 1];
13519
+ curr = items[i];
13520
+
13521
+ // only add points that breaks the scale linearity
13522
+ if (prev === undefined || next === undefined || Math.round((next + prev) / 2) !== curr) {
13523
+ table.push({time: curr, pos: i / (ilen - 1)});
13524
+ }
13525
+ }
13526
+
13527
+ return table;
13528
+ }
13529
+
13530
+ // @see adapted from http://www.anujgakhar.com/2014/03/01/binary-search-in-javascript/
13531
+ function lookup(table, key, value) {
13532
+ var lo = 0;
13533
+ var hi = table.length - 1;
13534
+ var mid, i0, i1;
13535
+
13536
+ while (lo >= 0 && lo <= hi) {
13537
+ mid = (lo + hi) >> 1;
13538
+ i0 = table[mid - 1] || null;
13539
+ i1 = table[mid];
13540
+
13541
+ if (!i0) {
13542
+ // given value is outside table (before first item)
13543
+ return {lo: null, hi: i1};
13544
+ } else if (i1[key] < value) {
13545
+ lo = mid + 1;
13546
+ } else if (i0[key] > value) {
13547
+ hi = mid - 1;
13548
+ } else {
13549
+ return {lo: i0, hi: i1};
13550
+ }
13551
+ }
13552
+
13553
+ // given value is outside table (after last item)
13554
+ return {lo: i1, hi: null};
13555
+ }
13556
+
13557
+ /**
13558
+ * Linearly interpolates the given source `value` using the table items `skey` values and
13559
+ * returns the associated `tkey` value. For example, interpolate(table, 'time', 42, 'pos')
13560
+ * returns the position for a timestamp equal to 42. If value is out of bounds, values at
13561
+ * index [0, 1] or [n - 1, n] are used for the interpolation.
13562
+ */
13563
+ function interpolate(table, skey, sval, tkey) {
13564
+ var range = lookup(table, skey, sval);
13565
+
13566
+ // Note: the lookup table ALWAYS contains at least 2 items (min and max)
13567
+ var prev = !range.lo ? table[0] : !range.hi ? table[table.length - 2] : range.lo;
13568
+ var next = !range.lo ? table[1] : !range.hi ? table[table.length - 1] : range.hi;
13569
+
13570
+ var span = next[skey] - prev[skey];
13571
+ var ratio = span ? (sval - prev[skey]) / span : 0;
13572
+ var offset = (next[tkey] - prev[tkey]) * ratio;
13573
+
13574
+ return prev[tkey] + offset;
13575
+ }
13576
+
13577
+ /**
13578
+ * Convert the given value to a moment object using the given time options.
13579
+ * @see http://momentjs.com/docs/#/parsing/
13580
+ */
13581
+ function momentify(value, options) {
13582
+ var parser = options.parser;
13583
+ var format = options.parser || options.format;
13584
+
13585
+ if (typeof parser === 'function') {
13586
+ return parser(value);
13587
+ }
13588
+
13589
+ if (typeof value === 'string' && typeof format === 'string') {
13590
+ return moment(value, format);
13591
+ }
13592
+
13593
+ if (!(value instanceof moment)) {
13594
+ value = moment(value);
13595
+ }
13596
+
13597
+ if (value.isValid()) {
13598
+ return value;
13599
+ }
13600
+
13601
+ // Labels are in an incompatible moment format and no `parser` has been provided.
13602
+ // The user might still use the deprecated `format` option to convert his inputs.
13603
+ if (typeof format === 'function') {
13604
+ return format(value);
13605
+ }
13606
+
13607
+ return value;
13608
+ }
13609
+
13610
+ function parse(input, scale) {
13611
+ if (helpers.isNullOrUndef(input)) {
13612
+ return null;
13613
+ }
13614
+
13615
+ var options = scale.options.time;
13616
+ var value = momentify(scale.getRightValue(input), options);
13617
+ if (!value.isValid()) {
13618
+ return null;
13619
+ }
13620
+
13621
+ if (options.round) {
13622
+ value.startOf(options.round);
13623
+ }
13624
+
13625
+ return value.valueOf();
13626
+ }
13627
+
13628
+ /**
13629
+ * Returns the number of unit to skip to be able to display up to `capacity` number of ticks
13630
+ * in `unit` for the given `min` / `max` range and respecting the interval steps constraints.
13631
+ */
13632
+ function determineStepSize(min, max, unit, capacity) {
13633
+ var range = max - min;
13634
+ var interval = INTERVALS[unit];
13635
+ var milliseconds = interval.size;
13636
+ var steps = interval.steps;
13637
+ var i, ilen, factor;
13638
+
13639
+ if (!steps) {
13640
+ return Math.ceil(range / ((capacity || 1) * milliseconds));
13641
+ }
13642
+
13643
+ for (i = 0, ilen = steps.length; i < ilen; ++i) {
13644
+ factor = steps[i];
13645
+ if (Math.ceil(range / (milliseconds * factor)) <= capacity) {
13646
+ break;
13647
+ }
13648
+ }
13649
+
13650
+ return factor;
13651
+ }
13652
+
13653
+ /**
13654
+ * Figures out what unit results in an appropriate number of auto-generated ticks
13655
+ */
13656
+ function determineUnitForAutoTicks(minUnit, min, max, capacity) {
13657
+ var ilen = UNITS.length;
13658
+ var i, interval, factor;
13659
+
13660
+ for (i = UNITS.indexOf(minUnit); i < ilen - 1; ++i) {
13661
+ interval = INTERVALS[UNITS[i]];
13662
+ factor = interval.steps ? interval.steps[interval.steps.length - 1] : MAX_INTEGER;
13663
+
13664
+ if (interval.common && Math.ceil((max - min) / (factor * interval.size)) <= capacity) {
13665
+ return UNITS[i];
13666
+ }
13667
+ }
13668
+
13669
+ return UNITS[ilen - 1];
13670
+ }
13671
+
13672
+ /**
13673
+ * Figures out what unit to format a set of ticks with
13674
+ */
13675
+ function determineUnitForFormatting(ticks, minUnit, min, max) {
13676
+ var duration = moment.duration(moment(max).diff(moment(min)));
13677
+ var ilen = UNITS.length;
13678
+ var i, unit;
13679
+
13680
+ for (i = ilen - 1; i >= UNITS.indexOf(minUnit); i--) {
13681
+ unit = UNITS[i];
13682
+ if (INTERVALS[unit].common && duration.as(unit) >= ticks.length) {
13683
+ return unit;
13684
+ }
13685
+ }
13686
+
13687
+ return UNITS[minUnit ? UNITS.indexOf(minUnit) : 0];
13688
+ }
13689
+
13690
+ function determineMajorUnit(unit) {
13691
+ for (var i = UNITS.indexOf(unit) + 1, ilen = UNITS.length; i < ilen; ++i) {
13692
+ if (INTERVALS[UNITS[i]].common) {
13693
+ return UNITS[i];
13694
+ }
13695
+ }
13696
+ }
13697
+
13698
+ /**
13699
+ * Generates a maximum of `capacity` timestamps between min and max, rounded to the
13700
+ * `minor` unit, aligned on the `major` unit and using the given scale time `options`.
13701
+ * Important: this method can return ticks outside the min and max range, it's the
13702
+ * responsibility of the calling code to clamp values if needed.
13703
+ */
13704
+ function generate(min, max, capacity, options) {
13705
+ var timeOpts = options.time;
13706
+ var minor = timeOpts.unit || determineUnitForAutoTicks(timeOpts.minUnit, min, max, capacity);
13707
+ var major = determineMajorUnit(minor);
13708
+ var stepSize = helpers.valueOrDefault(timeOpts.stepSize, timeOpts.unitStepSize);
13709
+ var weekday = minor === 'week' ? timeOpts.isoWeekday : false;
13710
+ var majorTicksEnabled = options.ticks.major.enabled;
13711
+ var interval = INTERVALS[minor];
13712
+ var first = moment(min);
13713
+ var last = moment(max);
13714
+ var ticks = [];
13715
+ var time;
13716
+
13717
+ if (!stepSize) {
13718
+ stepSize = determineStepSize(min, max, minor, capacity);
13719
+ }
13720
+
13721
+ // For 'week' unit, handle the first day of week option
13722
+ if (weekday) {
13723
+ first = first.isoWeekday(weekday);
13724
+ last = last.isoWeekday(weekday);
13725
+ }
13726
+
13727
+ // Align first/last ticks on unit
13728
+ first = first.startOf(weekday ? 'day' : minor);
13729
+ last = last.startOf(weekday ? 'day' : minor);
13730
+
13731
+ // Make sure that the last tick include max
13732
+ if (last < max) {
13733
+ last.add(1, minor);
13734
+ }
13735
+
13736
+ time = moment(first);
13737
+
13738
+ if (majorTicksEnabled && major && !weekday && !timeOpts.round) {
13739
+ // Align the first tick on the previous `minor` unit aligned on the `major` unit:
13740
+ // we first aligned time on the previous `major` unit then add the number of full
13741
+ // stepSize there is between first and the previous major time.
13742
+ time.startOf(major);
13743
+ time.add(~~((first - time) / (interval.size * stepSize)) * stepSize, minor);
13744
+ }
13745
+
13746
+ for (; time < last; time.add(stepSize, minor)) {
13747
+ ticks.push(+time);
13748
+ }
13749
+
13750
+ ticks.push(+time);
13751
+
13752
+ return ticks;
13753
+ }
13754
+
13755
+ /**
13756
+ * Returns the right and left offsets from edges in the form of {left, right}.
13757
+ * Offsets are added when the `offset` option is true.
13758
+ */
13759
+ function computeOffsets(table, ticks, min, max, options) {
13760
+ var left = 0;
13761
+ var right = 0;
13762
+ var upper, lower;
13763
+
13764
+ if (options.offset && ticks.length) {
13765
+ if (!options.time.min) {
13766
+ upper = ticks.length > 1 ? ticks[1] : max;
13767
+ lower = ticks[0];
13768
+ left = (
13769
+ interpolate(table, 'time', upper, 'pos') -
13770
+ interpolate(table, 'time', lower, 'pos')
13771
+ ) / 2;
13772
+ }
13773
+ if (!options.time.max) {
13774
+ upper = ticks[ticks.length - 1];
13775
+ lower = ticks.length > 1 ? ticks[ticks.length - 2] : min;
13776
+ right = (
13777
+ interpolate(table, 'time', upper, 'pos') -
13778
+ interpolate(table, 'time', lower, 'pos')
13779
+ ) / 2;
13780
+ }
13781
+ }
13782
+
13783
+ return {left: left, right: right};
13784
+ }
13785
+
13786
+ function ticksFromTimestamps(values, majorUnit) {
13787
+ var ticks = [];
13788
+ var i, ilen, value, major;
13789
+
13790
+ for (i = 0, ilen = values.length; i < ilen; ++i) {
13791
+ value = values[i];
13792
+ major = majorUnit ? value === +moment(value).startOf(majorUnit) : false;
13793
+
13794
+ ticks.push({
13795
+ value: value,
13796
+ major: major
13797
+ });
13798
+ }
13799
+
13800
+ return ticks;
13801
+ }
13802
+
13803
+ module.exports = function(Chart) {
13804
+
13805
+ var defaultConfig = {
13806
+ position: 'bottom',
13807
+
13808
+ /**
13809
+ * Data distribution along the scale:
13810
+ * - 'linear': data are spread according to their time (distances can vary),
13811
+ * - 'series': data are spread at the same distance from each other.
13812
+ * @see https://github.com/chartjs/Chart.js/pull/4507
13813
+ * @since 2.7.0
13814
+ */
13815
+ distribution: 'linear',
13816
+
13817
+ /**
13818
+ * Scale boundary strategy (bypassed by min/max time options)
13819
+ * - `data`: make sure data are fully visible, ticks outside are removed
13820
+ * - `ticks`: make sure ticks are fully visible, data outside are truncated
13821
+ * @see https://github.com/chartjs/Chart.js/pull/4556
13822
+ * @since 2.7.0
13823
+ */
13824
+ bounds: 'data',
13825
+
13826
+ time: {
13827
+ 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
13828
+ format: false, // DEPRECATED false == date objects, moment object, callback or a pattern string from http://momentjs.com/docs/#/parsing/string-format/
13829
+ unit: false, // false == automatic or override with week, month, year, etc.
13830
+ round: false, // none, or override with week, month, year, etc.
13831
+ displayFormat: false, // DEPRECATED
13832
+ isoWeekday: false, // override week start day - see http://momentjs.com/docs/#/get-set/iso-weekday/
13833
+ minUnit: 'millisecond',
13834
+
13835
+ // defaults to unit's corresponding unitFormat below or override using pattern string from http://momentjs.com/docs/#/displaying/format/
13836
+ displayFormats: {
13837
+ millisecond: 'h:mm:ss.SSS a', // 11:20:01.123 AM,
13838
+ second: 'h:mm:ss a', // 11:20:01 AM
13839
+ minute: 'h:mm a', // 11:20 AM
13840
+ hour: 'hA', // 5PM
13841
+ day: 'MMM D', // Sep 4
13842
+ week: 'll', // Week 46, or maybe "[W]WW - YYYY" ?
13843
+ month: 'MMM YYYY', // Sept 2015
13844
+ quarter: '[Q]Q - YYYY', // Q3
13845
+ year: 'YYYY' // 2015
13846
+ },
13847
+ },
13848
+ ticks: {
13849
+ autoSkip: false,
13850
+
13851
+ /**
13852
+ * Ticks generation input values:
13853
+ * - 'auto': generates "optimal" ticks based on scale size and time options.
13854
+ * - 'data': generates ticks from data (including labels from data {t|x|y} objects).
13855
+ * - 'labels': generates ticks from user given `data.labels` values ONLY.
13856
+ * @see https://github.com/chartjs/Chart.js/pull/4507
13857
+ * @since 2.7.0
13858
+ */
13859
+ source: 'auto',
13860
+
13861
+ major: {
13862
+ enabled: false
13863
+ }
13864
+ }
13865
+ };
13866
+
13867
+ var TimeScale = Chart.Scale.extend({
13868
+ initialize: function() {
13869
+ if (!moment) {
13870
+ 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');
13871
+ }
13872
+
13873
+ this.mergeTicksOptions();
13874
+
13875
+ Chart.Scale.prototype.initialize.call(this);
13876
+ },
13877
+
13878
+ update: function() {
13879
+ var me = this;
13880
+ var options = me.options;
13881
+
13882
+ // DEPRECATIONS: output a message only one time per update
13883
+ if (options.time && options.time.format) {
13884
+ console.warn('options.time.format is deprecated and replaced by options.time.parser.');
13885
+ }
13886
+
13887
+ return Chart.Scale.prototype.update.apply(me, arguments);
13888
+ },
13889
+
13890
+ /**
13891
+ * Allows data to be referenced via 't' attribute
13892
+ */
13893
+ getRightValue: function(rawValue) {
13894
+ if (rawValue && rawValue.t !== undefined) {
13895
+ rawValue = rawValue.t;
13896
+ }
13897
+ return Chart.Scale.prototype.getRightValue.call(this, rawValue);
13898
+ },
13899
+
13900
+ determineDataLimits: function() {
13901
+ var me = this;
13902
+ var chart = me.chart;
13903
+ var timeOpts = me.options.time;
13904
+ var min = MAX_INTEGER;
13905
+ var max = MIN_INTEGER;
13906
+ var timestamps = [];
13907
+ var datasets = [];
13908
+ var labels = [];
13909
+ var i, j, ilen, jlen, data, timestamp;
13910
+
13911
+ // Convert labels to timestamps
13912
+ for (i = 0, ilen = chart.data.labels.length; i < ilen; ++i) {
13913
+ labels.push(parse(chart.data.labels[i], me));
13914
+ }
13915
+
13916
+ // Convert data to timestamps
13917
+ for (i = 0, ilen = (chart.data.datasets || []).length; i < ilen; ++i) {
13918
+ if (chart.isDatasetVisible(i)) {
13919
+ data = chart.data.datasets[i].data;
13920
+
13921
+ // Let's consider that all data have the same format.
13922
+ if (helpers.isObject(data[0])) {
13923
+ datasets[i] = [];
13924
+
13925
+ for (j = 0, jlen = data.length; j < jlen; ++j) {
13926
+ timestamp = parse(data[j], me);
13927
+ timestamps.push(timestamp);
13928
+ datasets[i][j] = timestamp;
13929
+ }
13930
+ } else {
13931
+ timestamps.push.apply(timestamps, labels);
13932
+ datasets[i] = labels.slice(0);
13933
+ }
13934
+ } else {
13935
+ datasets[i] = [];
13936
+ }
13937
+ }
13938
+
13939
+ if (labels.length) {
13940
+ // Sort labels **after** data have been converted
13941
+ labels = arrayUnique(labels).sort(sorter);
13942
+ min = Math.min(min, labels[0]);
13943
+ max = Math.max(max, labels[labels.length - 1]);
13944
+ }
13945
+
13946
+ if (timestamps.length) {
13947
+ timestamps = arrayUnique(timestamps).sort(sorter);
13948
+ min = Math.min(min, timestamps[0]);
13949
+ max = Math.max(max, timestamps[timestamps.length - 1]);
13950
+ }
13951
+
13952
+ min = parse(timeOpts.min, me) || min;
13953
+ max = parse(timeOpts.max, me) || max;
13954
+
13955
+ // In case there is no valid min/max, let's use today limits
13956
+ min = min === MAX_INTEGER ? +moment().startOf('day') : min;
13957
+ max = max === MIN_INTEGER ? +moment().endOf('day') + 1 : max;
13958
+
13959
+ // Make sure that max is strictly higher than min (required by the lookup table)
13960
+ me.min = Math.min(min, max);
13961
+ me.max = Math.max(min + 1, max);
13962
+
13963
+ // PRIVATE
13964
+ me._horizontal = me.isHorizontal();
13965
+ me._table = [];
13966
+ me._timestamps = {
13967
+ data: timestamps,
13968
+ datasets: datasets,
13969
+ labels: labels
13970
+ };
13971
+ },
13972
+
13973
+ buildTicks: function() {
13974
+ var me = this;
13975
+ var min = me.min;
13976
+ var max = me.max;
13977
+ var options = me.options;
13978
+ var timeOpts = options.time;
13979
+ var timestamps = [];
13980
+ var ticks = [];
13981
+ var i, ilen, timestamp;
13982
+
13983
+ switch (options.ticks.source) {
13984
+ case 'data':
13985
+ timestamps = me._timestamps.data;
13986
+ break;
13987
+ case 'labels':
13988
+ timestamps = me._timestamps.labels;
13989
+ break;
13990
+ case 'auto':
13991
+ default:
13992
+ timestamps = generate(min, max, me.getLabelCapacity(min), options);
13993
+ }
13994
+
13995
+ if (options.bounds === 'ticks' && timestamps.length) {
13996
+ min = timestamps[0];
13997
+ max = timestamps[timestamps.length - 1];
13998
+ }
13999
+
14000
+ // Enforce limits with user min/max options
14001
+ min = parse(timeOpts.min, me) || min;
14002
+ max = parse(timeOpts.max, me) || max;
14003
+
14004
+ // Remove ticks outside the min/max range
14005
+ for (i = 0, ilen = timestamps.length; i < ilen; ++i) {
14006
+ timestamp = timestamps[i];
14007
+ if (timestamp >= min && timestamp <= max) {
14008
+ ticks.push(timestamp);
14009
+ }
14010
+ }
14011
+
14012
+ me.min = min;
14013
+ me.max = max;
14014
+
14015
+ // PRIVATE
14016
+ me._unit = timeOpts.unit || determineUnitForFormatting(ticks, timeOpts.minUnit, me.min, me.max);
14017
+ me._majorUnit = determineMajorUnit(me._unit);
14018
+ me._table = buildLookupTable(me._timestamps.data, min, max, options.distribution);
14019
+ me._offsets = computeOffsets(me._table, ticks, min, max, options);
14020
+
14021
+ return ticksFromTimestamps(ticks, me._majorUnit);
14022
+ },
14023
+
14024
+ getLabelForIndex: function(index, datasetIndex) {
14025
+ var me = this;
14026
+ var data = me.chart.data;
14027
+ var timeOpts = me.options.time;
14028
+ var label = data.labels && index < data.labels.length ? data.labels[index] : '';
14029
+ var value = data.datasets[datasetIndex].data[index];
14030
+
14031
+ if (helpers.isObject(value)) {
14032
+ label = me.getRightValue(value);
14033
+ }
14034
+ if (timeOpts.tooltipFormat) {
14035
+ label = momentify(label, timeOpts).format(timeOpts.tooltipFormat);
14036
+ }
14037
+
14038
+ return label;
14039
+ },
14040
+
14041
+ /**
14042
+ * Function to format an individual tick mark
14043
+ * @private
14044
+ */
14045
+ tickFormatFunction: function(tick, index, ticks, formatOverride) {
14046
+ var me = this;
14047
+ var options = me.options;
14048
+ var time = tick.valueOf();
14049
+ var formats = options.time.displayFormats;
14050
+ var minorFormat = formats[me._unit];
14051
+ var majorUnit = me._majorUnit;
14052
+ var majorFormat = formats[majorUnit];
14053
+ var majorTime = tick.clone().startOf(majorUnit).valueOf();
14054
+ var majorTickOpts = options.ticks.major;
14055
+ var major = majorTickOpts.enabled && majorUnit && majorFormat && time === majorTime;
14056
+ var label = tick.format(formatOverride ? formatOverride : major ? majorFormat : minorFormat);
14057
+ var tickOpts = major ? majorTickOpts : options.ticks.minor;
14058
+ var formatter = helpers.valueOrDefault(tickOpts.callback, tickOpts.userCallback);
14059
+
14060
+ return formatter ? formatter(label, index, ticks) : label;
14061
+ },
14062
+
14063
+ convertTicksToLabels: function(ticks) {
14064
+ var labels = [];
14065
+ var i, ilen;
14066
+
14067
+ for (i = 0, ilen = ticks.length; i < ilen; ++i) {
14068
+ labels.push(this.tickFormatFunction(moment(ticks[i].value), i, ticks));
14069
+ }
14070
+
14071
+ return labels;
14072
+ },
14073
+
14074
+ /**
14075
+ * @private
14076
+ */
14077
+ getPixelForOffset: function(time) {
14078
+ var me = this;
14079
+ var size = me._horizontal ? me.width : me.height;
14080
+ var start = me._horizontal ? me.left : me.top;
14081
+ var pos = interpolate(me._table, 'time', time, 'pos');
14082
+
14083
+ return start + size * (me._offsets.left + pos) / (me._offsets.left + 1 + me._offsets.right);
14084
+ },
14085
+
14086
+ getPixelForValue: function(value, index, datasetIndex) {
14087
+ var me = this;
14088
+ var time = null;
14089
+
14090
+ if (index !== undefined && datasetIndex !== undefined) {
14091
+ time = me._timestamps.datasets[datasetIndex][index];
14092
+ }
14093
+
14094
+ if (time === null) {
14095
+ time = parse(value, me);
14096
+ }
14097
+
14098
+ if (time !== null) {
14099
+ return me.getPixelForOffset(time);
14100
+ }
14101
+ },
14102
+
14103
+ getPixelForTick: function(index) {
14104
+ var ticks = this.getTicks();
14105
+ return index >= 0 && index < ticks.length ?
14106
+ this.getPixelForOffset(ticks[index].value) :
14107
+ null;
14108
+ },
14109
+
14110
+ getValueForPixel: function(pixel) {
14111
+ var me = this;
14112
+ var size = me._horizontal ? me.width : me.height;
14113
+ var start = me._horizontal ? me.left : me.top;
14114
+ var pos = (size ? (pixel - start) / size : 0) * (me._offsets.left + 1 + me._offsets.left) - me._offsets.right;
14115
+ var time = interpolate(me._table, 'pos', pos, 'time');
14116
+
14117
+ return moment(time);
14118
+ },
14119
+
14120
+ /**
14121
+ * Crude approximation of what the label width might be
14122
+ * @private
14123
+ */
14124
+ getLabelWidth: function(label) {
14125
+ var me = this;
14126
+ var ticksOpts = me.options.ticks;
14127
+ var tickLabelWidth = me.ctx.measureText(label).width;
14128
+ var angle = helpers.toRadians(ticksOpts.maxRotation);
14129
+ var cosRotation = Math.cos(angle);
14130
+ var sinRotation = Math.sin(angle);
14131
+ var tickFontSize = helpers.valueOrDefault(ticksOpts.fontSize, defaults.global.defaultFontSize);
14132
+
14133
+ return (tickLabelWidth * cosRotation) + (tickFontSize * sinRotation);
14134
+ },
14135
+