Simple History - Version 2.6

Version Description

(May 2016) =

  • Added: A nice little graph in the sidebar that displays the number of logged events per day the last 28 days. Graph is powered by Graph.js.
  • Added: Function get_num_events_last_n_days()
  • Added: Function get_num_events_per_day_last_n_days()
  • Changed: Switched to transients from cache at some places, because more people will benefit from transients instead of cache (that requires object cache to be installed).
  • Changed: New constant SETTINGS_GENERAL_OPTION_GROUP. Fixes https://wordpress.org/support/topic/constant-for-settings-option-group-name-option_group.
  • Fixed: Long log messages with no spaces would get cut of. Now all the message is shown, but with one or several line breaks. Fixes https://github.com/bonny/WordPress-Simple-History/pull/112.
  • Fixed: Some small CSS modification to make the page less "jumpy" while loading (for example setting a default height to the select2 input box).
Download this release

Release Info

Developer eskapism
Plugin Icon 128x128 Simple History
Version 2.6
Comparing to
See all releases

Code changes from version 2.5.5 to 2.6

css/styles.css CHANGED
@@ -277,6 +277,8 @@ Style different log levels.
277
  margin-top: 0.4em;
278
  font-size: 15px;
279
  color: #333;
 
 
280
  }
281
 
282
  .SimpleHistoryLogitem__details p {
277
  margin-top: 0.4em;
278
  font-size: 15px;
279
  color: #333;
280
+ /* break long lines so content always fit, even if very long (like very long url from http_api-logger */
281
+ word-break: break-word;
282
  }
283
 
284
  .SimpleHistoryLogitem__details p {
dropins/SimpleHistoryFilterDropin.css CHANGED
@@ -30,6 +30,12 @@
30
  width: 310px;
31
  }
32
 
 
 
 
 
 
 
33
  /**
34
  * Search results in filter
35
  */
30
  width: 310px;
31
  }
32
 
33
+ /* set height on date input or it will "jump" during page load */
34
+ .wp-admin select[multiple].SimpleHistory__filters__filter--date {
35
+ height: 2.2em;
36
+ overflow: hidden;
37
+ }
38
+
39
  /**
40
  * Search results in filter
41
  */
dropins/SimpleHistoryFilterDropin.php CHANGED
@@ -34,35 +34,6 @@ class SimpleHistoryFilterDropin {
34
 
35
  }
36
 
37
- public function get_unique_events_for_days($days = 7) {
38
-
39
- global $wpdb;
40
-
41
- $days = (int) $days;
42
-
43
- $table_name = $wpdb->prefix . SimpleHistory::DBTABLE;
44
-
45
- // Number of unique events the last n days
46
- $cache_key = "SimpleHistory_FilterDropin_unique_events_for_days" . $days;
47
- $numEvents = wp_cache_get($cache_key);
48
-
49
- if ( false == $numEvents ) {
50
-
51
- $sql = $wpdb->prepare("
52
- SELECT count( DISTINCT occasionsID )
53
- FROM $table_name
54
- WHERE date >= DATE_ADD(CURDATE(), INTERVAL -%d DAY)
55
- ", $days);
56
-
57
- $numEvents = $wpdb->get_var($sql);
58
-
59
- wp_cache_set( $cache_key, $numEvents, "", DAY_IN_SECONDS);
60
-
61
- }
62
-
63
- return $numEvents;
64
-
65
- }
66
 
67
  public function gui_page_filters() {
68
 
@@ -96,8 +67,8 @@ class SimpleHistoryFilterDropin {
96
  $loggers_user_can_read_sql_in = $this->sh->getLoggersThatUserCanRead(null, "sql");
97
 
98
  // Get unique months
99
- $cache_key = "SimpleHistory_FilterDropin_Months";
100
- $result_months = wp_cache_get($cache_key);
101
 
102
  if ( false === $result_months ) {
103
 
@@ -112,7 +83,7 @@ class SimpleHistoryFilterDropin {
112
 
113
  $result_months = $wpdb->get_results($sql_dates);
114
 
115
- wp_cache_set($cache_key, $result_months, "", HOUR_IN_SECONDS);
116
 
117
  }
118
 
@@ -128,7 +99,7 @@ class SimpleHistoryFilterDropin {
128
  $daysToShow = 1;
129
 
130
  // Start with the latest day
131
- $numEvents = $this->get_unique_events_for_days($daysToShow);
132
  $numPages = $numEvents / $this->sh->get_pager_size();
133
 
134
  $arr_days_and_pages[] = array(
@@ -144,7 +115,7 @@ class SimpleHistoryFilterDropin {
144
 
145
  // Not that many things the last day. Let's try to expand to 7 days instead.
146
  $daysToShow = 7;
147
- $numEvents = $this->get_unique_events_for_days($daysToShow);
148
  $numPages = $numEvents / $this->sh->get_pager_size();
149
 
150
  $arr_days_and_pages[] = array(
@@ -156,7 +127,7 @@ class SimpleHistoryFilterDropin {
156
 
157
  // Not that many things the last 7 days. Let's try to expand to 14 days instead.
158
  $daysToShow = 14;
159
- $numEvents = $this->get_unique_events_for_days($daysToShow);
160
  $numPages = $numEvents / $this->sh->get_pager_size();
161
 
162
  $arr_days_and_pages[] = array(
@@ -168,7 +139,7 @@ class SimpleHistoryFilterDropin {
168
 
169
  // Not many things the last 14 days either. Let try with 30 days.
170
  $daysToShow = 30;
171
- $numEvents = $this->get_unique_events_for_days($daysToShow);
172
  $numPages = $numEvents / $this->sh->get_pager_size();
173
 
174
  $arr_days_and_pages[] = array(
34
 
35
  }
36
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
37
 
38
  public function gui_page_filters() {
39
 
67
  $loggers_user_can_read_sql_in = $this->sh->getLoggersThatUserCanRead(null, "sql");
68
 
69
  // Get unique months
70
+ $cache_key = "sh_filter_unique_months";
71
+ $result_months = get_transient( $cache_key );
72
 
73
  if ( false === $result_months ) {
74
 
83
 
84
  $result_months = $wpdb->get_results($sql_dates);
85
 
86
+ set_transient( $cache_key, $result_months, HOUR_IN_SECONDS );
87
 
88
  }
89
 
99
  $daysToShow = 1;
100
 
101
  // Start with the latest day
102
+ $numEvents = $this->sh->get_unique_events_for_days($daysToShow);
103
  $numPages = $numEvents / $this->sh->get_pager_size();
104
 
105
  $arr_days_and_pages[] = array(
115
 
116
  // Not that many things the last day. Let's try to expand to 7 days instead.
117
  $daysToShow = 7;
118
+ $numEvents = $this->sh->get_unique_events_for_days($daysToShow);
119
  $numPages = $numEvents / $this->sh->get_pager_size();
120
 
121
  $arr_days_and_pages[] = array(
127
 
128
  // Not that many things the last 7 days. Let's try to expand to 14 days instead.
129
  $daysToShow = 14;
130
+ $numEvents = $this->sh->get_unique_events_for_days($daysToShow);
131
  $numPages = $numEvents / $this->sh->get_pager_size();
132
 
133
  $arr_days_and_pages[] = array(
139
 
140
  // Not many things the last 14 days either. Let try with 30 days.
141
  $daysToShow = 30;
142
+ $numEvents = $this->sh->get_unique_events_for_days($daysToShow);
143
  $numPages = $numEvents / $this->sh->get_pager_size();
144
 
145
  $arr_days_and_pages[] = array(
dropins/SimpleHistoryRSSDropin.php CHANGED
@@ -43,7 +43,7 @@ class SimpleHistoryRSSDropin {
43
  public function add_settings() {
44
 
45
  //we register a setting to keep track of the RSS feed status (enabled/disabled)
46
- register_setting( 'simple_history_settings_group', 'simple_history_enable_rss_feed', array($this, 'update_rss_status') );
47
 
48
  /**
49
  * Start new section for RSS feed
43
  public function add_settings() {
44
 
45
  //we register a setting to keep track of the RSS feed status (enabled/disabled)
46
+ register_setting( SimpleHistory::SETTINGS_GENERAL_OPTION_GROUP, 'simple_history_enable_rss_feed', array($this, 'update_rss_status') );
47
 
48
  /**
49
  * Start new section for RSS feed
dropins/SimpleHistorySidebarStats.php ADDED
@@ -0,0 +1,178 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ defined( 'ABSPATH' ) or die();
4
+
5
+ /*
6
+ Dropin Name: Sidebar with short stats
7
+ Dropin URI: http://simple-history.com/
8
+ Author: Pär Thernström
9
+ */
10
+
11
+ class SimpleHistorySidebarStats {
12
+
13
+ private $sh;
14
+
15
+ function __construct( $sh ) {
16
+
17
+ $this->init( $sh );
18
+
19
+ }
20
+
21
+ function init( $sh ) {
22
+
23
+ $this->sh = $sh;
24
+
25
+ add_action("simple_history/dropin/sidebar/sidebar_html", array( $this, "on_sidebar_html" ), 5 );
26
+
27
+ add_action( 'simple_history/enqueue_admin_scripts', array( $this, 'on_admin_enqueue_scripts') );
28
+
29
+ add_action( "simple_history/admin_footer", array($this, "on_admin_footer") );
30
+
31
+ }
32
+
33
+ public function on_admin_enqueue_scripts() {
34
+
35
+ wp_enqueue_script( "chart.js", SIMPLE_HISTORY_DIR_URL . "js/Chart.js", array( "jquery" ), SIMPLE_HISTORY_VERSION, true );
36
+
37
+ }
38
+
39
+ function on_admin_footer() {
40
+
41
+ ?>
42
+ <script>
43
+ /**
44
+ * JavaScript for SimpleHistory_SidebarChart
45
+ */
46
+ (function($) {
47
+
48
+ $(function() {
49
+
50
+ var ctx = $(".SimpleHistory_SidebarChart_ChartCanvas");
51
+
52
+ if ( ! ctx.length ) {
53
+ return;
54
+ }
55
+
56
+ var chartLabels = JSON.parse( $(".SimpleHistory_SidebarChart_ChartLabels").val() );
57
+ var chartDatasetData = JSON.parse( $(".SimpleHistory_SidebarChart_ChartDatasetData").val() );
58
+
59
+ var myChart = new Chart(ctx, {
60
+ type: 'bar',
61
+ data: {
62
+ labels: chartLabels,
63
+ datasets: [{
64
+ data: chartDatasetData,
65
+ backgroundColor: "rgb(210,210,210)",
66
+ hoverBackgroundColor: "rgb(175,175,175)",
67
+ }]
68
+ },
69
+ options: {
70
+ legend: {
71
+ display: false
72
+ },
73
+ scales: {
74
+ yAxes: [{
75
+ ticks: {
76
+ beginAtZero:true
77
+ },
78
+ }],
79
+ xAxes: [{
80
+ display: false
81
+ }]
82
+ }
83
+ }
84
+ });
85
+
86
+ });
87
+
88
+ })(jQuery);
89
+
90
+
91
+ </script>
92
+
93
+ <?php
94
+
95
+ }
96
+
97
+ function on_sidebar_html() {
98
+
99
+ $num_days = 28;
100
+
101
+ $num_events_per_day_for_period = $this->sh->get_num_events_per_day_last_n_days( $num_days );
102
+
103
+ // Period = all dates, so empty ones don't get lost
104
+ $period_start_date = DateTime::createFromFormat('U', strtotime("-$num_days days"));
105
+ $period_end_date = DateTime::createFromFormat('U', time());
106
+ $interval = DateInterval::createFromDateString('1 day');
107
+ $period = new DatePeriod($period_start_date, $interval, $period_end_date->add( date_interval_create_from_date_string('1 days') ) );
108
+
109
+ ?>
110
+
111
+ <div class="postbox">
112
+
113
+ <h3 class="hndle"><?php _e("Stats", "simple-history") ?></h3>
114
+
115
+ <div class="inside">
116
+
117
+ <p>
118
+ <?php
119
+
120
+ printf(
121
+ __('<b>%1$s events</b> have been logged the last <b>%2$s days</b>.', "simple-history"),
122
+ $this->sh->get_num_events_last_n_days( $num_days ),
123
+ number_format_i18n( $num_days )
124
+ );
125
+
126
+ ?>
127
+ </p>
128
+
129
+ <!-- wrapper div so sidebar does not "jump" when loading. so annoying. -->
130
+ <div style="position: relative; height: 0; overflow: hidden; padding-bottom: 40%;">
131
+ <canvas style="position: absolute; left: 0; right: 0;" class="SimpleHistory_SidebarChart_ChartCanvas" width="100" height="40"></canvas>
132
+ </div>
133
+
134
+ <p class="SimpleHistory_SidebarChart_ChartDescription" style="font-style: italic; color: #777; text-align: center;">
135
+ <?php _e("Number of events per day.", "simple-history") ?>
136
+ </p>
137
+
138
+ <?php
139
+
140
+ $arr_labels = array();
141
+ $arr_dataset_data = array();
142
+
143
+ foreach ( $period as $dt ) {
144
+
145
+ $datef = _x( 'M j', "stats: date in rows per day chart", "simple-history" );
146
+ $str_date = date_i18n( $datef, $dt->getTimestamp() );
147
+
148
+ // Get data for this day, if exist
149
+ // Day in object is in format '2014-09-07'
150
+ $yearDate = $dt->format( "Y-m-d" );
151
+ $day_data = wp_filter_object_list( $num_events_per_day_for_period, array("yearDate" => $yearDate) );
152
+
153
+ $arr_labels[] = $str_date;
154
+
155
+ if ( $day_data ) {
156
+
157
+ $day_data = reset( $day_data );
158
+ $arr_dataset_data[] = $day_data->count;
159
+
160
+ } else {
161
+ $arr_dataset_data[] = 0;
162
+ }
163
+
164
+ }
165
+
166
+ ?>
167
+
168
+ <input type="hidden" class="SimpleHistory_SidebarChart_ChartLabels" value="<?php esc_attr_e( json_encode( $arr_labels ) ) ?>">
169
+ <input type="hidden" class="SimpleHistory_SidebarChart_ChartDatasetData" value="<?php esc_attr_e( json_encode( $arr_dataset_data ) ) ?>">
170
+
171
+ </div>
172
+ </div>
173
+
174
+ <?php
175
+
176
+ }
177
+
178
+ }
inc/SimpleHistory.php CHANGED
@@ -63,6 +63,9 @@ class SimpleHistory {
63
  /** Slug for the settings menu */
64
  const SETTINGS_MENU_SLUG = "simple_history_settings_menu_slug";
65
 
 
 
 
66
  /** ID for the general settings section */
67
  const SETTINGS_SECTION_GENERAL_ID = "simple_history_settings_section_general";
68
 
@@ -884,6 +887,7 @@ class SimpleHistory {
884
  $dropinsDir . "SimpleHistorySettingsLogtestDropin.php",
885
  $dropinsDir . "SimpleHistorySettingsStatsDropin.php",
886
  $dropinsDir . "SimpleHistorySidebarDropin.php",
 
887
  );
888
 
889
  /**
@@ -969,6 +973,7 @@ class SimpleHistory {
969
  "name" => $oneDropinName,
970
  "instance" => new $oneDropinName( $this ),
971
  );
 
972
  }
973
 
974
  }
@@ -1662,8 +1667,8 @@ Because Simple History was just recently installed, this feed does not contain m
1662
  );
1663
 
1664
  // Nonces for show where inputs
1665
- register_setting( "simple_history_settings_group", "simple_history_show_on_dashboard" );
1666
- register_setting( "simple_history_settings_group", "simple_history_show_as_page" );
1667
 
1668
  // Dropdown number if items to show
1669
  add_settings_field(
@@ -1675,7 +1680,7 @@ Because Simple History was just recently installed, this feed does not contain m
1675
  );
1676
 
1677
  // Nonces for number of items inputs
1678
- register_setting( "simple_history_settings_group", "simple_history_pager_size" );
1679
 
1680
  // Link to clear log
1681
  add_settings_field(
@@ -2560,7 +2565,7 @@ Because Simple History was just recently installed, this feed does not contain m
2560
  * with all loggers they are allowed to read
2561
  *
2562
  * @param int $user_id Id of user to get loggers for. Defaults to current user id.
2563
- * @param string $format format to return loggers in. Default is array.
2564
  * @return array
2565
  */
2566
  public function getLoggersThatUserCanRead( $user_id = "", $format = "array" ) {
@@ -2969,6 +2974,118 @@ Because Simple History was just recently installed, this feed does not contain m
2969
 
2970
  }
2971
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2972
  } // class
2973
 
2974
 
63
  /** Slug for the settings menu */
64
  const SETTINGS_MENU_SLUG = "simple_history_settings_menu_slug";
65
 
66
+ /** Slug for the settings menu */
67
+ const SETTINGS_GENERAL_OPTION_GROUP = "simple_history_settings_group";
68
+
69
  /** ID for the general settings section */
70
  const SETTINGS_SECTION_GENERAL_ID = "simple_history_settings_section_general";
71
 
887
  $dropinsDir . "SimpleHistorySettingsLogtestDropin.php",
888
  $dropinsDir . "SimpleHistorySettingsStatsDropin.php",
889
  $dropinsDir . "SimpleHistorySidebarDropin.php",
890
+ $dropinsDir . "SimpleHistorySidebarStats.php",
891
  );
892
 
893
  /**
973
  "name" => $oneDropinName,
974
  "instance" => new $oneDropinName( $this ),
975
  );
976
+
977
  }
978
 
979
  }
1667
  );
1668
 
1669
  // Nonces for show where inputs
1670
+ register_setting( SimpleHistory::SETTINGS_GENERAL_OPTION_GROUP, "simple_history_show_on_dashboard" );
1671
+ register_setting( SimpleHistory::SETTINGS_GENERAL_OPTION_GROUP, "simple_history_show_as_page" );
1672
 
1673
  // Dropdown number if items to show
1674
  add_settings_field(
1680
  );
1681
 
1682
  // Nonces for number of items inputs
1683
+ register_setting( SimpleHistory::SETTINGS_GENERAL_OPTION_GROUP, "simple_history_pager_size" );
1684
 
1685
  // Link to clear log
1686
  add_settings_field(
2565
  * with all loggers they are allowed to read
2566
  *
2567
  * @param int $user_id Id of user to get loggers for. Defaults to current user id.
2568
+ * @param string $format format to return loggers in. Default is array. Can also be "sql"
2569
  * @return array
2570
  */
2571
  public function getLoggersThatUserCanRead( $user_id = "", $format = "array" ) {
2974
 
2975
  }
2976
 
2977
+
2978
+ // Number of rows the last n days
2979
+ function get_num_events_last_n_days( $period_days = 28 ) {
2980
+
2981
+ $transient_key = "sh_" . md5( __METHOD__ . $period_days . "_2");
2982
+
2983
+ $count = get_transient( $transient_key );
2984
+
2985
+
2986
+ if ( false === $count ) {
2987
+
2988
+ global $wpdb;
2989
+
2990
+ $sqlStringLoggersUserCanRead = $this->getLoggersThatUserCanRead( null, "sql" );
2991
+
2992
+ $sql = sprintf(
2993
+ '
2994
+ SELECT count(*)
2995
+ FROM %1$s
2996
+ WHERE UNIX_TIMESTAMP(date) >= %2$d
2997
+ AND logger IN %3$s
2998
+ ',
2999
+ $wpdb->prefix . SimpleHistory::DBTABLE,
3000
+ strtotime("-$period_days days"),
3001
+ $sqlStringLoggersUserCanRead
3002
+ );
3003
+
3004
+ $count = $wpdb->get_var( $sql );
3005
+
3006
+ set_transient( $transient_key, $count, HOUR_IN_SECONDS );
3007
+
3008
+ }
3009
+
3010
+ return $count;
3011
+
3012
+ } // get_num_events_last_n_days
3013
+
3014
+
3015
+ function get_num_events_per_day_last_n_days( $period_days = 28 ) {
3016
+
3017
+ $transient_key = "sh_" . md5( __METHOD__ . $period_days . "_2");
3018
+
3019
+ $dates = get_transient( $transient_key );
3020
+
3021
+ if ( false === $dates ) {
3022
+
3023
+ global $wpdb;
3024
+
3025
+ $sqlStringLoggersUserCanRead = $this->getLoggersThatUserCanRead( null, "sql" );
3026
+
3027
+ $sql = sprintf(
3028
+ '
3029
+ SELECT
3030
+ date_format(date, "%%Y-%%m-%%d") AS yearDate,
3031
+ count(date) AS count
3032
+ FROM
3033
+ %1$s
3034
+ WHERE
3035
+ UNIX_TIMESTAMP(date) >= %2$d
3036
+ AND logger IN (%3$d)
3037
+ GROUP BY yearDate
3038
+ ORDER BY yearDate ASC
3039
+ ',
3040
+ $wpdb->prefix . SimpleHistory::DBTABLE,
3041
+ strtotime("-$period_days days"),
3042
+ $sqlStringLoggersUserCanRead
3043
+ );
3044
+
3045
+ $dates = $wpdb->get_results( $sql );
3046
+
3047
+ set_transient( $transient_key, $dates, HOUR_IN_SECONDS );
3048
+ // echo "set";exit;
3049
+
3050
+ } else {
3051
+ // echo "get";exit;
3052
+ }
3053
+
3054
+ return $dates;
3055
+
3056
+ } // get_num_events_per_day_for_period
3057
+
3058
+ // Number of unique events the last n days
3059
+ public function get_unique_events_for_days( $days = 7 ) {
3060
+
3061
+ global $wpdb;
3062
+
3063
+ $days = (int) $days;
3064
+
3065
+ $table_name = $wpdb->prefix . SimpleHistory::DBTABLE;
3066
+
3067
+ $cache_key = "sh_" .md5( __METHOD__ . $days );
3068
+
3069
+ $numEvents = get_transient( $cache_key );
3070
+
3071
+ if ( false == $numEvents ) {
3072
+
3073
+ $sql = $wpdb->prepare("
3074
+ SELECT count( DISTINCT occasionsID )
3075
+ FROM $table_name
3076
+ WHERE date >= DATE_ADD(CURDATE(), INTERVAL -%d DAY)
3077
+ ", $days);
3078
+
3079
+ $numEvents = $wpdb->get_var($sql);
3080
+
3081
+ set_transient( $cache_key, $numEvents, HOUR_IN_SECONDS );
3082
+
3083
+ }
3084
+
3085
+ return $numEvents;
3086
+
3087
+ } // get_unique_events_for_days
3088
+
3089
  } // class
3090
 
3091
 
index.php CHANGED
@@ -5,7 +5,7 @@ Plugin URI: http://simple-history.com
5
  Text Domain: simple-history
6
  Domain Path: /languages
7
  Description: Plugin that logs various things that occur in WordPress and then presents those events in a very nice GUI.
8
- Version: 2.5.5
9
  Author: Pär Thernström
10
  Author URI: http://simple-history.com/
11
  License: GPL2
@@ -42,7 +42,7 @@ if ( version_compare( phpversion(), "5.3", ">=") ) {
42
  // register_activation_hook( trailingslashit(WP_PLUGIN_DIR) . trailingslashit( plugin_basename(__DIR__) ) . "index.php" , array("SimpleHistory", "on_plugin_activate" ) );
43
 
44
  if ( ! defined( 'SIMPLE_HISTORY_VERSION' ) ) {
45
- define( 'SIMPLE_HISTORY_VERSION', '2.5.5' );
46
  }
47
 
48
  if ( ! defined( 'SIMPLE_HISTORY_PATH' ) ) {
5
  Text Domain: simple-history
6
  Domain Path: /languages
7
  Description: Plugin that logs various things that occur in WordPress and then presents those events in a very nice GUI.
8
+ Version: 2.6
9
  Author: Pär Thernström
10
  Author URI: http://simple-history.com/
11
  License: GPL2
42
  // register_activation_hook( trailingslashit(WP_PLUGIN_DIR) . trailingslashit( plugin_basename(__DIR__) ) . "index.php" , array("SimpleHistory", "on_plugin_activate" ) );
43
 
44
  if ( ! defined( 'SIMPLE_HISTORY_VERSION' ) ) {
45
+ define( 'SIMPLE_HISTORY_VERSION', '2.6' );
46
  }
47
 
48
  if ( ! defined( 'SIMPLE_HISTORY_PATH' ) ) {
js/Chart.js ADDED
@@ -0,0 +1,9214 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ (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){
2
+
3
+ },{}],2:[function(require,module,exports){
4
+ /* MIT license */
5
+
6
+ module.exports = {
7
+ rgb2hsl: rgb2hsl,
8
+ rgb2hsv: rgb2hsv,
9
+ rgb2hwb: rgb2hwb,
10
+ rgb2cmyk: rgb2cmyk,
11
+ rgb2keyword: rgb2keyword,
12
+ rgb2xyz: rgb2xyz,
13
+ rgb2lab: rgb2lab,
14
+ rgb2lch: rgb2lch,
15
+
16
+ hsl2rgb: hsl2rgb,
17
+ hsl2hsv: hsl2hsv,
18
+ hsl2hwb: hsl2hwb,
19
+ hsl2cmyk: hsl2cmyk,
20
+ hsl2keyword: hsl2keyword,
21
+
22
+ hsv2rgb: hsv2rgb,
23
+ hsv2hsl: hsv2hsl,
24
+ hsv2hwb: hsv2hwb,
25
+ hsv2cmyk: hsv2cmyk,
26
+ hsv2keyword: hsv2keyword,
27
+
28
+ hwb2rgb: hwb2rgb,
29
+ hwb2hsl: hwb2hsl,
30
+ hwb2hsv: hwb2hsv,
31
+ hwb2cmyk: hwb2cmyk,
32
+ hwb2keyword: hwb2keyword,
33
+
34
+ cmyk2rgb: cmyk2rgb,
35
+ cmyk2hsl: cmyk2hsl,
36
+ cmyk2hsv: cmyk2hsv,
37
+ cmyk2hwb: cmyk2hwb,
38
+ cmyk2keyword: cmyk2keyword,
39
+
40
+ keyword2rgb: keyword2rgb,
41
+ keyword2hsl: keyword2hsl,
42
+ keyword2hsv: keyword2hsv,
43
+ keyword2hwb: keyword2hwb,
44
+ keyword2cmyk: keyword2cmyk,
45
+ keyword2lab: keyword2lab,
46
+ keyword2xyz: keyword2xyz,
47
+
48
+ xyz2rgb: xyz2rgb,
49
+ xyz2lab: xyz2lab,
50
+ xyz2lch: xyz2lch,
51
+
52
+ lab2xyz: lab2xyz,
53
+ lab2rgb: lab2rgb,
54
+ lab2lch: lab2lch,
55
+
56
+ lch2lab: lch2lab,
57
+ lch2xyz: lch2xyz,
58
+ lch2rgb: lch2rgb
59
+ }
60
+
61
+
62
+ function rgb2hsl(rgb) {
63
+ var r = rgb[0]/255,
64
+ g = rgb[1]/255,
65
+ b = rgb[2]/255,
66
+ min = Math.min(r, g, b),
67
+ max = Math.max(r, g, b),
68
+ delta = max - min,
69
+ h, s, l;
70
+
71
+ if (max == min)
72
+ h = 0;
73
+ else if (r == max)
74
+ h = (g - b) / delta;
75
+ else if (g == max)
76
+ h = 2 + (b - r) / delta;
77
+ else if (b == max)
78
+ h = 4 + (r - g)/ delta;
79
+
80
+ h = Math.min(h * 60, 360);
81
+
82
+ if (h < 0)
83
+ h += 360;
84
+
85
+ l = (min + max) / 2;
86
+
87
+ if (max == min)
88
+ s = 0;
89
+ else if (l <= 0.5)
90
+ s = delta / (max + min);
91
+ else
92
+ s = delta / (2 - max - min);
93
+
94
+ return [h, s * 100, l * 100];
95
+ }
96
+
97
+ function rgb2hsv(rgb) {
98
+ var r = rgb[0],
99
+ g = rgb[1],
100
+ b = rgb[2],
101
+ min = Math.min(r, g, b),
102
+ max = Math.max(r, g, b),
103
+ delta = max - min,
104
+ h, s, v;
105
+
106
+ if (max == 0)
107
+ s = 0;
108
+ else
109
+ s = (delta/max * 1000)/10;
110
+
111
+ if (max == min)
112
+ h = 0;
113
+ else if (r == max)
114
+ h = (g - b) / delta;
115
+ else if (g == max)
116
+ h = 2 + (b - r) / delta;
117
+ else if (b == max)
118
+ h = 4 + (r - g) / delta;
119
+
120
+ h = Math.min(h * 60, 360);
121
+
122
+ if (h < 0)
123
+ h += 360;
124
+
125
+ v = ((max / 255) * 1000) / 10;
126
+
127
+ return [h, s, v];
128
+ }
129
+
130
+ function rgb2hwb(rgb) {
131
+ var r = rgb[0],
132
+ g = rgb[1],
133
+ b = rgb[2],
134
+ h = rgb2hsl(rgb)[0],
135
+ w = 1/255 * Math.min(r, Math.min(g, b)),
136
+ b = 1 - 1/255 * Math.max(r, Math.max(g, b));
137
+
138
+ return [h, w * 100, b * 100];
139
+ }
140
+
141
+ function rgb2cmyk(rgb) {
142
+ var r = rgb[0] / 255,
143
+ g = rgb[1] / 255,
144
+ b = rgb[2] / 255,
145
+ c, m, y, k;
146
+
147
+ k = Math.min(1 - r, 1 - g, 1 - b);
148
+ c = (1 - r - k) / (1 - k) || 0;
149
+ m = (1 - g - k) / (1 - k) || 0;
150
+ y = (1 - b - k) / (1 - k) || 0;
151
+ return [c * 100, m * 100, y * 100, k * 100];
152
+ }
153
+
154
+ function rgb2keyword(rgb) {
155
+ return reverseKeywords[JSON.stringify(rgb)];
156
+ }
157
+
158
+ function rgb2xyz(rgb) {
159
+ var r = rgb[0] / 255,
160
+ g = rgb[1] / 255,
161
+ b = rgb[2] / 255;
162
+
163
+ // assume sRGB
164
+ r = r > 0.04045 ? Math.pow(((r + 0.055) / 1.055), 2.4) : (r / 12.92);
165
+ g = g > 0.04045 ? Math.pow(((g + 0.055) / 1.055), 2.4) : (g / 12.92);
166
+ b = b > 0.04045 ? Math.pow(((b + 0.055) / 1.055), 2.4) : (b / 12.92);
167
+
168
+ var x = (r * 0.4124) + (g * 0.3576) + (b * 0.1805);
169
+ var y = (r * 0.2126) + (g * 0.7152) + (b * 0.0722);
170
+ var z = (r * 0.0193) + (g * 0.1192) + (b * 0.9505);
171
+
172
+ return [x * 100, y *100, z * 100];
173
+ }
174
+
175
+ function rgb2lab(rgb) {
176
+ var xyz = rgb2xyz(rgb),
177
+ x = xyz[0],
178
+ y = xyz[1],
179
+ z = xyz[2],
180
+ l, a, b;
181
+
182
+ x /= 95.047;
183
+ y /= 100;
184
+ z /= 108.883;
185
+
186
+ x = x > 0.008856 ? Math.pow(x, 1/3) : (7.787 * x) + (16 / 116);
187
+ y = y > 0.008856 ? Math.pow(y, 1/3) : (7.787 * y) + (16 / 116);
188
+ z = z > 0.008856 ? Math.pow(z, 1/3) : (7.787 * z) + (16 / 116);
189
+
190
+ l = (116 * y) - 16;
191
+ a = 500 * (x - y);
192
+ b = 200 * (y - z);
193
+
194
+ return [l, a, b];
195
+ }
196
+
197
+ function rgb2lch(args) {
198
+ return lab2lch(rgb2lab(args));
199
+ }
200
+
201
+ function hsl2rgb(hsl) {
202
+ var h = hsl[0] / 360,
203
+ s = hsl[1] / 100,
204
+ l = hsl[2] / 100,
205
+ t1, t2, t3, rgb, val;
206
+
207
+ if (s == 0) {
208
+ val = l * 255;
209
+ return [val, val, val];
210
+ }
211
+
212
+ if (l < 0.5)
213
+ t2 = l * (1 + s);
214
+ else
215
+ t2 = l + s - l * s;
216
+ t1 = 2 * l - t2;
217
+
218
+ rgb = [0, 0, 0];
219
+ for (var i = 0; i < 3; i++) {
220
+ t3 = h + 1 / 3 * - (i - 1);
221
+ t3 < 0 && t3++;
222
+ t3 > 1 && t3--;
223
+
224
+ if (6 * t3 < 1)
225
+ val = t1 + (t2 - t1) * 6 * t3;
226
+ else if (2 * t3 < 1)
227
+ val = t2;
228
+ else if (3 * t3 < 2)
229
+ val = t1 + (t2 - t1) * (2 / 3 - t3) * 6;
230
+ else
231
+ val = t1;
232
+
233
+ rgb[i] = val * 255;
234
+ }
235
+
236
+ return rgb;
237
+ }
238
+
239
+ function hsl2hsv(hsl) {
240
+ var h = hsl[0],
241
+ s = hsl[1] / 100,
242
+ l = hsl[2] / 100,
243
+ sv, v;
244
+
245
+ if(l === 0) {
246
+ // no need to do calc on black
247
+ // also avoids divide by 0 error
248
+ return [0, 0, 0];
249
+ }
250
+
251
+ l *= 2;
252
+ s *= (l <= 1) ? l : 2 - l;
253
+ v = (l + s) / 2;
254
+ sv = (2 * s) / (l + s);
255
+ return [h, sv * 100, v * 100];
256
+ }
257
+
258
+ function hsl2hwb(args) {
259
+ return rgb2hwb(hsl2rgb(args));
260
+ }
261
+
262
+ function hsl2cmyk(args) {
263
+ return rgb2cmyk(hsl2rgb(args));
264
+ }
265
+
266
+ function hsl2keyword(args) {
267
+ return rgb2keyword(hsl2rgb(args));
268
+ }
269
+
270
+
271
+ function hsv2rgb(hsv) {
272
+ var h = hsv[0] / 60,
273
+ s = hsv[1] / 100,
274
+ v = hsv[2] / 100,
275
+ hi = Math.floor(h) % 6;
276
+
277
+ var f = h - Math.floor(h),
278
+ p = 255 * v * (1 - s),
279
+ q = 255 * v * (1 - (s * f)),
280
+ t = 255 * v * (1 - (s * (1 - f))),
281
+ v = 255 * v;
282
+
283
+ switch(hi) {
284
+ case 0:
285
+ return [v, t, p];
286
+ case 1:
287
+ return [q, v, p];
288
+ case 2:
289
+ return [p, v, t];
290
+ case 3:
291
+ return [p, q, v];
292
+ case 4:
293
+ return [t, p, v];
294
+ case 5:
295
+ return [v, p, q];
296
+ }
297
+ }
298
+
299
+ function hsv2hsl(hsv) {
300
+ var h = hsv[0],
301
+ s = hsv[1] / 100,
302
+ v = hsv[2] / 100,
303
+ sl, l;
304
+
305
+ l = (2 - s) * v;
306
+ sl = s * v;
307
+ sl /= (l <= 1) ? l : 2 - l;
308
+ sl = sl || 0;
309
+ l /= 2;
310
+ return [h, sl * 100, l * 100];
311
+ }
312
+
313
+ function hsv2hwb(args) {
314
+ return rgb2hwb(hsv2rgb(args))
315
+ }
316
+
317
+ function hsv2cmyk(args) {
318
+ return rgb2cmyk(hsv2rgb(args));
319
+ }
320
+
321
+ function hsv2keyword(args) {
322
+ return rgb2keyword(hsv2rgb(args));
323
+ }
324
+
325
+ // http://dev.w3.org/csswg/css-color/#hwb-to-rgb
326
+ function hwb2rgb(hwb) {
327
+ var h = hwb[0] / 360,
328
+ wh = hwb[1] / 100,
329
+ bl = hwb[2] / 100,
330
+ ratio = wh + bl,
331
+ i, v, f, n;
332
+
333
+ // wh + bl cant be > 1
334
+ if (ratio > 1) {
335
+ wh /= ratio;
336
+ bl /= ratio;
337
+ }
338
+
339
+ i = Math.floor(6 * h);
340
+ v = 1 - bl;
341
+ f = 6 * h - i;
342
+ if ((i & 0x01) != 0) {
343
+ f = 1 - f;
344
+ }
345
+ n = wh + f * (v - wh); // linear interpolation
346
+
347
+ switch (i) {
348
+ default:
349
+ case 6:
350
+ case 0: r = v; g = n; b = wh; break;
351
+ case 1: r = n; g = v; b = wh; break;
352
+ case 2: r = wh; g = v; b = n; break;
353
+ case 3: r = wh; g = n; b = v; break;
354
+ case 4: r = n; g = wh; b = v; break;
355
+ case 5: r = v; g = wh; b = n; break;
356
+ }
357
+
358
+ return [r * 255, g * 255, b * 255];
359
+ }
360
+
361
+ function hwb2hsl(args) {
362
+ return rgb2hsl(hwb2rgb(args));
363
+ }
364
+
365
+ function hwb2hsv(args) {
366
+ return rgb2hsv(hwb2rgb(args));
367
+ }
368
+
369
+ function hwb2cmyk(args) {
370
+ return rgb2cmyk(hwb2rgb(args));
371
+ }
372
+
373
+ function hwb2keyword(args) {
374
+ return rgb2keyword(hwb2rgb(args));
375
+ }
376
+
377
+ function cmyk2rgb(cmyk) {
378
+ var c = cmyk[0] / 100,
379
+ m = cmyk[1] / 100,
380
+ y = cmyk[2] / 100,
381
+ k = cmyk[3] / 100,
382
+ r, g, b;
383
+
384
+ r = 1 - Math.min(1, c * (1 - k) + k);
385
+ g = 1 - Math.min(1, m * (1 - k) + k);
386
+ b = 1 - Math.min(1, y * (1 - k) + k);
387
+ return [r * 255, g * 255, b * 255];
388
+ }
389
+
390
+ function cmyk2hsl(args) {
391
+ return rgb2hsl(cmyk2rgb(args));
392
+ }
393
+
394
+ function cmyk2hsv(args) {
395
+ return rgb2hsv(cmyk2rgb(args));
396
+ }
397
+
398
+ function cmyk2hwb(args) {
399
+ return rgb2hwb(cmyk2rgb(args));
400
+ }
401
+
402
+ function cmyk2keyword(args) {
403
+ return rgb2keyword(cmyk2rgb(args));
404
+ }
405
+
406
+
407
+ function xyz2rgb(xyz) {
408
+ var x = xyz[0] / 100,
409
+ y = xyz[1] / 100,
410
+ z = xyz[2] / 100,
411
+ r, g, b;
412
+
413
+ r = (x * 3.2406) + (y * -1.5372) + (z * -0.4986);
414
+ g = (x * -0.9689) + (y * 1.8758) + (z * 0.0415);
415
+ b = (x * 0.0557) + (y * -0.2040) + (z * 1.0570);
416
+
417
+ // assume sRGB
418
+ r = r > 0.0031308 ? ((1.055 * Math.pow(r, 1.0 / 2.4)) - 0.055)
419
+ : r = (r * 12.92);
420
+
421
+ g = g > 0.0031308 ? ((1.055 * Math.pow(g, 1.0 / 2.4)) - 0.055)
422
+ : g = (g * 12.92);
423
+
424
+ b = b > 0.0031308 ? ((1.055 * Math.pow(b, 1.0 / 2.4)) - 0.055)
425
+ : b = (b * 12.92);
426
+
427
+ r = Math.min(Math.max(0, r), 1);
428
+ g = Math.min(Math.max(0, g), 1);
429
+ b = Math.min(Math.max(0, b), 1);
430
+
431
+ return [r * 255, g * 255, b * 255];
432
+ }
433
+
434
+ function xyz2lab(xyz) {
435
+ var x = xyz[0],
436
+ y = xyz[1],
437
+ z = xyz[2],
438
+ l, a, b;
439
+
440
+ x /= 95.047;
441
+ y /= 100;
442
+ z /= 108.883;
443
+
444
+ x = x > 0.008856 ? Math.pow(x, 1/3) : (7.787 * x) + (16 / 116);
445
+ y = y > 0.008856 ? Math.pow(y, 1/3) : (7.787 * y) + (16 / 116);
446
+ z = z > 0.008856 ? Math.pow(z, 1/3) : (7.787 * z) + (16 / 116);
447
+
448
+ l = (116 * y) - 16;
449
+ a = 500 * (x - y);
450
+ b = 200 * (y - z);
451
+
452
+ return [l, a, b];
453
+ }
454
+
455
+ function xyz2lch(args) {
456
+ return lab2lch(xyz2lab(args));
457
+ }
458
+
459
+ function lab2xyz(lab) {
460
+ var l = lab[0],
461
+ a = lab[1],
462
+ b = lab[2],
463
+ x, y, z, y2;
464
+
465
+ if (l <= 8) {
466
+ y = (l * 100) / 903.3;
467
+ y2 = (7.787 * (y / 100)) + (16 / 116);
468
+ } else {
469
+ y = 100 * Math.pow((l + 16) / 116, 3);
470
+ y2 = Math.pow(y / 100, 1/3);
471
+ }
472
+
473
+ x = x / 95.047 <= 0.008856 ? x = (95.047 * ((a / 500) + y2 - (16 / 116))) / 7.787 : 95.047 * Math.pow((a / 500) + y2, 3);
474
+
475
+ z = z / 108.883 <= 0.008859 ? z = (108.883 * (y2 - (b / 200) - (16 / 116))) / 7.787 : 108.883 * Math.pow(y2 - (b / 200), 3);
476
+
477
+ return [x, y, z];
478
+ }
479
+
480
+ function lab2lch(lab) {
481
+ var l = lab[0],
482
+ a = lab[1],
483
+ b = lab[2],
484
+ hr, h, c;
485
+
486
+ hr = Math.atan2(b, a);
487
+ h = hr * 360 / 2 / Math.PI;
488
+ if (h < 0) {
489
+ h += 360;
490
+ }
491
+ c = Math.sqrt(a * a + b * b);
492
+ return [l, c, h];
493
+ }
494
+
495
+ function lab2rgb(args) {
496
+ return xyz2rgb(lab2xyz(args));
497
+ }
498
+
499
+ function lch2lab(lch) {
500
+ var l = lch[0],
501
+ c = lch[1],
502
+ h = lch[2],
503
+ a, b, hr;
504
+
505
+ hr = h / 360 * 2 * Math.PI;
506
+ a = c * Math.cos(hr);
507
+ b = c * Math.sin(hr);
508
+ return [l, a, b];
509
+ }
510
+
511
+ function lch2xyz(args) {
512
+ return lab2xyz(lch2lab(args));
513
+ }
514
+
515
+ function lch2rgb(args) {
516
+ return lab2rgb(lch2lab(args));
517
+ }
518
+
519
+ function keyword2rgb(keyword) {
520
+ return cssKeywords[keyword];
521
+ }
522
+
523
+ function keyword2hsl(args) {
524
+ return rgb2hsl(keyword2rgb(args));
525
+ }
526
+
527
+ function keyword2hsv(args) {
528
+ return rgb2hsv(keyword2rgb(args));
529
+ }
530
+
531
+ function keyword2hwb(args) {
532
+ return rgb2hwb(keyword2rgb(args));
533
+ }
534
+
535
+ function keyword2cmyk(args) {
536
+ return rgb2cmyk(keyword2rgb(args));
537
+ }
538
+
539
+ function keyword2lab(args) {
540
+ return rgb2lab(keyword2rgb(args));
541
+ }
542
+
543
+ function keyword2xyz(args) {
544
+ return rgb2xyz(keyword2rgb(args));
545
+ }
546
+
547
+ var cssKeywords = {
548
+ aliceblue: [240,248,255],
549
+ antiquewhite: [250,235,215],
550
+ aqua: [0,255,255],
551
+ aquamarine: [127,255,212],
552
+ azure: [240,255,255],
553
+ beige: [245,245,220],
554
+ bisque: [255,228,196],
555
+ black: [0,0,0],
556
+ blanchedalmond: [255,235,205],
557
+ blue: [0,0,255],
558
+ blueviolet: [138,43,226],
559
+ brown: [165,42,42],
560
+ burlywood: [222,184,135],
561
+ cadetblue: [95,158,160],
562
+ chartreuse: [127,255,0],
563
+ chocolate: [210,105,30],
564
+ coral: [255,127,80],
565
+ cornflowerblue: [100,149,237],
566
+ cornsilk: [255,248,220],
567
+ crimson: [220,20,60],
568
+ cyan: [0,255,255],
569
+ darkblue: [0,0,139],
570
+ darkcyan: [0,139,139],
571
+ darkgoldenrod: [184,134,11],
572
+ darkgray: [169,169,169],
573
+ darkgreen: [0,100,0],
574
+ darkgrey: [169,169,169],
575
+ darkkhaki: [189,183,107],
576
+ darkmagenta: [139,0,139],
577
+ darkolivegreen: [85,107,47],
578
+ darkorange: [255,140,0],
579
+ darkorchid: [153,50,204],
580
+ darkred: [139,0,0],
581
+ darksalmon: [233,150,122],
582
+ darkseagreen: [143,188,143],
583
+ darkslateblue: [72,61,139],
584
+ darkslategray: [47,79,79],
585
+ darkslategrey: [47,79,79],
586
+ darkturquoise: [0,206,209],
587
+ darkviolet: [148,0,211],
588
+ deeppink: [255,20,147],
589
+ deepskyblue: [0,191,255],
590
+ dimgray: [105,105,105],
591
+ dimgrey: [105,105,105],
592
+ dodgerblue: [30,144,255],
593
+ firebrick: [178,34,34],
594
+ floralwhite: [255,250,240],
595
+ forestgreen: [34,139,34],
596
+ fuchsia: [255,0,255],
597
+ gainsboro: [220,220,220],
598
+ ghostwhite: [248,248,255],
599
+ gold: [255,215,0],
600
+ goldenrod: [218,165,32],
601
+ gray: [128,128,128],
602
+ green: [0,128,0],
603
+ greenyellow: [173,255,47],
604
+ grey: [128,128,128],
605
+ honeydew: [240,255,240],
606
+ hotpink: [255,105,180],
607
+ indianred: [205,92,92],
608
+ indigo: [75,0,130],
609
+ ivory: [255,255,240],
610
+ khaki: [240,230,140],
611
+ lavender: [230,230,250],
612
+ lavenderblush: [255,240,245],
613
+ lawngreen: [124,252,0],
614
+ lemonchiffon: [255,250,205],
615
+ lightblue: [173,216,230],
616
+ lightcoral: [240,128,128],
617
+ lightcyan: [224,255,255],
618
+ lightgoldenrodyellow: [250,250,210],
619
+ lightgray: [211,211,211],
620
+ lightgreen: [144,238,144],
621
+ lightgrey: [211,211,211],
622
+ lightpink: [255,182,193],
623
+ lightsalmon: [255,160,122],
624
+ lightseagreen: [32,178,170],
625
+ lightskyblue: [135,206,250],
626
+ lightslategray: [119,136,153],
627
+ lightslategrey: [119,136,153],
628
+ lightsteelblue: [176,196,222],
629
+ lightyellow: [255,255,224],
630
+ lime: [0,255,0],
631
+ limegreen: [50,205,50],
632
+ linen: [250,240,230],
633
+ magenta: [255,0,255],
634
+ maroon: [128,0,0],
635
+ mediumaquamarine: [102,205,170],
636
+ mediumblue: [0,0,205],
637
+ mediumorchid: [186,85,211],
638
+ mediumpurple: [147,112,219],
639
+ mediumseagreen: [60,179,113],
640
+ mediumslateblue: [123,104,238],
641
+ mediumspringgreen: [0,250,154],
642
+ mediumturquoise: [72,209,204],
643
+ mediumvioletred: [199,21,133],
644
+ midnightblue: [25,25,112],
645
+ mintcream: [245,255,250],
646
+ mistyrose: [255,228,225],
647
+ moccasin: [255,228,181],
648
+ navajowhite: [255,222,173],
649
+ navy: [0,0,128],
650
+ oldlace: [253,245,230],
651
+ olive: [128,128,0],
652
+ olivedrab: [107,142,35],
653
+ orange: [255,165,0],
654
+ orangered: [255,69,0],
655
+ orchid: [218,112,214],
656
+ palegoldenrod: [238,232,170],
657
+ palegreen: [152,251,152],
658
+ paleturquoise: [175,238,238],
659
+ palevioletred: [219,112,147],
660
+ papayawhip: [255,239,213],
661
+ peachpuff: [255,218,185],
662
+ peru: [205,133,63],
663
+ pink: [255,192,203],
664
+ plum: [221,160,221],
665
+ powderblue: [176,224,230],
666
+ purple: [128,0,128],
667
+ rebeccapurple: [102, 51, 153],
668
+ red: [255,0,0],
669
+ rosybrown: [188,143,143],
670
+ royalblue: [65,105,225],
671
+ saddlebrown: [139,69,19],
672
+ salmon: [250,128,114],
673
+ sandybrown: [244,164,96],
674
+ seagreen: [46,139,87],
675
+ seashell: [255,245,238],
676
+ sienna: [160,82,45],
677
+ silver: [192,192,192],
678
+ skyblue: [135,206,235],
679
+ slateblue: [106,90,205],
680
+ slategray: [112,128,144],
681
+ slategrey: [112,128,144],
682
+ snow: [255,250,250],
683
+ springgreen: [0,255,127],
684
+ steelblue: [70,130,180],
685
+ tan: [210,180,140],
686
+ teal: [0,128,128],
687
+ thistle: [216,191,216],
688
+ tomato: [255,99,71],
689
+ turquoise: [64,224,208],
690
+ violet: [238,130,238],
691
+ wheat: [245,222,179],
692
+ white: [255,255,255],
693
+ whitesmoke: [245,245,245],
694
+ yellow: [255,255,0],
695
+ yellowgreen: [154,205,50]
696
+ };
697
+
698
+ var reverseKeywords = {};
699
+ for (var key in cssKeywords) {
700
+ reverseKeywords[JSON.stringify(cssKeywords[key])] = key;
701
+ }
702
+
703
+ },{}],3:[function(require,module,exports){
704
+ var conversions = require("./conversions");
705
+
706
+ var convert = function() {
707
+ return new Converter();
708
+ }
709
+
710
+ for (var func in conversions) {
711
+ // export Raw versions
712
+ convert[func + "Raw"] = (function(func) {
713
+ // accept array or plain args
714
+ return function(arg) {
715
+ if (typeof arg == "number")
716
+ arg = Array.prototype.slice.call(arguments);
717
+ return conversions[func](arg);
718
+ }
719
+ })(func);
720
+
721
+ var pair = /(\w+)2(\w+)/.exec(func),
722
+ from = pair[1],
723
+ to = pair[2];
724
+
725
+ // export rgb2hsl and ["rgb"]["hsl"]
726
+ convert[from] = convert[from] || {};
727
+
728
+ convert[from][to] = convert[func] = (function(func) {
729
+ return function(arg) {
730
+ if (typeof arg == "number")
731
+ arg = Array.prototype.slice.call(arguments);
732
+
733
+ var val = conversions[func](arg);
734
+ if (typeof val == "string" || val === undefined)
735
+ return val; // keyword
736
+
737
+ for (var i = 0; i < val.length; i++)
738
+ val[i] = Math.round(val[i]);
739
+ return val;
740
+ }
741
+ })(func);
742
+ }
743
+
744
+
745
+ /* Converter does lazy conversion and caching */
746
+ var Converter = function() {
747
+ this.convs = {};
748
+ };
749
+
750
+ /* Either get the values for a space or
751
+ set the values for a space, depending on args */
752
+ Converter.prototype.routeSpace = function(space, args) {
753
+ var values = args[0];
754
+ if (values === undefined) {
755
+ // color.rgb()
756
+ return this.getValues(space);
757
+ }
758
+ // color.rgb(10, 10, 10)
759
+ if (typeof values == "number") {
760
+ values = Array.prototype.slice.call(args);
761
+ }
762
+
763
+ return this.setValues(space, values);
764
+ };
765
+
766
+ /* Set the values for a space, invalidating cache */
767
+ Converter.prototype.setValues = function(space, values) {
768
+ this.space = space;
769
+ this.convs = {};
770
+ this.convs[space] = values;
771
+ return this;
772
+ };
773
+
774
+ /* Get the values for a space. If there's already
775
+ a conversion for the space, fetch it, otherwise
776
+ compute it */
777
+ Converter.prototype.getValues = function(space) {
778
+ var vals = this.convs[space];
779
+ if (!vals) {
780
+ var fspace = this.space,
781
+ from = this.convs[fspace];
782
+ vals = convert[fspace][space](from);
783
+
784
+ this.convs[space] = vals;
785
+ }
786
+ return vals;
787
+ };
788
+
789
+ ["rgb", "hsl", "hsv", "cmyk", "keyword"].forEach(function(space) {
790
+ Converter.prototype[space] = function(vals) {
791
+ return this.routeSpace(space, arguments);
792
+ }
793
+ });
794
+
795
+ module.exports = convert;
796
+ },{"./conversions":2}],4:[function(require,module,exports){
797
+ /* MIT license */
798
+ var colorNames = require('color-name');
799
+
800
+ module.exports = {
801
+ getRgba: getRgba,
802
+ getHsla: getHsla,
803
+ getRgb: getRgb,
804
+ getHsl: getHsl,
805
+ getHwb: getHwb,
806
+ getAlpha: getAlpha,
807
+
808
+ hexString: hexString,
809
+ rgbString: rgbString,
810
+ rgbaString: rgbaString,
811
+ percentString: percentString,
812
+ percentaString: percentaString,
813
+ hslString: hslString,
814
+ hslaString: hslaString,
815
+ hwbString: hwbString,
816
+ keyword: keyword
817
+ }
818
+
819
+ function getRgba(string) {
820
+ if (!string) {
821
+ return;
822
+ }
823
+ var abbr = /^#([a-fA-F0-9]{3})$/,
824
+ hex = /^#([a-fA-F0-9]{6})$/,
825
+ rgba = /^rgba?\(\s*([+-]?\d+)\s*,\s*([+-]?\d+)\s*,\s*([+-]?\d+)\s*(?:,\s*([+-]?[\d\.]+)\s*)?\)$/,
826
+ per = /^rgba?\(\s*([+-]?[\d\.]+)\%\s*,\s*([+-]?[\d\.]+)\%\s*,\s*([+-]?[\d\.]+)\%\s*(?:,\s*([+-]?[\d\.]+)\s*)?\)$/,
827
+ keyword = /(\w+)/;
828
+
829
+ var rgb = [0, 0, 0],
830
+ a = 1,
831
+ match = string.match(abbr);
832
+ if (match) {
833
+ match = match[1];
834
+ for (var i = 0; i < rgb.length; i++) {
835
+ rgb[i] = parseInt(match[i] + match[i], 16);
836
+ }
837
+ }
838
+ else if (match = string.match(hex)) {
839
+ match = match[1];
840
+ for (var i = 0; i < rgb.length; i++) {
841
+ rgb[i] = parseInt(match.slice(i * 2, i * 2 + 2), 16);
842
+ }
843
+ }
844
+ else if (match = string.match(rgba)) {
845
+ for (var i = 0; i < rgb.length; i++) {
846
+ rgb[i] = parseInt(match[i + 1]);
847
+ }
848
+ a = parseFloat(match[4]);
849
+ }
850
+ else if (match = string.match(per)) {
851
+ for (var i = 0; i < rgb.length; i++) {
852
+ rgb[i] = Math.round(parseFloat(match[i + 1]) * 2.55);
853
+ }
854
+ a = parseFloat(match[4]);
855
+ }
856
+ else if (match = string.match(keyword)) {
857
+ if (match[1] == "transparent") {
858
+ return [0, 0, 0, 0];
859
+ }
860
+ rgb = colorNames[match[1]];
861
+ if (!rgb) {
862
+ return;
863
+ }
864
+ }
865
+
866
+ for (var i = 0; i < rgb.length; i++) {
867
+ rgb[i] = scale(rgb[i], 0, 255);
868
+ }
869
+ if (!a && a != 0) {
870
+ a = 1;
871
+ }
872
+ else {
873
+ a = scale(a, 0, 1);
874
+ }
875
+ rgb[3] = a;
876
+ return rgb;
877
+ }
878
+
879
+ function getHsla(string) {
880
+ if (!string) {
881
+ return;
882
+ }
883
+ var hsl = /^hsla?\(\s*([+-]?\d+)(?:deg)?\s*,\s*([+-]?[\d\.]+)%\s*,\s*([+-]?[\d\.]+)%\s*(?:,\s*([+-]?[\d\.]+)\s*)?\)/;
884
+ var match = string.match(hsl);
885
+ if (match) {
886
+ var alpha = parseFloat(match[4]);
887
+ var h = scale(parseInt(match[1]), 0, 360),
888
+ s = scale(parseFloat(match[2]), 0, 100),
889
+ l = scale(parseFloat(match[3]), 0, 100),
890
+ a = scale(isNaN(alpha) ? 1 : alpha, 0, 1);
891
+ return [h, s, l, a];
892
+ }
893
+ }
894
+
895
+ function getHwb(string) {
896
+ if (!string) {
897
+ return;
898
+ }
899
+ var hwb = /^hwb\(\s*([+-]?\d+)(?:deg)?\s*,\s*([+-]?[\d\.]+)%\s*,\s*([+-]?[\d\.]+)%\s*(?:,\s*([+-]?[\d\.]+)\s*)?\)/;
900
+ var match = string.match(hwb);
901
+ if (match) {
902
+ var alpha = parseFloat(match[4]);
903
+ var h = scale(parseInt(match[1]), 0, 360),
904
+ w = scale(parseFloat(match[2]), 0, 100),
905
+ b = scale(parseFloat(match[3]), 0, 100),
906
+ a = scale(isNaN(alpha) ? 1 : alpha, 0, 1);
907
+ return [h, w, b, a];
908
+ }
909
+ }
910
+
911
+ function getRgb(string) {
912
+ var rgba = getRgba(string);
913
+ return rgba && rgba.slice(0, 3);
914
+ }
915
+
916
+ function getHsl(string) {
917
+ var hsla = getHsla(string);
918
+ return hsla && hsla.slice(0, 3);
919
+ }
920
+
921
+ function getAlpha(string) {
922
+ var vals = getRgba(string);
923
+ if (vals) {
924
+ return vals[3];
925
+ }
926
+ else if (vals = getHsla(string)) {
927
+ return vals[3];
928
+ }
929
+ else if (vals = getHwb(string)) {
930
+ return vals[3];
931
+ }
932
+ }
933
+
934
+ // generators
935
+ function hexString(rgb) {
936
+ return "#" + hexDouble(rgb[0]) + hexDouble(rgb[1])
937
+ + hexDouble(rgb[2]);
938
+ }
939
+
940
+ function rgbString(rgba, alpha) {
941
+ if (alpha < 1 || (rgba[3] && rgba[3] < 1)) {
942
+ return rgbaString(rgba, alpha);
943
+ }
944
+ return "rgb(" + rgba[0] + ", " + rgba[1] + ", " + rgba[2] + ")";
945
+ }
946
+
947
+ function rgbaString(rgba, alpha) {
948
+ if (alpha === undefined) {
949
+ alpha = (rgba[3] !== undefined ? rgba[3] : 1);
950
+ }
951
+ return "rgba(" + rgba[0] + ", " + rgba[1] + ", " + rgba[2]
952
+ + ", " + alpha + ")";
953
+ }
954
+
955
+ function percentString(rgba, alpha) {
956
+ if (alpha < 1 || (rgba[3] && rgba[3] < 1)) {
957
+ return percentaString(rgba, alpha);
958
+ }
959
+ var r = Math.round(rgba[0]/255 * 100),
960
+ g = Math.round(rgba[1]/255 * 100),
961
+ b = Math.round(rgba[2]/255 * 100);
962
+
963
+ return "rgb(" + r + "%, " + g + "%, " + b + "%)";
964
+ }
965
+
966
+ function percentaString(rgba, alpha) {
967
+ var r = Math.round(rgba[0]/255 * 100),
968
+ g = Math.round(rgba[1]/255 * 100),
969
+ b = Math.round(rgba[2]/255 * 100);
970
+ return "rgba(" + r + "%, " + g + "%, " + b + "%, " + (alpha || rgba[3] || 1) + ")";
971
+ }
972
+
973
+ function hslString(hsla, alpha) {
974
+ if (alpha < 1 || (hsla[3] && hsla[3] < 1)) {
975
+ return hslaString(hsla, alpha);
976
+ }
977
+ return "hsl(" + hsla[0] + ", " + hsla[1] + "%, " + hsla[2] + "%)";
978
+ }
979
+
980
+ function hslaString(hsla, alpha) {
981
+ if (alpha === undefined) {
982
+ alpha = (hsla[3] !== undefined ? hsla[3] : 1);
983
+ }
984
+ return "hsla(" + hsla[0] + ", " + hsla[1] + "%, " + hsla[2] + "%, "
985
+ + alpha + ")";
986
+ }
987
+
988
+ // hwb is a bit different than rgb(a) & hsl(a) since there is no alpha specific syntax
989
+ // (hwb have alpha optional & 1 is default value)
990
+ function hwbString(hwb, alpha) {
991
+ if (alpha === undefined) {
992
+ alpha = (hwb[3] !== undefined ? hwb[3] : 1);
993
+ }
994
+ return "hwb(" + hwb[0] + ", " + hwb[1] + "%, " + hwb[2] + "%"
995
+ + (alpha !== undefined && alpha !== 1 ? ", " + alpha : "") + ")";
996
+ }
997
+
998
+ function keyword(rgb) {
999
+ return reverseNames[rgb.slice(0, 3)];
1000
+ }
1001
+
1002
+ // helpers
1003
+ function scale(num, min, max) {
1004
+ return Math.min(Math.max(min, num), max);
1005
+ }
1006
+
1007
+ function hexDouble(num) {
1008
+ var str = num.toString(16).toUpperCase();
1009
+ return (str.length < 2) ? "0" + str : str;
1010
+ }
1011
+
1012
+
1013
+ //create a list of reverse color names
1014
+ var reverseNames = {};
1015
+ for (var name in colorNames) {
1016
+ reverseNames[colorNames[name]] = name;
1017
+ }
1018
+
1019
+ },{"color-name":5}],5:[function(require,module,exports){
1020
+ module.exports = {
1021
+ "aliceblue": [240, 248, 255],
1022
+ "antiquewhite": [250, 235, 215],
1023
+ "aqua": [0, 255, 255],
1024
+ "aquamarine": [127, 255, 212],
1025
+ "azure": [240, 255, 255],
1026
+ "beige": [245, 245, 220],
1027
+ "bisque": [255, 228, 196],
1028
+ "black": [0, 0, 0],
1029
+ "blanchedalmond": [255, 235, 205],
1030
+ "blue": [0, 0, 255],
1031
+ "blueviolet": [138, 43, 226],
1032
+ "brown": [165, 42, 42],
1033
+ "burlywood": [222, 184, 135],
1034
+ "cadetblue": [95, 158, 160],
1035
+ "chartreuse": [127, 255, 0],
1036
+ "chocolate": [210, 105, 30],
1037
+ "coral": [255, 127, 80],
1038
+ "cornflowerblue": [100, 149, 237],
1039
+ "cornsilk": [255, 248, 220],
1040
+ "crimson": [220, 20, 60],
1041
+ "cyan": [0, 255, 255],
1042
+ "darkblue": [0, 0, 139],
1043
+ "darkcyan": [0, 139, 139],
1044
+ "darkgoldenrod": [184, 134, 11],
1045
+ "darkgray": [169, 169, 169],
1046
+ "darkgreen": [0, 100, 0],
1047
+ "darkgrey": [169, 169, 169],
1048
+ "darkkhaki": [189, 183, 107],
1049
+ "darkmagenta": [139, 0, 139],
1050
+ "darkolivegreen": [85, 107, 47],
1051
+ "darkorange": [255, 140, 0],
1052
+ "darkorchid": [153, 50, 204],
1053
+ "darkred": [139, 0, 0],
1054
+ "darksalmon": [233, 150, 122],
1055
+ "darkseagreen": [143, 188, 143],
1056
+ "darkslateblue": [72, 61, 139],
1057
+ "darkslategray": [47, 79, 79],
1058
+ "darkslategrey": [47, 79, 79],
1059
+ "darkturquoise": [0, 206, 209],
1060
+ "darkviolet": [148, 0, 211],
1061
+ "deeppink": [255, 20, 147],
1062
+ "deepskyblue": [0, 191, 255],
1063
+ "dimgray": [105, 105, 105],
1064
+ "dimgrey": [105, 105, 105],
1065
+ "dodgerblue": [30, 144, 255],
1066
+ "firebrick": [178, 34, 34],
1067
+ "floralwhite": [255, 250, 240],
1068
+ "forestgreen": [34, 139, 34],
1069
+ "fuchsia": [255, 0, 255],
1070
+ "gainsboro": [220, 220, 220],
1071
+ "ghostwhite": [248, 248, 255],
1072
+ "gold": [255, 215, 0],
1073
+ "goldenrod": [218, 165, 32],
1074
+ "gray": [128, 128, 128],
1075
+ "green": [0, 128, 0],
1076
+ "greenyellow": [173, 255, 47],
1077
+ "grey": [128, 128, 128],
1078
+ "honeydew": [240, 255, 240],
1079
+ "hotpink": [255, 105, 180],
1080
+ "indianred": [205, 92, 92],
1081
+ "indigo": [75, 0, 130],
1082
+ "ivory": [255, 255, 240],
1083
+ "khaki": [240, 230, 140],
1084
+ "lavender": [230, 230, 250],
1085
+ "lavenderblush": [255, 240, 245],
1086
+ "lawngreen": [124, 252, 0],
1087
+ "lemonchiffon": [255, 250, 205],
1088
+ "lightblue": [173, 216, 230],
1089
+ "lightcoral": [240, 128, 128],
1090
+ "lightcyan": [224, 255, 255],
1091
+ "lightgoldenrodyellow": [250, 250, 210],
1092
+ "lightgray": [211, 211, 211],
1093
+ "lightgreen": [144, 238, 144],
1094
+ "lightgrey": [211, 211, 211],
1095
+ "lightpink": [255, 182, 193],
1096
+ "lightsalmon": [255, 160, 122],
1097
+ "lightseagreen": [32, 178, 170],
1098
+ "lightskyblue": [135, 206, 250],
1099
+ "lightslategray": [119, 136, 153],
1100
+ "lightslategrey": [119, 136, 153],
1101
+ "lightsteelblue": [176, 196, 222],
1102
+ "lightyellow": [255, 255, 224],
1103
+ "lime": [0, 255, 0],
1104
+ "limegreen": [50, 205, 50],
1105
+ "linen": [250, 240, 230],
1106
+ "magenta": [255, 0, 255],
1107
+ "maroon": [128, 0, 0],
1108
+ "mediumaquamarine": [102, 205, 170],
1109
+ "mediumblue": [0, 0, 205],
1110
+ "mediumorchid": [186, 85, 211],
1111
+ "mediumpurple": [147, 112, 219],
1112
+ "mediumseagreen": [60, 179, 113],
1113
+ "mediumslateblue": [123, 104, 238],
1114
+ "mediumspringgreen": [0, 250, 154],
1115
+ "mediumturquoise": [72, 209, 204],
1116
+ "mediumvioletred": [199, 21, 133],
1117
+ "midnightblue": [25, 25, 112],
1118
+ "mintcream": [245, 255, 250],
1119
+ "mistyrose": [255, 228, 225],
1120
+ "moccasin": [255, 228, 181],
1121
+ "navajowhite": [255, 222, 173],
1122
+ "navy": [0, 0, 128],
1123
+ "oldlace": [253, 245, 230],
1124
+ "olive": [128, 128, 0],
1125
+ "olivedrab": [107, 142, 35],
1126
+ "orange": [255, 165, 0],
1127
+ "orangered": [255, 69, 0],
1128
+ "orchid": [218, 112, 214],
1129
+ "palegoldenrod": [238, 232, 170],
1130
+ "palegreen": [152, 251, 152],
1131
+ "paleturquoise": [175, 238, 238],
1132
+ "palevioletred": [219, 112, 147],
1133
+ "papayawhip": [255, 239, 213],
1134
+ "peachpuff": [255, 218, 185],
1135
+ "peru": [205, 133, 63],
1136
+ "pink": [255, 192, 203],
1137
+ "plum": [221, 160, 221],
1138
+ "powderblue": [176, 224, 230],
1139
+ "purple": [128, 0, 128],
1140
+ "rebeccapurple": [102, 51, 153],
1141
+ "red": [255, 0, 0],
1142
+ "rosybrown": [188, 143, 143],
1143
+ "royalblue": [65, 105, 225],
1144
+ "saddlebrown": [139, 69, 19],
1145
+ "salmon": [250, 128, 114],
1146
+ "sandybrown": [244, 164, 96],
1147
+ "seagreen": [46, 139, 87],
1148
+ "seashell": [255, 245, 238],
1149
+ "sienna": [160, 82, 45],
1150
+ "silver": [192, 192, 192],
1151
+ "skyblue": [135, 206, 235],
1152
+ "slateblue": [106, 90, 205],
1153
+ "slategray": [112, 128, 144],
1154
+ "slategrey": [112, 128, 144],
1155
+ "snow": [255, 250, 250],
1156
+ "springgreen": [0, 255, 127],
1157
+ "steelblue": [70, 130, 180],
1158
+ "tan": [210, 180, 140],
1159
+ "teal": [0, 128, 128],
1160
+ "thistle": [216, 191, 216],
1161
+ "tomato": [255, 99, 71],
1162
+ "turquoise": [64, 224, 208],
1163
+ "violet": [238, 130, 238],
1164
+ "wheat": [245, 222, 179],
1165
+ "white": [255, 255, 255],
1166
+ "whitesmoke": [245, 245, 245],
1167
+ "yellow": [255, 255, 0],
1168
+ "yellowgreen": [154, 205, 50]
1169
+ };
1170
+ },{}],6:[function(require,module,exports){
1171
+ /* MIT license */
1172
+
1173
+ var convert = require("color-convert"),
1174
+ string = require("color-string");
1175
+
1176
+ var Color = function(obj) {
1177
+ if (obj instanceof Color) return obj;
1178
+ if (!(this instanceof Color)) return new Color(obj);
1179
+
1180
+ this.values = {
1181
+ rgb: [0, 0, 0],
1182
+ hsl: [0, 0, 0],
1183
+ hsv: [0, 0, 0],
1184
+ hwb: [0, 0, 0],
1185
+ cmyk: [0, 0, 0, 0],
1186
+ alpha: 1
1187
+ }
1188
+
1189
+ // parse Color() argument
1190
+ if (typeof obj == "string") {
1191
+ var vals = string.getRgba(obj);
1192
+ if (vals) {
1193
+ this.setValues("rgb", vals);
1194
+ } else if (vals = string.getHsla(obj)) {
1195
+ this.setValues("hsl", vals);
1196
+ } else if (vals = string.getHwb(obj)) {
1197
+ this.setValues("hwb", vals);
1198
+ } else {
1199
+ throw new Error("Unable to parse color from string \"" + obj + "\"");
1200
+ }
1201
+ } else if (typeof obj == "object") {
1202
+ var vals = obj;
1203
+ if (vals["r"] !== undefined || vals["red"] !== undefined) {
1204
+ this.setValues("rgb", vals)
1205
+ } else if (vals["l"] !== undefined || vals["lightness"] !== undefined) {
1206
+ this.setValues("hsl", vals)
1207
+ } else if (vals["v"] !== undefined || vals["value"] !== undefined) {
1208
+ this.setValues("hsv", vals)
1209
+ } else if (vals["w"] !== undefined || vals["whiteness"] !== undefined) {
1210
+ this.setValues("hwb", vals)
1211
+ } else if (vals["c"] !== undefined || vals["cyan"] !== undefined) {
1212
+ this.setValues("cmyk", vals)
1213
+ } else {
1214
+ throw new Error("Unable to parse color from object " + JSON.stringify(obj));
1215
+ }
1216
+ }
1217
+ }
1218
+
1219
+ Color.prototype = {
1220
+ rgb: function(vals) {
1221
+ return this.setSpace("rgb", arguments);
1222
+ },
1223
+ hsl: function(vals) {
1224
+ return this.setSpace("hsl", arguments);
1225
+ },
1226
+ hsv: function(vals) {
1227
+ return this.setSpace("hsv", arguments);
1228
+ },
1229
+ hwb: function(vals) {
1230
+ return this.setSpace("hwb", arguments);
1231
+ },
1232
+ cmyk: function(vals) {
1233
+ return this.setSpace("cmyk", arguments);
1234
+ },
1235
+
1236
+ rgbArray: function() {
1237
+ return this.values.rgb;
1238
+ },
1239
+ hslArray: function() {
1240
+ return this.values.hsl;
1241
+ },
1242
+ hsvArray: function() {
1243
+ return this.values.hsv;
1244
+ },
1245
+ hwbArray: function() {
1246
+ if (this.values.alpha !== 1) {
1247
+ return this.values.hwb.concat([this.values.alpha])
1248
+ }
1249
+ return this.values.hwb;
1250
+ },
1251
+ cmykArray: function() {
1252
+ return this.values.cmyk;
1253
+ },
1254
+ rgbaArray: function() {
1255
+ var rgb = this.values.rgb;
1256
+ return rgb.concat([this.values.alpha]);
1257
+ },
1258
+ hslaArray: function() {
1259
+ var hsl = this.values.hsl;
1260
+ return hsl.concat([this.values.alpha]);
1261
+ },
1262
+ alpha: function(val) {
1263
+ if (val === undefined) {
1264
+ return this.values.alpha;
1265
+ }
1266
+ this.setValues("alpha", val);
1267
+ return this;
1268
+ },
1269
+
1270
+ red: function(val) {
1271
+ return this.setChannel("rgb", 0, val);
1272
+ },
1273
+ green: function(val) {
1274
+ return this.setChannel("rgb", 1, val);
1275
+ },
1276
+ blue: function(val) {
1277
+ return this.setChannel("rgb", 2, val);
1278
+ },
1279
+ hue: function(val) {
1280
+ return this.setChannel("hsl", 0, val);
1281
+ },
1282
+ saturation: function(val) {
1283
+ return this.setChannel("hsl", 1, val);
1284
+ },
1285
+ lightness: function(val) {
1286
+ return this.setChannel("hsl", 2, val);
1287
+ },
1288
+ saturationv: function(val) {
1289
+ return this.setChannel("hsv", 1, val);
1290
+ },
1291
+ whiteness: function(val) {
1292
+ return this.setChannel("hwb", 1, val);
1293
+ },
1294
+ blackness: function(val) {
1295
+ return this.setChannel("hwb", 2, val);
1296
+ },
1297
+ value: function(val) {
1298
+ return this.setChannel("hsv", 2, val);
1299
+ },
1300
+ cyan: function(val) {
1301
+ return this.setChannel("cmyk", 0, val);
1302
+ },
1303
+ magenta: function(val) {
1304
+ return this.setChannel("cmyk", 1, val);
1305
+ },
1306
+ yellow: function(val) {
1307
+ return this.setChannel("cmyk", 2, val);
1308
+ },
1309
+ black: function(val) {
1310
+ return this.setChannel("cmyk", 3, val);
1311
+ },
1312
+
1313
+ hexString: function() {
1314
+ return string.hexString(this.values.rgb);
1315
+ },
1316
+ rgbString: function() {
1317
+ return string.rgbString(this.values.rgb, this.values.alpha);
1318
+ },
1319
+ rgbaString: function() {
1320
+ return string.rgbaString(this.values.rgb, this.values.alpha);
1321
+ },
1322
+ percentString: function() {
1323
+ return string.percentString(this.values.rgb, this.values.alpha);
1324
+ },
1325
+ hslString: function() {
1326
+ return string.hslString(this.values.hsl, this.values.alpha);
1327
+ },
1328
+ hslaString: function() {
1329
+ return string.hslaString(this.values.hsl, this.values.alpha);
1330
+ },
1331
+ hwbString: function() {
1332
+ return string.hwbString(this.values.hwb, this.values.alpha);
1333
+ },
1334
+ keyword: function() {
1335
+ return string.keyword(this.values.rgb, this.values.alpha);
1336
+ },
1337
+
1338
+ rgbNumber: function() {
1339
+ return (this.values.rgb[0] << 16) | (this.values.rgb[1] << 8) | this.values.rgb[2];
1340
+ },
1341
+
1342
+ luminosity: function() {
1343
+ // http://www.w3.org/TR/WCAG20/#relativeluminancedef
1344
+ var rgb = this.values.rgb;
1345
+ var lum = [];
1346
+ for (var i = 0; i < rgb.length; i++) {
1347
+ var chan = rgb[i] / 255;
1348
+ lum[i] = (chan <= 0.03928) ? chan / 12.92 : Math.pow(((chan + 0.055) / 1.055), 2.4)
1349
+ }
1350
+ return 0.2126 * lum[0] + 0.7152 * lum[1] + 0.0722 * lum[2];
1351
+ },
1352
+
1353
+ contrast: function(color2) {
1354
+ // http://www.w3.org/TR/WCAG20/#contrast-ratiodef
1355
+ var lum1 = this.luminosity();
1356
+ var lum2 = color2.luminosity();
1357
+ if (lum1 > lum2) {
1358
+ return (lum1 + 0.05) / (lum2 + 0.05)
1359
+ };
1360
+ return (lum2 + 0.05) / (lum1 + 0.05);
1361
+ },
1362
+
1363
+ level: function(color2) {
1364
+ var contrastRatio = this.contrast(color2);
1365
+ return (contrastRatio >= 7.1) ? 'AAA' : (contrastRatio >= 4.5) ? 'AA' : '';
1366
+ },
1367
+
1368
+ dark: function() {
1369
+ // YIQ equation from http://24ways.org/2010/calculating-color-contrast
1370
+ var rgb = this.values.rgb,
1371
+ yiq = (rgb[0] * 299 + rgb[1] * 587 + rgb[2] * 114) / 1000;
1372
+ return yiq < 128;
1373
+ },
1374
+
1375
+ light: function() {
1376
+ return !this.dark();
1377
+ },
1378
+
1379
+ negate: function() {
1380
+ var rgb = []
1381
+ for (var i = 0; i < 3; i++) {
1382
+ rgb[i] = 255 - this.values.rgb[i];
1383
+ }
1384
+ this.setValues("rgb", rgb);
1385
+ return this;
1386
+ },
1387
+
1388
+ lighten: function(ratio) {
1389
+ this.values.hsl[2] += this.values.hsl[2] * ratio;
1390
+ this.setValues("hsl", this.values.hsl);
1391
+ return this;
1392
+ },
1393
+
1394
+ darken: function(ratio) {
1395
+ this.values.hsl[2] -= this.values.hsl[2] * ratio;
1396
+ this.setValues("hsl", this.values.hsl);
1397
+ return this;
1398
+ },
1399
+
1400
+ saturate: function(ratio) {
1401
+ this.values.hsl[1] += this.values.hsl[1] * ratio;
1402
+ this.setValues("hsl", this.values.hsl);
1403
+ return this;
1404
+ },
1405
+
1406
+ desaturate: function(ratio) {
1407
+ this.values.hsl[1] -= this.values.hsl[1] * ratio;
1408
+ this.setValues("hsl", this.values.hsl);
1409
+ return this;
1410
+ },
1411
+
1412
+ whiten: function(ratio) {
1413
+ this.values.hwb[1] += this.values.hwb[1] * ratio;
1414
+ this.setValues("hwb", this.values.hwb);
1415
+ return this;
1416
+ },
1417
+
1418
+ blacken: function(ratio) {
1419
+ this.values.hwb[2] += this.values.hwb[2] * ratio;
1420
+ this.setValues("hwb", this.values.hwb);
1421
+ return this;
1422
+ },
1423
+
1424
+ greyscale: function() {
1425
+ var rgb = this.values.rgb;
1426
+ // http://en.wikipedia.org/wiki/Grayscale#Converting_color_to_grayscale
1427
+ var val = rgb[0] * 0.3 + rgb[1] * 0.59 + rgb[2] * 0.11;
1428
+ this.setValues("rgb", [val, val, val]);
1429
+ return this;
1430
+ },
1431
+
1432
+ clearer: function(ratio) {
1433
+ this.setValues("alpha", this.values.alpha - (this.values.alpha * ratio));
1434
+ return this;
1435
+ },
1436
+
1437
+ opaquer: function(ratio) {
1438
+ this.setValues("alpha", this.values.alpha + (this.values.alpha * ratio));
1439
+ return this;
1440
+ },
1441
+
1442
+ rotate: function(degrees) {
1443
+ var hue = this.values.hsl[0];
1444
+ hue = (hue + degrees) % 360;
1445
+ hue = hue < 0 ? 360 + hue : hue;
1446
+ this.values.hsl[0] = hue;
1447
+ this.setValues("hsl", this.values.hsl);
1448
+ return this;
1449
+ },
1450
+
1451
+ mix: function(color2, weight) {
1452
+ weight = 1 - (weight == null ? 0.5 : weight);
1453
+
1454
+ // algorithm from Sass's mix(). Ratio of first color in mix is
1455
+ // determined by the alphas of both colors and the weight
1456
+ var t1 = weight * 2 - 1,
1457
+ d = this.alpha() - color2.alpha();
1458
+
1459
+ var weight1 = (((t1 * d == -1) ? t1 : (t1 + d) / (1 + t1 * d)) + 1) / 2;
1460
+ var weight2 = 1 - weight1;
1461
+
1462
+ var rgb = this.rgbArray();
1463
+ var rgb2 = color2.rgbArray();
1464
+
1465
+ for (var i = 0; i < rgb.length; i++) {
1466
+ rgb[i] = rgb[i] * weight1 + rgb2[i] * weight2;
1467
+ }
1468
+ this.setValues("rgb", rgb);
1469
+
1470
+ var alpha = this.alpha() * weight + color2.alpha() * (1 - weight);
1471
+ this.setValues("alpha", alpha);
1472
+
1473
+ return this;
1474
+ },
1475
+
1476
+ toJSON: function() {
1477
+ return this.rgb();
1478
+ },
1479
+
1480
+ clone: function() {
1481
+ return new Color(this.rgb());
1482
+ }
1483
+ }
1484
+
1485
+
1486
+ Color.prototype.getValues = function(space) {
1487
+ var vals = {};
1488
+ for (var i = 0; i < space.length; i++) {
1489
+ vals[space.charAt(i)] = this.values[space][i];
1490
+ }
1491
+ if (this.values.alpha != 1) {
1492
+ vals["a"] = this.values.alpha;
1493
+ }
1494
+ // {r: 255, g: 255, b: 255, a: 0.4}
1495
+ return vals;
1496
+ }
1497
+
1498
+ Color.prototype.setValues = function(space, vals) {
1499
+ var spaces = {
1500
+ "rgb": ["red", "green", "blue"],
1501
+ "hsl": ["hue", "saturation", "lightness"],
1502
+ "hsv": ["hue", "saturation", "value"],
1503
+ "hwb": ["hue", "whiteness", "blackness"],
1504
+ "cmyk": ["cyan", "magenta", "yellow", "black"]
1505
+ };
1506
+
1507
+ var maxes = {
1508
+ "rgb": [255, 255, 255],
1509
+ "hsl": [360, 100, 100],
1510
+ "hsv": [360, 100, 100],
1511
+ "hwb": [360, 100, 100],
1512
+ "cmyk": [100, 100, 100, 100]
1513
+ };
1514
+
1515
+ var alpha = 1;
1516
+ if (space == "alpha") {
1517
+ alpha = vals;
1518
+ } else if (vals.length) {
1519
+ // [10, 10, 10]
1520
+ this.values[space] = vals.slice(0, space.length);
1521
+ alpha = vals[space.length];
1522
+ } else if (vals[space.charAt(0)] !== undefined) {
1523
+ // {r: 10, g: 10, b: 10}
1524
+ for (var i = 0; i < space.length; i++) {
1525
+ this.values[space][i] = vals[space.charAt(i)];
1526
+ }
1527
+ alpha = vals.a;
1528
+ } else if (vals[spaces[space][0]] !== undefined) {
1529
+ // {red: 10, green: 10, blue: 10}
1530
+ var chans = spaces[space];
1531
+ for (var i = 0; i < space.length; i++) {
1532
+ this.values[space][i] = vals[chans[i]];
1533
+ }
1534
+ alpha = vals.alpha;
1535
+ }
1536
+ this.values.alpha = Math.max(0, Math.min(1, (alpha !== undefined ? alpha : this.values.alpha)));
1537
+ if (space == "alpha") {
1538
+ return;
1539
+ }
1540
+
1541
+ // cap values of the space prior converting all values
1542
+ for (var i = 0; i < space.length; i++) {
1543
+ var capped = Math.max(0, Math.min(maxes[space][i], this.values[space][i]));
1544
+ this.values[space][i] = Math.round(capped);
1545
+ }
1546
+
1547
+ // convert to all the other color spaces
1548
+ for (var sname in spaces) {
1549
+ if (sname != space) {
1550
+ this.values[sname] = convert[space][sname](this.values[space])
1551
+ }
1552
+
1553
+ // cap values
1554
+ for (var i = 0; i < sname.length; i++) {
1555
+ var capped = Math.max(0, Math.min(maxes[sname][i], this.values[sname][i]));
1556
+ this.values[sname][i] = Math.round(capped);
1557
+ }
1558
+ }
1559
+ return true;
1560
+ }
1561
+
1562
+ Color.prototype.setSpace = function(space, args) {
1563
+ var vals = args[0];
1564
+ if (vals === undefined) {
1565
+ // color.rgb()
1566
+ return this.getValues(space);
1567
+ }
1568
+ // color.rgb(10, 10, 10)
1569
+ if (typeof vals == "number") {
1570
+ vals = Array.prototype.slice.call(args);
1571
+ }
1572
+ this.setValues(space, vals);
1573
+ return this;
1574
+ }
1575
+
1576
+ Color.prototype.setChannel = function(space, index, val) {
1577
+ if (val === undefined) {
1578
+ // color.red()
1579
+ return this.values[space][index];
1580
+ }
1581
+ // color.red(100)
1582
+ this.values[space][index] = val;
1583
+ this.setValues(space, this.values[space]);
1584
+ return this;
1585
+ }
1586
+
1587
+ window.Color = module.exports = Color
1588
+
1589
+ },{"color-convert":3,"color-string":4}],7:[function(require,module,exports){
1590
+ /*!
1591
+ * Chart.js
1592
+ * http://chartjs.org/
1593
+ * Version: 2.0.2
1594
+ *
1595
+ * Copyright 2015 Nick Downie
1596
+ * Released under the MIT license
1597
+ * https://github.com/nnnick/Chart.js/blob/master/LICENSE.md
1598
+ */
1599
+
1600
+
1601
+ var Chart = require('./core/core.js')();
1602
+
1603
+ require('./core/core.helpers')(Chart);
1604
+ require('./core/core.element')(Chart);
1605
+ require('./core/core.animation')(Chart);
1606
+ require('./core/core.controller')(Chart);
1607
+ require('./core/core.datasetController')(Chart);
1608
+ require('./core/core.layoutService')(Chart);
1609
+ require('./core/core.legend')(Chart);
1610
+ require('./core/core.scale')(Chart);
1611
+ require('./core/core.scaleService')(Chart);
1612
+ require('./core/core.title')(Chart);
1613
+ require('./core/core.tooltip')(Chart);
1614
+
1615
+ require('./controllers/controller.bar')(Chart);
1616
+ require('./controllers/controller.bubble')(Chart);
1617
+ require('./controllers/controller.doughnut')(Chart);
1618
+ require('./controllers/controller.line')(Chart);
1619
+ require('./controllers/controller.polarArea')(Chart);
1620
+ require('./controllers/controller.radar')(Chart);
1621
+
1622
+ require('./scales/scale.category')(Chart);
1623
+ require('./scales/scale.linear')(Chart);
1624
+ require('./scales/scale.logarithmic')(Chart);
1625
+ require('./scales/scale.radialLinear')(Chart);
1626
+ require('./scales/scale.time')(Chart);
1627
+
1628
+ require('./elements/element.arc')(Chart);
1629
+ require('./elements/element.line')(Chart);
1630
+ require('./elements/element.point')(Chart);
1631
+ require('./elements/element.rectangle')(Chart);
1632
+
1633
+ require('./charts/Chart.Bar')(Chart);
1634
+ require('./charts/Chart.Bubble')(Chart);
1635
+ require('./charts/Chart.Doughnut')(Chart);
1636
+ require('./charts/Chart.Line')(Chart);
1637
+ require('./charts/Chart.PolarArea')(Chart);
1638
+ require('./charts/Chart.Radar')(Chart);
1639
+ require('./charts/Chart.Scatter')(Chart);
1640
+
1641
+ window.Chart = module.exports = Chart;
1642
+
1643
+ },{"./charts/Chart.Bar":8,"./charts/Chart.Bubble":9,"./charts/Chart.Doughnut":10,"./charts/Chart.Line":11,"./charts/Chart.PolarArea":12,"./charts/Chart.Radar":13,"./charts/Chart.Scatter":14,"./controllers/controller.bar":15,"./controllers/controller.bubble":16,"./controllers/controller.doughnut":17,"./controllers/controller.line":18,"./controllers/controller.polarArea":19,"./controllers/controller.radar":20,"./core/core.animation":21,"./core/core.controller":22,"./core/core.datasetController":23,"./core/core.element":24,"./core/core.helpers":25,"./core/core.js":26,"./core/core.layoutService":27,"./core/core.legend":28,"./core/core.scale":29,"./core/core.scaleService":30,"./core/core.title":31,"./core/core.tooltip":32,"./elements/element.arc":33,"./elements/element.line":34,"./elements/element.point":35,"./elements/element.rectangle":36,"./scales/scale.category":37,"./scales/scale.linear":38,"./scales/scale.logarithmic":39,"./scales/scale.radialLinear":40,"./scales/scale.time":41}],8:[function(require,module,exports){
1644
+ "use strict";
1645
+
1646
+ module.exports = function(Chart) {
1647
+
1648
+ Chart.Bar = function(context, config) {
1649
+ config.type = 'bar';
1650
+
1651
+ return new Chart(context, config);
1652
+ };
1653
+
1654
+ };
1655
+ },{}],9:[function(require,module,exports){
1656
+ "use strict";
1657
+
1658
+ module.exports = function(Chart) {
1659
+
1660
+ Chart.Bubble = function(context, config) {
1661
+ config.type = 'bubble';
1662
+ return new Chart(context, config);
1663
+ };
1664
+
1665
+ };
1666
+ },{}],10:[function(require,module,exports){
1667
+ "use strict";
1668
+
1669
+ module.exports = function(Chart) {
1670
+
1671
+ Chart.Doughnut = function(context, config) {
1672
+ config.type = 'doughnut';
1673
+
1674
+ return new Chart(context, config);
1675
+ };
1676
+
1677
+ };
1678
+ },{}],11:[function(require,module,exports){
1679
+ "use strict";
1680
+
1681
+ module.exports = function(Chart) {
1682
+
1683
+ Chart.Line = function(context, config) {
1684
+ config.type = 'line';
1685
+
1686
+ return new Chart(context, config);
1687
+ };
1688
+
1689
+ };
1690
+ },{}],12:[function(require,module,exports){
1691
+ "use strict";
1692
+
1693
+ module.exports = function(Chart) {
1694
+
1695
+ Chart.PolarArea = function(context, config) {
1696
+ config.type = 'polarArea';
1697
+
1698
+ return new Chart(context, config);
1699
+ };
1700
+
1701
+ };
1702
+ },{}],13:[function(require,module,exports){
1703
+ "use strict";
1704
+
1705
+ module.exports = function(Chart) {
1706
+
1707
+ var helpers = Chart.helpers;
1708
+
1709
+ var defaultConfig = {
1710
+ aspectRatio: 1
1711
+ };
1712
+
1713
+ Chart.Radar = function(context, config) {
1714
+ config.options = helpers.configMerge(defaultConfig, config.options);
1715
+ config.type = 'radar';
1716
+
1717
+ return new Chart(context, config);
1718
+ };
1719
+
1720
+ };
1721
+
1722
+ },{}],14:[function(require,module,exports){
1723
+ "use strict";
1724
+
1725
+ module.exports = function(Chart) {
1726
+
1727
+ var defaultConfig = {
1728
+ hover: {
1729
+ mode: 'single'
1730
+ },
1731
+
1732
+ scales: {
1733
+ xAxes: [{
1734
+ type: "linear", // scatter should not use a category axis
1735
+ position: "bottom",
1736
+ id: "x-axis-1" // need an ID so datasets can reference the scale
1737
+ }],
1738
+ yAxes: [{
1739
+ type: "linear",
1740
+ position: "left",
1741
+ id: "y-axis-1"
1742
+ }]
1743
+ },
1744
+
1745
+ tooltips: {
1746
+ callbacks: {
1747
+ title: function(tooltipItems, data) {
1748
+ // Title doesn't make sense for scatter since we format the data as a point
1749
+ return '';
1750
+ },
1751
+ label: function(tooltipItem, data) {
1752
+ return '(' + tooltipItem.xLabel + ', ' + tooltipItem.yLabel + ')';
1753
+ }
1754
+ }
1755
+ }
1756
+ };
1757
+
1758
+ // Register the default config for this type
1759
+ Chart.defaults.scatter = defaultConfig;
1760
+
1761
+ // Scatter charts use line controllers
1762
+ Chart.controllers.scatter = Chart.controllers.line;
1763
+
1764
+ Chart.Scatter = function(context, config) {
1765
+ config.type = 'scatter';
1766
+ return new Chart(context, config);
1767
+ };
1768
+
1769
+ };
1770
+ },{}],15:[function(require,module,exports){
1771
+ "use strict";
1772
+
1773
+ module.exports = function(Chart) {
1774
+
1775
+ var helpers = Chart.helpers;
1776
+
1777
+ Chart.defaults.bar = {
1778
+ hover: {
1779
+ mode: "label"
1780
+ },
1781
+
1782
+ scales: {
1783
+ xAxes: [{
1784
+ type: "category",
1785
+
1786
+ // Specific to Bar Controller
1787
+ categoryPercentage: 0.8,
1788
+ barPercentage: 0.9,
1789
+
1790
+ // grid line settings
1791
+ gridLines: {
1792
+ offsetGridLines: true
1793
+ }
1794
+ }],
1795
+ yAxes: [{
1796
+ type: "linear"
1797
+ }]
1798
+ }
1799
+ };
1800
+
1801
+ Chart.controllers.bar = Chart.DatasetController.extend({
1802
+ initialize: function(chart, datasetIndex) {
1803
+ Chart.DatasetController.prototype.initialize.call(this, chart, datasetIndex);
1804
+
1805
+ // Use this to indicate that this is a bar dataset.
1806
+ this.getDataset().bar = true;
1807
+ },
1808
+ // Get the number of datasets that display bars. We use this to correctly calculate the bar width
1809
+ getBarCount: function getBarCount() {
1810
+ var barCount = 0;
1811
+ helpers.each(this.chart.data.datasets, function(dataset) {
1812
+ if (helpers.isDatasetVisible(dataset) && dataset.bar) {
1813
+ ++barCount;
1814
+ }
1815
+ });
1816
+ return barCount;
1817
+ },
1818
+
1819
+ addElements: function() {
1820
+ this.getDataset().metaData = this.getDataset().metaData || [];
1821
+ helpers.each(this.getDataset().data, function(value, index) {
1822
+ this.getDataset().metaData[index] = this.getDataset().metaData[index] || new Chart.elements.Rectangle({
1823
+ _chart: this.chart.chart,
1824
+ _datasetIndex: this.index,
1825
+ _index: index
1826
+ });
1827
+ }, this);
1828
+ },
1829
+ addElementAndReset: function(index) {
1830
+ this.getDataset().metaData = this.getDataset().metaData || [];
1831
+ var rectangle = new Chart.elements.Rectangle({
1832
+ _chart: this.chart.chart,
1833
+ _datasetIndex: this.index,
1834
+ _index: index
1835
+ });
1836
+
1837
+ var numBars = this.getBarCount();
1838
+
1839
+ this.updateElement(rectangle, index, true, numBars);
1840
+ this.getDataset().metaData.splice(index, 0, rectangle);
1841
+ },
1842
+
1843
+ update: function update(reset) {
1844
+ var numBars = this.getBarCount();
1845
+
1846
+ helpers.each(this.getDataset().metaData, function(rectangle, index) {
1847
+ this.updateElement(rectangle, index, reset, numBars);
1848
+ }, this);
1849
+ },
1850
+
1851
+ updateElement: function updateElement(rectangle, index, reset, numBars) {
1852
+
1853
+ var xScale = this.getScaleForId(this.getDataset().xAxisID);
1854
+ var yScale = this.getScaleForId(this.getDataset().yAxisID);
1855
+
1856
+ var yScalePoint;
1857
+
1858
+ if (yScale.min < 0 && yScale.max < 0) {
1859
+ // all less than 0. use the top
1860
+ yScalePoint = yScale.getPixelForValue(yScale.max);
1861
+ } else if (yScale.min > 0 && yScale.max > 0) {
1862
+ yScalePoint = yScale.getPixelForValue(yScale.min);
1863
+ } else {
1864
+ yScalePoint = yScale.getPixelForValue(0);
1865
+ }
1866
+
1867
+ helpers.extend(rectangle, {
1868
+ // Utility
1869
+ _chart: this.chart.chart,
1870
+ _xScale: xScale,
1871
+ _yScale: yScale,
1872
+ _datasetIndex: this.index,
1873
+ _index: index,
1874
+
1875
+
1876
+ // Desired view properties
1877
+ _model: {
1878
+ x: this.calculateBarX(index, this.index),
1879
+ y: reset ? yScalePoint : this.calculateBarY(index, this.index),
1880
+
1881
+ // Tooltip
1882
+ label: this.chart.data.labels[index],
1883
+ datasetLabel: this.getDataset().label,
1884
+
1885
+ // Appearance
1886
+ base: reset ? yScalePoint : this.calculateBarBase(this.index, index),
1887
+ width: this.calculateBarWidth(numBars),
1888
+ backgroundColor: rectangle.custom && rectangle.custom.backgroundColor ? rectangle.custom.backgroundColor : helpers.getValueAtIndexOrDefault(this.getDataset().backgroundColor, index, this.chart.options.elements.rectangle.backgroundColor),
1889
+ borderSkipped: rectangle.custom && rectangle.custom.borderSkipped ? rectangle.custom.borderSkipped : this.chart.options.elements.rectangle.borderSkipped,
1890
+ borderColor: rectangle.custom && rectangle.custom.borderColor ? rectangle.custom.borderColor : helpers.getValueAtIndexOrDefault(this.getDataset().borderColor, index, this.chart.options.elements.rectangle.borderColor),
1891
+ borderWidth: rectangle.custom && rectangle.custom.borderWidth ? rectangle.custom.borderWidth : helpers.getValueAtIndexOrDefault(this.getDataset().borderWidth, index, this.chart.options.elements.rectangle.borderWidth)
1892
+ }
1893
+ });
1894
+ rectangle.pivot();
1895
+ },
1896
+
1897
+ calculateBarBase: function(datasetIndex, index) {
1898
+
1899
+ var xScale = this.getScaleForId(this.getDataset().xAxisID);
1900
+ var yScale = this.getScaleForId(this.getDataset().yAxisID);
1901
+
1902
+ var base = 0;
1903
+
1904
+ if (yScale.options.stacked) {
1905
+
1906
+ var value = this.chart.data.datasets[datasetIndex].data[index];
1907
+
1908
+ if (value < 0) {
1909
+ for (var i = 0; i < datasetIndex; i++) {
1910
+ var negDS = this.chart.data.datasets[i];
1911
+ if (helpers.isDatasetVisible(negDS) && negDS.yAxisID === yScale.id && negDS.bar) {
1912
+ base += negDS.data[index] < 0 ? negDS.data[index] : 0;
1913
+ }
1914
+ }
1915
+ } else {
1916
+ for (var j = 0; j < datasetIndex; j++) {
1917
+ var posDS = this.chart.data.datasets[j];
1918
+ if (helpers.isDatasetVisible(posDS) && posDS.yAxisID === yScale.id && posDS.bar) {
1919
+ base += posDS.data[index] > 0 ? posDS.data[index] : 0;
1920
+ }
1921
+ }
1922
+ }
1923
+
1924
+ return yScale.getPixelForValue(base);
1925
+ }
1926
+
1927
+ base = yScale.getPixelForValue(yScale.min);
1928
+
1929
+ if (yScale.beginAtZero || ((yScale.min <= 0 && yScale.max >= 0) || (yScale.min >= 0 && yScale.max <= 0))) {
1930
+ base = yScale.getPixelForValue(0, 0);
1931
+ //base += yScale.options.gridLines.lineWidth;
1932
+ } else if (yScale.min < 0 && yScale.max < 0) {
1933
+ // All values are negative. Use the top as the base
1934
+ base = yScale.getPixelForValue(yScale.max);
1935
+ }
1936
+
1937
+ return base;
1938
+
1939
+ },
1940
+
1941
+ getRuler: function() {
1942
+
1943
+ var xScale = this.getScaleForId(this.getDataset().xAxisID);
1944
+ var yScale = this.getScaleForId(this.getDataset().yAxisID);
1945
+ var datasetCount = this.getBarCount();
1946
+
1947
+ var tickWidth = (function() {
1948
+ var min = xScale.getPixelForTick(1) - xScale.getPixelForTick(0);
1949
+ for (var i = 2; i < this.getDataset().data.length; i++) {
1950
+ min = Math.min(xScale.getPixelForTick(i) - xScale.getPixelForTick(i - 1), min);
1951
+ }
1952
+ return min;
1953
+ }).call(this);
1954
+ var categoryWidth = tickWidth * xScale.options.categoryPercentage;
1955
+ var categorySpacing = (tickWidth - (tickWidth * xScale.options.categoryPercentage)) / 2;
1956
+ var fullBarWidth = categoryWidth / datasetCount;
1957
+ var barWidth = fullBarWidth * xScale.options.barPercentage;
1958
+ var barSpacing = fullBarWidth - (fullBarWidth * xScale.options.barPercentage);
1959
+
1960
+ return {
1961
+ datasetCount: datasetCount,
1962
+ tickWidth: tickWidth,
1963
+ categoryWidth: categoryWidth,
1964
+ categorySpacing: categorySpacing,
1965
+ fullBarWidth: fullBarWidth,
1966
+ barWidth: barWidth,
1967
+ barSpacing: barSpacing
1968
+ };
1969
+ },
1970
+
1971
+ calculateBarWidth: function() {
1972
+ var xScale = this.getScaleForId(this.getDataset().xAxisID);
1973
+ var ruler = this.getRuler();
1974
+ return xScale.options.stacked ? ruler.categoryWidth : ruler.barWidth;
1975
+ },
1976
+
1977
+ // Get bar index from the given dataset index accounting for the fact that not all bars are visible
1978
+ getBarIndex: function(datasetIndex) {
1979
+ var barIndex = 0;
1980
+
1981
+ for (var j = 0; j < datasetIndex; ++j) {
1982
+ if (helpers.isDatasetVisible(this.chart.data.datasets[j]) && this.chart.data.datasets[j].bar) {
1983
+ ++barIndex;
1984
+ }
1985
+ }
1986
+
1987
+ return barIndex;
1988
+ },
1989
+
1990
+ calculateBarX: function(index, datasetIndex) {
1991
+
1992
+ var yScale = this.getScaleForId(this.getDataset().yAxisID);
1993
+ var xScale = this.getScaleForId(this.getDataset().xAxisID);
1994
+ var barIndex = this.getBarIndex(datasetIndex);
1995
+
1996
+ var ruler = this.getRuler();
1997
+ var leftTick = xScale.getPixelForValue(null, index, datasetIndex, this.chart.isCombo);
1998
+ leftTick -= this.chart.isCombo ? (ruler.tickWidth / 2) : 0;
1999
+
2000
+ if (xScale.options.stacked) {
2001
+ return leftTick + (ruler.categoryWidth / 2) + ruler.categorySpacing;
2002
+ }
2003
+
2004
+ return leftTick +
2005
+ (ruler.barWidth / 2) +
2006
+ ruler.categorySpacing +
2007
+ (ruler.barWidth * barIndex) +
2008
+ (ruler.barSpacing / 2) +
2009
+ (ruler.barSpacing * barIndex);
2010
+ },
2011
+
2012
+ calculateBarY: function(index, datasetIndex) {
2013
+
2014
+ var xScale = this.getScaleForId(this.getDataset().xAxisID);
2015
+ var yScale = this.getScaleForId(this.getDataset().yAxisID);
2016
+
2017
+ var value = this.getDataset().data[index];
2018
+
2019
+ if (yScale.options.stacked) {
2020
+
2021
+ var sumPos = 0,
2022
+ sumNeg = 0;
2023
+
2024
+ for (var i = 0; i < datasetIndex; i++) {
2025
+ var ds = this.chart.data.datasets[i];
2026
+ if (helpers.isDatasetVisible(ds) && ds.bar && ds.yAxisID === yScale.id) {
2027
+ if (ds.data[index] < 0) {
2028
+ sumNeg += ds.data[index] || 0;
2029
+ } else {
2030
+ sumPos += ds.data[index] || 0;
2031
+ }
2032
+ }
2033
+ }
2034
+
2035
+ if (value < 0) {
2036
+ return yScale.getPixelForValue(sumNeg + value);
2037
+ } else {
2038
+ return yScale.getPixelForValue(sumPos + value);
2039
+ }
2040
+
2041
+ return yScale.getPixelForValue(value);
2042
+ }
2043
+
2044
+ return yScale.getPixelForValue(value);
2045
+ },
2046
+
2047
+ draw: function(ease) {
2048
+ var easingDecimal = ease || 1;
2049
+ helpers.each(this.getDataset().metaData, function(rectangle, index) {
2050
+ var d = this.getDataset().data[index];
2051
+ if (d !== null && d !== undefined && !isNaN(d)) {
2052
+ rectangle.transition(easingDecimal).draw();
2053
+ }
2054
+ }, this);
2055
+ },
2056
+
2057
+ setHoverStyle: function(rectangle) {
2058
+ var dataset = this.chart.data.datasets[rectangle._datasetIndex];
2059
+ var index = rectangle._index;
2060
+
2061
+ rectangle._model.backgroundColor = rectangle.custom && rectangle.custom.hoverBackgroundColor ? rectangle.custom.hoverBackgroundColor : helpers.getValueAtIndexOrDefault(dataset.hoverBackgroundColor, index, helpers.color(rectangle._model.backgroundColor).saturate(0.5).darken(0.1).rgbString());
2062
+ rectangle._model.borderColor = rectangle.custom && rectangle.custom.hoverBorderColor ? rectangle.custom.hoverBorderColor : helpers.getValueAtIndexOrDefault(dataset.hoverBorderColor, index, helpers.color(rectangle._model.borderColor).saturate(0.5).darken(0.1).rgbString());
2063
+ rectangle._model.borderWidth = rectangle.custom && rectangle.custom.hoverBorderWidth ? rectangle.custom.hoverBorderWidth : helpers.getValueAtIndexOrDefault(dataset.hoverBorderWidth, index, rectangle._model.borderWidth);
2064
+ },
2065
+
2066
+ removeHoverStyle: function(rectangle) {
2067
+ var dataset = this.chart.data.datasets[rectangle._datasetIndex];
2068
+ var index = rectangle._index;
2069
+
2070
+ rectangle._model.backgroundColor = rectangle.custom && rectangle.custom.backgroundColor ? rectangle.custom.backgroundColor : helpers.getValueAtIndexOrDefault(this.getDataset().backgroundColor, index, this.chart.options.elements.rectangle.backgroundColor);
2071
+ rectangle._model.borderColor = rectangle.custom && rectangle.custom.borderColor ? rectangle.custom.borderColor : helpers.getValueAtIndexOrDefault(this.getDataset().borderColor, index, this.chart.options.elements.rectangle.borderColor);
2072
+ rectangle._model.borderWidth = rectangle.custom && rectangle.custom.borderWidth ? rectangle.custom.borderWidth : helpers.getValueAtIndexOrDefault(this.getDataset().borderWidth, index, this.chart.options.elements.rectangle.borderWidth);
2073
+ }
2074
+
2075
+ });
2076
+ };
2077
+
2078
+ },{}],16:[function(require,module,exports){
2079
+ "use strict";
2080
+
2081
+ module.exports = function(Chart) {
2082
+
2083
+ var helpers = Chart.helpers;
2084
+
2085
+ Chart.defaults.bubble = {
2086
+ hover: {
2087
+ mode: "single"
2088
+ },
2089
+
2090
+ scales: {
2091
+ xAxes: [{
2092
+ type: "linear", // bubble should probably use a linear scale by default
2093
+ position: "bottom",
2094
+ id: "x-axis-0" // need an ID so datasets can reference the scale
2095
+ }],
2096
+ yAxes: [{
2097
+ type: "linear",
2098
+ position: "left",
2099
+ id: "y-axis-0"
2100
+ }]
2101
+ },
2102
+
2103
+ tooltips: {
2104
+ callbacks: {
2105
+ title: function(tooltipItems, data) {
2106
+ // Title doesn't make sense for scatter since we format the data as a point
2107
+ return '';
2108
+ },
2109
+ label: function(tooltipItem, data) {
2110
+ var datasetLabel = data.datasets[tooltipItem.datasetIndex].label || '';
2111
+ var dataPoint = data.datasets[tooltipItem.datasetIndex].data[tooltipItem.index];
2112
+ return datasetLabel + ': (' + dataPoint.x + ', ' + dataPoint.y + ', ' + dataPoint.r + ')';
2113
+ }
2114
+ }
2115
+ }
2116
+ };
2117
+
2118
+
2119
+ Chart.controllers.bubble = Chart.DatasetController.extend({
2120
+ addElements: function() {
2121
+
2122
+ this.getDataset().metaData = this.getDataset().metaData || [];
2123
+
2124
+ helpers.each(this.getDataset().data, function(value, index) {
2125
+ this.getDataset().metaData[index] = this.getDataset().metaData[index] || new Chart.elements.Point({
2126
+ _chart: this.chart.chart,
2127
+ _datasetIndex: this.index,
2128
+ _index: index
2129
+ });
2130
+ }, this);
2131
+ },
2132
+ addElementAndReset: function(index) {
2133
+ this.getDataset().metaData = this.getDataset().metaData || [];
2134
+ var point = new Chart.elements.Point({
2135
+ _chart: this.chart.chart,
2136
+ _datasetIndex: this.index,
2137
+ _index: index
2138
+ });
2139
+
2140
+ // Reset the point
2141
+ this.updateElement(point, index, true);
2142
+
2143
+ // Add to the points array
2144
+ this.getDataset().metaData.splice(index, 0, point);
2145
+ },
2146
+
2147
+ update: function update(reset) {
2148
+ var points = this.getDataset().metaData;
2149
+
2150
+ var yScale = this.getScaleForId(this.getDataset().yAxisID);
2151
+ var xScale = this.getScaleForId(this.getDataset().xAxisID);
2152
+ var scaleBase;
2153
+
2154
+ if (yScale.min < 0 && yScale.max < 0) {
2155
+ scaleBase = yScale.getPixelForValue(yScale.max);
2156
+ } else if (yScale.min > 0 && yScale.max > 0) {
2157
+ scaleBase = yScale.getPixelForValue(yScale.min);
2158
+ } else {
2159
+ scaleBase = yScale.getPixelForValue(0);
2160
+ }
2161
+
2162
+ // Update Points
2163
+ helpers.each(points, function(point, index) {
2164
+ this.updateElement(point, index, reset);
2165
+ }, this);
2166
+
2167
+ },
2168
+
2169
+ updateElement: function(point, index, reset) {
2170
+ var yScale = this.getScaleForId(this.getDataset().yAxisID);
2171
+ var xScale = this.getScaleForId(this.getDataset().xAxisID);
2172
+ var scaleBase;
2173
+
2174
+ if (yScale.min < 0 && yScale.max < 0) {
2175
+ scaleBase = yScale.getPixelForValue(yScale.max);
2176
+ } else if (yScale.min > 0 && yScale.max > 0) {
2177
+ scaleBase = yScale.getPixelForValue(yScale.min);
2178
+ } else {
2179
+ scaleBase = yScale.getPixelForValue(0);
2180
+ }
2181
+
2182
+ helpers.extend(point, {
2183
+ // Utility
2184
+ _chart: this.chart.chart,
2185
+ _xScale: xScale,
2186
+ _yScale: yScale,
2187
+ _datasetIndex: this.index,
2188
+ _index: index,
2189
+
2190
+ // Desired view properties
2191
+ _model: {
2192
+ x: reset ? xScale.getPixelForDecimal(0.5) : xScale.getPixelForValue(this.getDataset().data[index], index, this.index, this.chart.isCombo),
2193
+ y: reset ? scaleBase : yScale.getPixelForValue(this.getDataset().data[index], index, this.index),
2194
+ // Appearance
2195
+ radius: reset ? 0 : point.custom && point.custom.radius ? point.custom.radius : this.getRadius(this.getDataset().data[index]),
2196
+ backgroundColor: point.custom && point.custom.backgroundColor ? point.custom.backgroundColor : helpers.getValueAtIndexOrDefault(this.getDataset().backgroundColor, index, this.chart.options.elements.point.backgroundColor),
2197
+ borderColor: point.custom && point.custom.borderColor ? point.custom.borderColor : helpers.getValueAtIndexOrDefault(this.getDataset().borderColor, index, this.chart.options.elements.point.borderColor),
2198
+ borderWidth: point.custom && point.custom.borderWidth ? point.custom.borderWidth : helpers.getValueAtIndexOrDefault(this.getDataset().borderWidth, index, this.chart.options.elements.point.borderWidth),
2199
+
2200
+ // Tooltip
2201
+ hitRadius: point.custom && point.custom.hitRadius ? point.custom.hitRadius : helpers.getValueAtIndexOrDefault(this.getDataset().hitRadius, index, this.chart.options.elements.point.hitRadius)
2202
+ }
2203
+ });
2204
+
2205
+ point._model.skip = point.custom && point.custom.skip ? point.custom.skip : (isNaN(point._model.x) || isNaN(point._model.y));
2206
+
2207
+ point.pivot();
2208
+ },
2209
+
2210
+ getRadius: function(value) {
2211
+ return value.r || this.chart.options.elements.point.radius;
2212
+ },
2213
+
2214
+ draw: function(ease) {
2215
+ var easingDecimal = ease || 1;
2216
+
2217
+ // Transition and Draw the Points
2218
+ helpers.each(this.getDataset().metaData, function(point, index) {
2219
+ point.transition(easingDecimal);
2220
+ point.draw();
2221
+ });
2222
+
2223
+ },
2224
+
2225
+ setHoverStyle: function(point) {
2226
+ // Point
2227
+ var dataset = this.chart.data.datasets[point._datasetIndex];
2228
+ var index = point._index;
2229
+
2230
+ point._model.radius = point.custom && point.custom.hoverRadius ? point.custom.hoverRadius : (helpers.getValueAtIndexOrDefault(dataset.hoverRadius, index, this.chart.options.elements.point.hoverRadius)) + this.getRadius(this.getDataset().data[point._index]);
2231
+ point._model.backgroundColor = point.custom && point.custom.hoverBackgroundColor ? point.custom.hoverBackgroundColor : helpers.getValueAtIndexOrDefault(dataset.hoverBackgroundColor, index, helpers.color(point._model.backgroundColor).saturate(0.5).darken(0.1).rgbString());
2232
+ point._model.borderColor = point.custom && point.custom.hoverBorderColor ? point.custom.hoverBorderColor : helpers.getValueAtIndexOrDefault(dataset.hoverBorderColor, index, helpers.color(point._model.borderColor).saturate(0.5).darken(0.1).rgbString());
2233
+ point._model.borderWidth = point.custom && point.custom.hoverBorderWidth ? point.custom.hoverBorderWidth : helpers.getValueAtIndexOrDefault(dataset.hoverBorderWidth, index, point._model.borderWidth);
2234
+ },
2235
+
2236
+ removeHoverStyle: function(point) {
2237
+ var dataset = this.chart.data.datasets[point._datasetIndex];
2238
+ var index = point._index;
2239
+
2240
+ point._model.radius = point.custom && point.custom.radius ? point.custom.radius : this.getRadius(this.getDataset().data[point._index]);
2241
+ point._model.backgroundColor = point.custom && point.custom.backgroundColor ? point.custom.backgroundColor : helpers.getValueAtIndexOrDefault(this.getDataset().backgroundColor, index, this.chart.options.elements.point.backgroundColor);
2242
+ point._model.borderColor = point.custom && point.custom.borderColor ? point.custom.borderColor : helpers.getValueAtIndexOrDefault(this.getDataset().borderColor, index, this.chart.options.elements.point.borderColor);
2243
+ point._model.borderWidth = point.custom && point.custom.borderWidth ? point.custom.borderWidth : helpers.getValueAtIndexOrDefault(this.getDataset().borderWidth, index, this.chart.options.elements.point.borderWidth);
2244
+ }
2245
+ });
2246
+ };
2247
+ },{}],17:[function(require,module,exports){
2248
+ "use strict";
2249
+
2250
+ module.exports = function(Chart) {
2251
+
2252
+ var helpers = Chart.helpers;
2253
+
2254
+ Chart.defaults.doughnut = {
2255
+ animation: {
2256
+ //Boolean - Whether we animate the rotation of the Doughnut
2257
+ animateRotate: true,
2258
+ //Boolean - Whether we animate scaling the Doughnut from the centre
2259
+ animateScale: false
2260
+ },
2261
+ aspectRatio: 1,
2262
+ hover: {
2263
+ mode: 'single'
2264
+ },
2265
+ legendCallback: function(chart) {
2266
+ var text = [];
2267
+ text.push('<ul class="' + chart.id + '-legend">');
2268
+
2269
+ if (chart.data.datasets.length) {
2270
+ for (var i = 0; i < chart.data.datasets[0].data.length; ++i) {
2271
+ text.push('<li><span style="background-color:' + chart.data.datasets[0].backgroundColor[i] + '">');
2272
+ if (chart.data.labels[i]) {
2273
+ text.push(chart.data.labels[i]);
2274
+ }
2275
+ text.push('</span></li>');
2276
+ }
2277
+ }
2278
+
2279
+ text.push('</ul>');
2280
+ return text.join("");
2281
+ },
2282
+ legend: {
2283
+ labels: {
2284
+ generateLabels: function(data) {
2285
+ if (data.labels.length && data.datasets.length) {
2286
+ return data.labels.map(function(label, i) {
2287
+ return {
2288
+ text: label,
2289
+ fillStyle: data.datasets[0].backgroundColor[i],
2290
+ hidden: isNaN(data.datasets[0].data[i]),
2291
+
2292
+ // Extra data used for toggling the correct item
2293
+ index: i
2294
+ };
2295
+ });
2296
+ } else {
2297
+ return [];
2298
+ }
2299
+ }
2300
+ },
2301
+ onClick: function(e, legendItem) {
2302
+ helpers.each(this.chart.data.datasets, function(dataset) {
2303
+ dataset.metaHiddenData = dataset.metaHiddenData || [];
2304
+ var idx = legendItem.index;
2305
+
2306
+ if (!isNaN(dataset.data[idx])) {
2307
+ dataset.metaHiddenData[idx] = dataset.data[idx];
2308
+ dataset.data[idx] = NaN;
2309
+ } else if (!isNaN(dataset.metaHiddenData[idx])) {
2310
+ dataset.data[idx] = dataset.metaHiddenData[idx];
2311
+ }
2312
+ });
2313
+
2314
+ this.chart.update();
2315
+ }
2316
+ },
2317
+
2318
+ //The percentage of the chart that we cut out of the middle.
2319
+ cutoutPercentage: 50,
2320
+
2321
+ //The rotation of the chart, where the first data arc begins.
2322
+ rotation: Math.PI * -0.5,
2323
+
2324
+ //The total circumference of the chart.
2325
+ circumference: Math.PI * 2.0,
2326
+
2327
+ // Need to override these to give a nice default
2328
+ tooltips: {
2329
+ callbacks: {
2330
+ title: function() {
2331
+ return '';
2332
+ },
2333
+ label: function(tooltipItem, data) {
2334
+ return data.labels[tooltipItem.index] + ': ' + data.datasets[tooltipItem.datasetIndex].data[tooltipItem.index];
2335
+ }
2336
+ }
2337
+ }
2338
+ };
2339
+
2340
+ Chart.defaults.pie = helpers.clone(Chart.defaults.doughnut);
2341
+ helpers.extend(Chart.defaults.pie, {
2342
+ cutoutPercentage: 0
2343
+ });
2344
+
2345
+
2346
+ Chart.controllers.doughnut = Chart.controllers.pie = Chart.DatasetController.extend({
2347
+ linkScales: function() {
2348
+ // no scales for doughnut
2349
+ },
2350
+
2351
+ addElements: function() {
2352
+ this.getDataset().metaData = this.getDataset().metaData || [];
2353
+ helpers.each(this.getDataset().data, function(value, index) {
2354
+ this.getDataset().metaData[index] = this.getDataset().metaData[index] || new Chart.elements.Arc({
2355
+ _chart: this.chart.chart,
2356
+ _datasetIndex: this.index,
2357
+ _index: index
2358
+ });
2359
+ }, this);
2360
+ },
2361
+ addElementAndReset: function(index, colorForNewElement) {
2362
+ this.getDataset().metaData = this.getDataset().metaData || [];
2363
+ var arc = new Chart.elements.Arc({
2364
+ _chart: this.chart.chart,
2365
+ _datasetIndex: this.index,
2366
+ _index: index
2367
+ });
2368
+
2369
+ if (colorForNewElement && helpers.isArray(this.getDataset().backgroundColor)) {
2370
+ this.getDataset().backgroundColor.splice(index, 0, colorForNewElement);
2371
+ }
2372
+
2373
+ // Reset the point
2374
+ this.updateElement(arc, index, true);
2375
+
2376
+ // Add to the points array
2377
+ this.getDataset().metaData.splice(index, 0, arc);
2378
+ },
2379
+
2380
+ getVisibleDatasetCount: function getVisibleDatasetCount() {
2381
+ return helpers.where(this.chart.data.datasets, function(ds) {
2382
+ return helpers.isDatasetVisible(ds);
2383
+ }).length;
2384
+ },
2385
+
2386
+ // Get index of the dataset in relation to the visible datasets. This allows determining the inner and outer radius correctly
2387
+ getRingIndex: function getRingIndex(datasetIndex) {
2388
+ var ringIndex = 0;
2389
+
2390
+ for (var j = 0; j < datasetIndex; ++j) {
2391
+ if (helpers.isDatasetVisible(this.chart.data.datasets[j])) {
2392
+ ++ringIndex;
2393
+ }
2394
+ }
2395
+
2396
+ return ringIndex;
2397
+ },
2398
+
2399
+ update: function update(reset) {
2400
+ var availableWidth = this.chart.chartArea.right - this.chart.chartArea.left - this.chart.options.elements.arc.borderWidth;
2401
+ var availableHeight = this.chart.chartArea.bottom - this.chart.chartArea.top - this.chart.options.elements.arc.borderWidth;
2402
+ var minSize = Math.min(availableWidth, availableHeight);
2403
+ var offset = {x: 0, y: 0};
2404
+
2405
+ // If the chart's circumference isn't a full circle, calculate minSize as a ratio of the width/height of the arc
2406
+ if (this.chart.options.circumference && this.chart.options.circumference < Math.PI * 2.0) {
2407
+ var startAngle = this.chart.options.rotation % (Math.PI * 2.0);
2408
+ startAngle += Math.PI * 2.0 * (startAngle >= Math.PI ? -1 : startAngle < -Math.PI ? 1 : 0);
2409
+ var endAngle = startAngle + this.chart.options.circumference;
2410
+ var start = {x: Math.cos(startAngle), y: Math.sin(startAngle)};
2411
+ var end = {x: Math.cos(endAngle), y: Math.sin(endAngle)};
2412
+ var contains0 = (startAngle <= 0 && 0 <= endAngle) || (startAngle <= Math.PI * 2.0 && Math.PI * 2.0 <= endAngle);
2413
+ var contains90 = (startAngle <= Math.PI * 0.5 && Math.PI * 0.5 <= endAngle) || (startAngle <= Math.PI * 2.5 && Math.PI * 2.5 <= endAngle);
2414
+ var contains180 = (startAngle <= -Math.PI && -Math.PI <= endAngle) || (startAngle <= Math.PI && Math.PI <= endAngle);
2415
+ var contains270 = (startAngle <= -Math.PI * 0.5 && -Math.PI * 0.5 <= endAngle) || (startAngle <= Math.PI * 1.5 && Math.PI * 1.5 <= endAngle);
2416
+ var cutout = this.chart.options.cutoutPercentage / 100.0;
2417
+ 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))};
2418
+ 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))};
2419
+ var size = {width: (max.x - min.x) * 0.5, height: (max.y - min.y) * 0.5};
2420
+ minSize = Math.min(availableWidth / size.width, availableHeight / size.height);
2421
+ offset = {x: (max.x + min.x) * -0.5, y: (max.y + min.y) * -0.5};
2422
+ }
2423
+
2424
+ this.chart.outerRadius = Math.max(minSize / 2, 0);
2425
+ this.chart.innerRadius = Math.max(this.chart.options.cutoutPercentage ? (this.chart.outerRadius / 100) * (this.chart.options.cutoutPercentage) : 1, 0);
2426
+ this.chart.radiusLength = (this.chart.outerRadius - this.chart.innerRadius) / this.getVisibleDatasetCount();
2427
+ this.chart.offsetX = offset.x * this.chart.outerRadius;
2428
+ this.chart.offsetY = offset.y * this.chart.outerRadius;
2429
+
2430
+ this.getDataset().total = 0;
2431
+ helpers.each(this.getDataset().data, function(value) {
2432
+ if (!isNaN(value)) {
2433
+ this.getDataset().total += Math.abs(value);
2434
+ }
2435
+ }, this);
2436
+
2437
+ this.outerRadius = this.chart.outerRadius - (this.chart.radiusLength * this.getRingIndex(this.index));
2438
+ this.innerRadius = this.outerRadius - this.chart.radiusLength;
2439
+
2440
+ helpers.each(this.getDataset().metaData, function(arc, index) {
2441
+ this.updateElement(arc, index, reset);
2442
+ }, this);
2443
+ },
2444
+ updateElement: function(arc, index, reset) {
2445
+ var centerX = (this.chart.chartArea.left + this.chart.chartArea.right) / 2;
2446
+ var centerY = (this.chart.chartArea.top + this.chart.chartArea.bottom) / 2;
2447
+ var startAngle = this.chart.options.rotation || (Math.PI * -0.5); // non reset case handled later
2448
+ var endAngle = this.chart.options.rotation || (Math.PI * -0.5); // non reset case handled later
2449
+ var circumference = reset && this.chart.options.animation.animateRotate ? 0 : this.calculateCircumference(this.getDataset().data[index]) * ((this.chart.options.circumference || (2.0 * Math.PI)) / (2.0 * Math.PI));
2450
+ var innerRadius = reset && this.chart.options.animation.animateScale ? 0 : this.innerRadius;
2451
+ var outerRadius = reset && this.chart.options.animation.animateScale ? 0 : this.outerRadius;
2452
+
2453
+ helpers.extend(arc, {
2454
+ // Utility
2455
+ _chart: this.chart.chart,
2456
+ _datasetIndex: this.index,
2457
+ _index: index,
2458
+
2459
+ // Desired view properties
2460
+ _model: {
2461
+ x: centerX + this.chart.offsetX,
2462
+ y: centerY + this.chart.offsetY,
2463
+ startAngle: startAngle,
2464
+ endAngle: endAngle,
2465
+ circumference: circumference,
2466
+ outerRadius: outerRadius,
2467
+ innerRadius: innerRadius,
2468
+
2469
+ backgroundColor: arc.custom && arc.custom.backgroundColor ? arc.custom.backgroundColor : helpers.getValueAtIndexOrDefault(this.getDataset().backgroundColor, index, this.chart.options.elements.arc.backgroundColor),
2470
+ hoverBackgroundColor: arc.custom && arc.custom.hoverBackgroundColor ? arc.custom.hoverBackgroundColor : helpers.getValueAtIndexOrDefault(this.getDataset().hoverBackgroundColor, index, this.chart.options.elements.arc.hoverBackgroundColor),
2471
+ borderWidth: arc.custom && arc.custom.borderWidth ? arc.custom.borderWidth : helpers.getValueAtIndexOrDefault(this.getDataset().borderWidth, index, this.chart.options.elements.arc.borderWidth),
2472
+ borderColor: arc.custom && arc.custom.borderColor ? arc.custom.borderColor : helpers.getValueAtIndexOrDefault(this.getDataset().borderColor, index, this.chart.options.elements.arc.borderColor),
2473
+
2474
+ label: helpers.getValueAtIndexOrDefault(this.getDataset().label, index, this.chart.data.labels[index])
2475
+ }
2476
+ });
2477
+
2478
+ // Set correct angles if not resetting
2479
+ if (!reset) {
2480
+
2481
+ if (index === 0) {
2482
+ arc._model.startAngle = this.chart.options.rotation || (Math.PI * -0.5);
2483
+ } else {
2484
+ arc._model.startAngle = this.getDataset().metaData[index - 1]._model.endAngle;
2485
+ }
2486
+
2487
+ arc._model.endAngle = arc._model.startAngle + arc._model.circumference;
2488
+ }
2489
+
2490
+ arc.pivot();
2491
+ },
2492
+
2493
+ draw: function(ease) {
2494
+ var easingDecimal = ease || 1;
2495
+ helpers.each(this.getDataset().metaData, function(arc, index) {
2496
+ arc.transition(easingDecimal).draw();
2497
+ });
2498
+ },
2499
+
2500
+ setHoverStyle: function(arc) {
2501
+ var dataset = this.chart.data.datasets[arc._datasetIndex];
2502
+ var index = arc._index;
2503
+
2504
+ arc._model.backgroundColor = arc.custom && arc.custom.hoverBackgroundColor ? arc.custom.hoverBackgroundColor : helpers.getValueAtIndexOrDefault(dataset.hoverBackgroundColor, index, helpers.color(arc._model.backgroundColor).saturate(0.5).darken(0.1).rgbString());
2505
+ arc._model.borderColor = arc.custom && arc.custom.hoverBorderColor ? arc.custom.hoverBorderColor : helpers.getValueAtIndexOrDefault(dataset.hoverBorderColor, index, helpers.color(arc._model.borderColor).saturate(0.5).darken(0.1).rgbString());
2506
+ arc._model.borderWidth = arc.custom && arc.custom.hoverBorderWidth ? arc.custom.hoverBorderWidth : helpers.getValueAtIndexOrDefault(dataset.hoverBorderWidth, index, arc._model.borderWidth);
2507
+ },
2508
+
2509
+ removeHoverStyle: function(arc) {
2510
+ var dataset = this.chart.data.datasets[arc._datasetIndex];
2511
+ var index = arc._index;
2512
+
2513
+ arc._model.backgroundColor = arc.custom && arc.custom.backgroundColor ? arc.custom.backgroundColor : helpers.getValueAtIndexOrDefault(this.getDataset().backgroundColor, index, this.chart.options.elements.arc.backgroundColor);
2514
+ arc._model.borderColor = arc.custom && arc.custom.borderColor ? arc.custom.borderColor : helpers.getValueAtIndexOrDefault(this.getDataset().borderColor, index, this.chart.options.elements.arc.borderColor);
2515
+ arc._model.borderWidth = arc.custom && arc.custom.borderWidth ? arc.custom.borderWidth : helpers.getValueAtIndexOrDefault(this.getDataset().borderWidth, index, this.chart.options.elements.arc.borderWidth);
2516
+ },
2517
+
2518
+ calculateCircumference: function(value) {
2519
+ if (this.getDataset().total > 0 && !isNaN(value)) {
2520
+ return (Math.PI * 1.999999) * (value / this.getDataset().total);
2521
+ } else {
2522
+ return 0;
2523
+ }
2524
+ }
2525
+ });
2526
+ };
2527
+ },{}],18:[function(require,module,exports){
2528
+ "use strict";
2529
+
2530
+ module.exports = function(Chart) {
2531
+
2532
+ var helpers = Chart.helpers;
2533
+
2534
+ Chart.defaults.line = {
2535
+ showLines: true,
2536
+
2537
+ hover: {
2538
+ mode: "label"
2539
+ },
2540
+
2541
+ scales: {
2542
+ xAxes: [{
2543
+ type: "category",
2544
+ id: 'x-axis-0'
2545
+ }],
2546
+ yAxes: [{
2547
+ type: "linear",
2548
+ id: 'y-axis-0'
2549
+ }]
2550
+ }
2551
+ };
2552
+
2553
+
2554
+ Chart.controllers.line = Chart.DatasetController.extend({
2555
+ addElements: function() {
2556
+
2557
+ this.getDataset().metaData = this.getDataset().metaData || [];
2558
+
2559
+ this.getDataset().metaDataset = this.getDataset().metaDataset || new Chart.elements.Line({
2560
+ _chart: this.chart.chart,
2561
+ _datasetIndex: this.index,
2562
+ _points: this.getDataset().metaData
2563
+ });
2564
+
2565
+ helpers.each(this.getDataset().data, function(value, index) {
2566
+ this.getDataset().metaData[index] = this.getDataset().metaData[index] || new Chart.elements.Point({
2567
+ _chart: this.chart.chart,
2568
+ _datasetIndex: this.index,
2569
+ _index: index
2570
+ });
2571
+ }, this);
2572
+ },
2573
+ addElementAndReset: function(index) {
2574
+ this.getDataset().metaData = this.getDataset().metaData || [];
2575
+ var point = new Chart.elements.Point({
2576
+ _chart: this.chart.chart,
2577
+ _datasetIndex: this.index,
2578
+ _index: index
2579
+ });
2580
+
2581
+ // Reset the point
2582
+ this.updateElement(point, index, true);
2583
+
2584
+ // Add to the points array
2585
+ this.getDataset().metaData.splice(index, 0, point);
2586
+
2587
+ // Make sure bezier control points are updated
2588
+ if (this.chart.options.showLines && this.chart.options.elements.line.tension !== 0)
2589
+ this.updateBezierControlPoints();
2590
+ },
2591
+
2592
+ update: function update(reset) {
2593
+ var line = this.getDataset().metaDataset;
2594
+ var points = this.getDataset().metaData;
2595
+
2596
+ var yScale = this.getScaleForId(this.getDataset().yAxisID);
2597
+ var xScale = this.getScaleForId(this.getDataset().xAxisID);
2598
+ var scaleBase;
2599
+
2600
+ if (yScale.min < 0 && yScale.max < 0) {
2601
+ scaleBase = yScale.getPixelForValue(yScale.max);
2602
+ } else if (yScale.min > 0 && yScale.max > 0) {
2603
+ scaleBase = yScale.getPixelForValue(yScale.min);
2604
+ } else {
2605
+ scaleBase = yScale.getPixelForValue(0);
2606
+ }
2607
+
2608
+ // Update Line
2609
+ if (this.chart.options.showLines) {
2610
+ // Utility
2611
+ line._scale = yScale;
2612
+ line._datasetIndex = this.index;
2613
+ // Data
2614
+ line._children = points;
2615
+ // Model
2616
+ line._model = {
2617
+ // Appearance
2618
+ tension: line.custom && line.custom.tension ? line.custom.tension : helpers.getValueOrDefault(this.getDataset().tension, this.chart.options.elements.line.tension),
2619
+ backgroundColor: line.custom && line.custom.backgroundColor ? line.custom.backgroundColor : (this.getDataset().backgroundColor || this.chart.options.elements.line.backgroundColor),
2620
+ borderWidth: line.custom && line.custom.borderWidth ? line.custom.borderWidth : (this.getDataset().borderWidth || this.chart.options.elements.line.borderWidth),
2621
+ borderColor: line.custom && line.custom.borderColor ? line.custom.borderColor : (this.getDataset().borderColor || this.chart.options.elements.line.borderColor),
2622
+ borderCapStyle: line.custom && line.custom.borderCapStyle ? line.custom.borderCapStyle : (this.getDataset().borderCapStyle || this.chart.options.elements.line.borderCapStyle),
2623
+ borderDash: line.custom && line.custom.borderDash ? line.custom.borderDash : (this.getDataset().borderDash || this.chart.options.elements.line.borderDash),
2624
+ borderDashOffset: line.custom && line.custom.borderDashOffset ? line.custom.borderDashOffset : (this.getDataset().borderDashOffset || this.chart.options.elements.line.borderDashOffset),
2625
+ borderJoinStyle: line.custom && line.custom.borderJoinStyle ? line.custom.borderJoinStyle : (this.getDataset().borderJoinStyle || this.chart.options.elements.line.borderJoinStyle),
2626
+ fill: line.custom && line.custom.fill ? line.custom.fill : (this.getDataset().fill !== undefined ? this.getDataset().fill : this.chart.options.elements.line.fill),
2627
+ // Scale
2628
+ scaleTop: yScale.top,
2629
+ scaleBottom: yScale.bottom,
2630
+ scaleZero: scaleBase
2631
+ };
2632
+ line.pivot();
2633
+ }
2634
+
2635
+ // Update Points
2636
+ helpers.each(points, function(point, index) {
2637
+ this.updateElement(point, index, reset);
2638
+ }, this);
2639
+
2640
+ if (this.chart.options.showLines && this.chart.options.elements.line.tension !== 0)
2641
+ this.updateBezierControlPoints();
2642
+ },
2643
+
2644
+ getPointBackgroundColor: function(point, index) {
2645
+ var backgroundColor = this.chart.options.elements.point.backgroundColor;
2646
+ var dataset = this.getDataset();
2647
+
2648
+ if (point.custom && point.custom.backgroundColor) {
2649
+ backgroundColor = point.custom.backgroundColor;
2650
+ } else if (dataset.pointBackgroundColor) {
2651
+ backgroundColor = helpers.getValueAtIndexOrDefault(dataset.pointBackgroundColor, index, backgroundColor);
2652
+ } else if (dataset.backgroundColor) {
2653
+ backgroundColor = dataset.backgroundColor;
2654
+ }
2655
+
2656
+ return backgroundColor;
2657
+ },
2658
+ getPointBorderColor: function(point, index) {
2659
+ var borderColor = this.chart.options.elements.point.borderColor;
2660
+ var dataset = this.getDataset();
2661
+
2662
+ if (point.custom && point.custom.borderColor) {
2663
+ borderColor = point.custom.borderColor;
2664
+ } else if (dataset.pointBorderColor) {
2665
+ borderColor = helpers.getValueAtIndexOrDefault(this.getDataset().pointBorderColor, index, borderColor);
2666
+ } else if (dataset.borderColor) {
2667
+ borderColor = dataset.borderColor;
2668
+ }
2669
+
2670
+ return borderColor;
2671
+ },
2672
+ getPointBorderWidth: function(point, index) {
2673
+ var borderWidth = this.chart.options.elements.point.borderWidth;
2674
+ var dataset = this.getDataset();
2675
+
2676
+ if (point.custom && point.custom.borderWidth !== undefined) {
2677
+ borderWidth = point.custom.borderWidth;
2678
+ } else if (dataset.pointBorderWidth !== undefined) {
2679
+ borderWidth = helpers.getValueAtIndexOrDefault(dataset.pointBorderWidth, index, borderWidth);
2680
+ } else if (dataset.borderWidth !== undefined) {
2681
+ borderWidth = dataset.borderWidth;
2682
+ }
2683
+
2684
+ return borderWidth;
2685
+ },
2686
+
2687
+ updateElement: function(point, index, reset) {
2688
+ var yScale = this.getScaleForId(this.getDataset().yAxisID);
2689
+ var xScale = this.getScaleForId(this.getDataset().xAxisID);
2690
+ var scaleBase;
2691
+
2692
+ if (yScale.min < 0 && yScale.max < 0) {
2693
+ scaleBase = yScale.getPixelForValue(yScale.max);
2694
+ } else if (yScale.min > 0 && yScale.max > 0) {
2695
+ scaleBase = yScale.getPixelForValue(yScale.min);
2696
+ } else {
2697
+ scaleBase = yScale.getPixelForValue(0);
2698
+ }
2699
+
2700
+ // Utility
2701
+ point._chart = this.chart.chart;
2702
+ point._xScale = xScale;
2703
+ point._yScale = yScale;
2704
+ point._datasetIndex = this.index;
2705
+ point._index = index;
2706
+
2707
+ // Desired view properties
2708
+ point._model = {
2709
+ x: xScale.getPixelForValue(this.getDataset().data[index], index, this.index, this.chart.isCombo),
2710
+ y: reset ? scaleBase : this.calculatePointY(this.getDataset().data[index], index, this.index, this.chart.isCombo),
2711
+ // Appearance
2712
+ tension: point.custom && point.custom.tension ? point.custom.tension : helpers.getValueOrDefault(this.getDataset().tension, this.chart.options.elements.line.tension),
2713
+ radius: point.custom && point.custom.radius ? point.custom.radius : helpers.getValueAtIndexOrDefault(this.getDataset().radius, index, this.chart.options.elements.point.radius),
2714
+ pointStyle: point.custom && point.custom.pointStyle ? point.custom.pointStyle : helpers.getValueAtIndexOrDefault(this.getDataset().pointStyle, index, this.chart.options.elements.point.pointStyle),
2715
+ backgroundColor: this.getPointBackgroundColor(point, index),
2716
+ borderColor: this.getPointBorderColor(point, index),
2717
+ borderWidth: this.getPointBorderWidth(point, index),
2718
+ // Tooltip
2719
+ hitRadius: point.custom && point.custom.hitRadius ? point.custom.hitRadius : helpers.getValueAtIndexOrDefault(this.getDataset().hitRadius, index, this.chart.options.elements.point.hitRadius)
2720
+ };
2721
+
2722
+ point._model.skip = point.custom && point.custom.skip ? point.custom.skip : (isNaN(point._model.x) || isNaN(point._model.y));
2723
+ },
2724
+
2725
+ calculatePointY: function(value, index, datasetIndex, isCombo) {
2726
+
2727
+ var xScale = this.getScaleForId(this.getDataset().xAxisID);
2728
+ var yScale = this.getScaleForId(this.getDataset().yAxisID);
2729
+
2730
+ if (yScale.options.stacked) {
2731
+
2732
+ var sumPos = 0,
2733
+ sumNeg = 0;
2734
+
2735
+ for (var i = 0; i < datasetIndex; i++) {
2736
+ var ds = this.chart.data.datasets[i];
2737
+ if (ds.type === 'line' && helpers.isDatasetVisible(ds)) {
2738
+ if (ds.data[index] < 0) {
2739
+ sumNeg += ds.data[index] || 0;
2740
+ } else {
2741
+ sumPos += ds.data[index] || 0;
2742
+ }
2743
+ }
2744
+ }
2745
+
2746
+ if (value < 0) {
2747
+ return yScale.getPixelForValue(sumNeg + value);
2748
+ } else {
2749
+ return yScale.getPixelForValue(sumPos + value);
2750
+ }
2751
+ }
2752
+
2753
+ return yScale.getPixelForValue(value);
2754
+ },
2755
+
2756
+ updateBezierControlPoints: function() {
2757
+ // Update bezier control points
2758
+ helpers.each(this.getDataset().metaData, function(point, index) {
2759
+ var controlPoints = helpers.splineCurve(
2760
+ helpers.previousItem(this.getDataset().metaData, index)._model,
2761
+ point._model,
2762
+ helpers.nextItem(this.getDataset().metaData, index)._model,
2763
+ point._model.tension
2764
+ );
2765
+
2766
+ // Prevent the bezier going outside of the bounds of the graph
2767
+ point._model.controlPointPreviousX = Math.max(Math.min(controlPoints.previous.x, this.chart.chartArea.right), this.chart.chartArea.left);
2768
+ point._model.controlPointPreviousY = Math.max(Math.min(controlPoints.previous.y, this.chart.chartArea.bottom), this.chart.chartArea.top);
2769
+
2770
+ point._model.controlPointNextX = Math.max(Math.min(controlPoints.next.x, this.chart.chartArea.right), this.chart.chartArea.left);
2771
+ point._model.controlPointNextY = Math.max(Math.min(controlPoints.next.y, this.chart.chartArea.bottom), this.chart.chartArea.top);
2772
+
2773
+ // Now pivot the point for animation
2774
+ point.pivot();
2775
+ }, this);
2776
+ },
2777
+
2778
+ draw: function(ease) {
2779
+ var easingDecimal = ease || 1;
2780
+
2781
+ // Transition Point Locations
2782
+ helpers.each(this.getDataset().metaData, function(point) {
2783
+ point.transition(easingDecimal);
2784
+ });
2785
+
2786
+ // Transition and Draw the line
2787
+ if (this.chart.options.showLines)
2788
+ this.getDataset().metaDataset.transition(easingDecimal).draw();
2789
+
2790
+ // Draw the points
2791
+ helpers.each(this.getDataset().metaData, function(point) {
2792
+ point.draw();
2793
+ });
2794
+ },
2795
+
2796
+ setHoverStyle: function(point) {
2797
+ // Point
2798
+ var dataset = this.chart.data.datasets[point._datasetIndex];
2799
+ var index = point._index;
2800
+
2801
+ point._model.radius = point.custom && point.custom.hoverRadius ? point.custom.hoverRadius : helpers.getValueAtIndexOrDefault(dataset.pointHoverRadius, index, this.chart.options.elements.point.hoverRadius);
2802
+ point._model.backgroundColor = point.custom && point.custom.hoverBackgroundColor ? point.custom.hoverBackgroundColor : helpers.getValueAtIndexOrDefault(dataset.pointHoverBackgroundColor, index, helpers.color(point._model.backgroundColor).saturate(0.5).darken(0.1).rgbString());
2803
+ point._model.borderColor = point.custom && point.custom.hoverBorderColor ? point.custom.hoverBorderColor : helpers.getValueAtIndexOrDefault(dataset.pointHoverBorderColor, index, helpers.color(point._model.borderColor).saturate(0.5).darken(0.1).rgbString());
2804
+ point._model.borderWidth = point.custom && point.custom.hoverBorderWidth ? point.custom.hoverBorderWidth : helpers.getValueAtIndexOrDefault(dataset.pointHoverBorderWidth, index, point._model.borderWidth);
2805
+ },
2806
+
2807
+ removeHoverStyle: function(point) {
2808
+ var dataset = this.chart.data.datasets[point._datasetIndex];
2809
+ var index = point._index;
2810
+
2811
+ point._model.radius = point.custom && point.custom.radius ? point.custom.radius : helpers.getValueAtIndexOrDefault(this.getDataset().radius, index, this.chart.options.elements.point.radius);
2812
+ point._model.backgroundColor = this.getPointBackgroundColor(point, index);
2813
+ point._model.borderColor = this.getPointBorderColor(point, index);
2814
+ point._model.borderWidth = this.getPointBorderWidth(point, index);
2815
+ }
2816
+ });
2817
+ };
2818
+
2819
+ },{}],19:[function(require,module,exports){
2820
+ "use strict";
2821
+
2822
+ module.exports = function(Chart) {
2823
+
2824
+ var helpers = Chart.helpers;
2825
+
2826
+ Chart.defaults.polarArea = {
2827
+
2828
+ scale: {
2829
+ type: "radialLinear",
2830
+ lineArc: true // so that lines are circular
2831
+ },
2832
+
2833
+ //Boolean - Whether to animate the rotation of the chart
2834
+ animateRotate: true,
2835
+ animateScale: true,
2836
+
2837
+ aspectRatio: 1,
2838
+ legendCallback: function(chart) {
2839
+ var text = [];
2840
+ text.push('<ul class="' + chart.id + '-legend">');
2841
+
2842
+ if (chart.data.datasets.length) {
2843
+ for (var i = 0; i < chart.data.datasets[0].data.length; ++i) {
2844
+ text.push('<li><span style="background-color:' + chart.data.datasets[0].backgroundColor[i] + '">');
2845
+ if (chart.data.labels[i]) {
2846
+ text.push(chart.data.labels[i]);
2847
+ }
2848
+ text.push('</span></li>');
2849
+ }
2850
+ }
2851
+
2852
+ text.push('</ul>');
2853
+ return text.join("");
2854
+ },
2855
+ legend: {
2856
+ labels: {
2857
+ generateLabels: function(data) {
2858
+ if (data.labels.length && data.datasets.length) {
2859
+ return data.labels.map(function(label, i) {
2860
+ return {
2861
+ text: label,
2862
+ fillStyle: data.datasets[0].backgroundColor[i],
2863
+ hidden: isNaN(data.datasets[0].data[i]),
2864
+
2865
+ // Extra data used for toggling the correct item
2866
+ index: i
2867
+ };
2868
+ });
2869
+ } else {
2870
+ return [];
2871
+ }
2872
+ }
2873
+ },
2874
+ onClick: function(e, legendItem) {
2875
+ helpers.each(this.chart.data.datasets, function(dataset) {
2876
+ dataset.metaHiddenData = dataset.metaHiddenData || [];
2877
+ var idx = legendItem.index;
2878
+
2879
+ if (!isNaN(dataset.data[idx])) {
2880
+ dataset.metaHiddenData[idx] = dataset.data[idx];
2881
+ dataset.data[idx] = NaN;
2882
+ } else if (!isNaN(dataset.metaHiddenData[idx])) {
2883
+ dataset.data[idx] = dataset.metaHiddenData[idx];
2884
+ }
2885
+ });
2886
+
2887
+ this.chart.update();
2888
+ }
2889
+ },
2890
+
2891
+ // Need to override these to give a nice default
2892
+ tooltips: {
2893
+ callbacks: {
2894
+ title: function() {
2895
+ return '';
2896
+ },
2897
+ label: function(tooltipItem, data) {
2898
+ return data.labels[tooltipItem.index] + ': ' + tooltipItem.yLabel;
2899
+ }
2900
+ }
2901
+ }
2902
+ };
2903
+
2904
+ Chart.controllers.polarArea = Chart.DatasetController.extend({
2905
+ linkScales: function() {
2906
+ // no scales for doughnut
2907
+ },
2908
+ addElements: function() {
2909
+ this.getDataset().metaData = this.getDataset().metaData || [];
2910
+ helpers.each(this.getDataset().data, function(value, index) {
2911
+ this.getDataset().metaData[index] = this.getDataset().metaData[index] || new Chart.elements.Arc({
2912
+ _chart: this.chart.chart,
2913
+ _datasetIndex: this.index,
2914
+ _index: index
2915
+ });
2916
+ }, this);
2917
+ },
2918
+ addElementAndReset: function(index) {
2919
+ this.getDataset().metaData = this.getDataset().metaData || [];
2920
+ var arc = new Chart.elements.Arc({
2921
+ _chart: this.chart.chart,
2922
+ _datasetIndex: this.index,
2923
+ _index: index
2924
+ });
2925
+
2926
+ // Reset the point
2927
+ this.updateElement(arc, index, true);
2928
+
2929
+ // Add to the points array
2930
+ this.getDataset().metaData.splice(index, 0, arc);
2931
+ },
2932
+ getVisibleDatasetCount: function getVisibleDatasetCount() {
2933
+ return helpers.where(this.chart.data.datasets, function(ds) {
2934
+ return helpers.isDatasetVisible(ds);
2935
+ }).length;
2936
+ },
2937
+
2938
+ update: function update(reset) {
2939
+ var minSize = Math.min(this.chart.chartArea.right - this.chart.chartArea.left, this.chart.chartArea.bottom - this.chart.chartArea.top);
2940
+ this.chart.outerRadius = Math.max((minSize - this.chart.options.elements.arc.borderWidth / 2) / 2, 0);
2941
+ this.chart.innerRadius = Math.max(this.chart.options.cutoutPercentage ? (this.chart.outerRadius / 100) * (this.chart.options.cutoutPercentage) : 1, 0);
2942
+ this.chart.radiusLength = (this.chart.outerRadius - this.chart.innerRadius) / this.getVisibleDatasetCount();
2943
+
2944
+ this.getDataset().total = 0;
2945
+ helpers.each(this.getDataset().data, function(value) {
2946
+ this.getDataset().total += Math.abs(value);
2947
+ }, this);
2948
+
2949
+ this.outerRadius = this.chart.outerRadius - (this.chart.radiusLength * this.index);
2950
+ this.innerRadius = this.outerRadius - this.chart.radiusLength;
2951
+
2952
+ helpers.each(this.getDataset().metaData, function(arc, index) {
2953
+ this.updateElement(arc, index, reset);
2954
+ }, this);
2955
+ },
2956
+
2957
+ updateElement: function(arc, index, reset) {
2958
+ var circumference = this.calculateCircumference(this.getDataset().data[index]);
2959
+ var centerX = (this.chart.chartArea.left + this.chart.chartArea.right) / 2;
2960
+ var centerY = (this.chart.chartArea.top + this.chart.chartArea.bottom) / 2;
2961
+
2962
+ // If there is NaN data before us, we need to calculate the starting angle correctly.
2963
+ // We could be way more efficient here, but its unlikely that the polar area chart will have a lot of data
2964
+ var notNullIndex = 0;
2965
+ for (var i = 0; i < index; ++i) {
2966
+ if (!isNaN(this.getDataset().data[i])) {
2967
+ ++notNullIndex;
2968
+ }
2969
+ }
2970
+
2971
+ var startAngle = (-0.5 * Math.PI) + (circumference * notNullIndex);
2972
+ var endAngle = startAngle + circumference;
2973
+
2974
+ var resetModel = {
2975
+ x: centerX,
2976
+ y: centerY,
2977
+ innerRadius: 0,
2978
+ outerRadius: this.chart.options.animateScale ? 0 : this.chart.scale.getDistanceFromCenterForValue(this.getDataset().data[index]),
2979
+ startAngle: this.chart.options.animateRotate ? Math.PI * -0.5 : startAngle,
2980
+ endAngle: this.chart.options.animateRotate ? Math.PI * -0.5 : endAngle,
2981
+
2982
+ backgroundColor: arc.custom && arc.custom.backgroundColor ? arc.custom.backgroundColor : helpers.getValueAtIndexOrDefault(this.getDataset().backgroundColor, index, this.chart.options.elements.arc.backgroundColor),
2983
+ borderWidth: arc.custom && arc.custom.borderWidth ? arc.custom.borderWidth : helpers.getValueAtIndexOrDefault(this.getDataset().borderWidth, index, this.chart.options.elements.arc.borderWidth),
2984
+ borderColor: arc.custom && arc.custom.borderColor ? arc.custom.borderColor : helpers.getValueAtIndexOrDefault(this.getDataset().borderColor, index, this.chart.options.elements.arc.borderColor),
2985
+
2986
+ label: helpers.getValueAtIndexOrDefault(this.chart.data.labels, index, this.chart.data.labels[index])
2987
+ };
2988
+
2989
+ helpers.extend(arc, {
2990
+ // Utility
2991
+ _chart: this.chart.chart,
2992
+ _datasetIndex: this.index,
2993
+ _index: index,
2994
+ _scale: this.chart.scale,
2995
+
2996
+ // Desired view properties
2997
+ _model: reset ? resetModel : {
2998
+ x: centerX,
2999
+ y: centerY,
3000
+ innerRadius: 0,
3001
+ outerRadius: this.chart.scale.getDistanceFromCenterForValue(this.getDataset().data[index]),
3002
+ startAngle: startAngle,
3003
+ endAngle: endAngle,
3004
+
3005
+ backgroundColor: arc.custom && arc.custom.backgroundColor ? arc.custom.backgroundColor : helpers.getValueAtIndexOrDefault(this.getDataset().backgroundColor, index, this.chart.options.elements.arc.backgroundColor),
3006
+ borderWidth: arc.custom && arc.custom.borderWidth ? arc.custom.borderWidth : helpers.getValueAtIndexOrDefault(this.getDataset().borderWidth, index, this.chart.options.elements.arc.borderWidth),
3007
+ borderColor: arc.custom && arc.custom.borderColor ? arc.custom.borderColor : helpers.getValueAtIndexOrDefault(this.getDataset().borderColor, index, this.chart.options.elements.arc.borderColor),
3008
+
3009
+ label: helpers.getValueAtIndexOrDefault(this.chart.data.labels, index, this.chart.data.labels[index])
3010
+ }
3011
+ });
3012
+
3013
+ arc.pivot();
3014
+ },
3015
+
3016
+ draw: function(ease) {
3017
+ var easingDecimal = ease || 1;
3018
+ helpers.each(this.getDataset().metaData, function(arc, index) {
3019
+ arc.transition(easingDecimal).draw();
3020
+ });
3021
+ },
3022
+
3023
+ setHoverStyle: function(arc) {
3024
+ var dataset = this.chart.data.datasets[arc._datasetIndex];
3025
+ var index = arc._index;
3026
+
3027
+ arc._model.backgroundColor = arc.custom && arc.custom.hoverBackgroundColor ? arc.custom.hoverBackgroundColor : helpers.getValueAtIndexOrDefault(dataset.hoverBackgroundColor, index, helpers.color(arc._model.backgroundColor).saturate(0.5).darken(0.1).rgbString());
3028
+ arc._model.borderColor = arc.custom && arc.custom.hoverBorderColor ? arc.custom.hoverBorderColor : helpers.getValueAtIndexOrDefault(dataset.hoverBorderColor, index, helpers.color(arc._model.borderColor).saturate(0.5).darken(0.1).rgbString());
3029
+ arc._model.borderWidth = arc.custom && arc.custom.hoverBorderWidth ? arc.custom.hoverBorderWidth : helpers.getValueAtIndexOrDefault(dataset.hoverBorderWidth, index, arc._model.borderWidth);
3030
+ },
3031
+
3032
+ removeHoverStyle: function(arc) {
3033
+ var dataset = this.chart.data.datasets[arc._datasetIndex];
3034
+ var index = arc._index;
3035
+
3036
+ arc._model.backgroundColor = arc.custom && arc.custom.backgroundColor ? arc.custom.backgroundColor : helpers.getValueAtIndexOrDefault(this.getDataset().backgroundColor, index, this.chart.options.elements.arc.backgroundColor);
3037
+ arc._model.borderColor = arc.custom && arc.custom.borderColor ? arc.custom.borderColor : helpers.getValueAtIndexOrDefault(this.getDataset().borderColor, index, this.chart.options.elements.arc.borderColor);
3038
+ arc._model.borderWidth = arc.custom && arc.custom.borderWidth ? arc.custom.borderWidth : helpers.getValueAtIndexOrDefault(this.getDataset().borderWidth, index, this.chart.options.elements.arc.borderWidth);
3039
+ },
3040
+
3041
+ calculateCircumference: function(value) {
3042
+ if (isNaN(value)) {
3043
+ return 0;
3044
+ } else {
3045
+ // Count the number of NaN values
3046
+ var numNaN = helpers.where(this.getDataset().data, function(data) {
3047
+ return isNaN(data);
3048
+ }).length;
3049
+
3050
+ return (2 * Math.PI) / (this.getDataset().data.length - numNaN);
3051
+ }
3052
+ }
3053
+ });
3054
+
3055
+ };
3056
+ },{}],20:[function(require,module,exports){
3057
+ "use strict";
3058
+
3059
+ module.exports = function(Chart) {
3060
+
3061
+ var helpers = Chart.helpers;
3062
+
3063
+
3064
+ Chart.defaults.radar = {
3065
+ scale: {
3066
+ type: "radialLinear"
3067
+ },
3068
+ elements: {
3069
+ line: {
3070
+ tension: 0 // no bezier in radar
3071
+ }
3072
+ }
3073
+ };
3074
+
3075
+ Chart.controllers.radar = Chart.DatasetController.extend({
3076
+ linkScales: function() {
3077
+ // No need. Single scale only
3078
+ },
3079
+
3080
+ addElements: function() {
3081
+
3082
+ this.getDataset().metaData = this.getDataset().metaData || [];
3083
+
3084
+ this.getDataset().metaDataset = this.getDataset().metaDataset || new Chart.elements.Line({
3085
+ _chart: this.chart.chart,
3086
+ _datasetIndex: this.index,
3087
+ _points: this.getDataset().metaData,
3088
+ _loop: true
3089
+ });
3090
+
3091
+ helpers.each(this.getDataset().data, function(value, index) {
3092
+ this.getDataset().metaData[index] = this.getDataset().metaData[index] || new Chart.elements.Point({
3093
+ _chart: this.chart.chart,
3094
+ _datasetIndex: this.index,
3095
+ _index: index,
3096
+ _model: {
3097
+ x: 0, //xScale.getPixelForValue(null, index, true),
3098
+ y: 0 //this.chartArea.bottom,
3099
+ }
3100
+ });
3101
+ }, this);
3102
+ },
3103
+ addElementAndReset: function(index) {
3104
+ this.getDataset().metaData = this.getDataset().metaData || [];
3105
+ var point = new Chart.elements.Point({
3106
+ _chart: this.chart.chart,
3107
+ _datasetIndex: this.index,
3108
+ _index: index
3109
+ });
3110
+
3111
+ // Reset the point
3112
+ this.updateElement(point, index, true);
3113
+
3114
+ // Add to the points array
3115
+ this.getDataset().metaData.splice(index, 0, point);
3116
+
3117
+ // Make sure bezier control points are updated
3118
+ this.updateBezierControlPoints();
3119
+ },
3120
+
3121
+ update: function update(reset) {
3122
+
3123
+ var line = this.getDataset().metaDataset;
3124
+ var points = this.getDataset().metaData;
3125
+
3126
+ var scale = this.chart.scale;
3127
+ var scaleBase;
3128
+
3129
+ if (scale.min < 0 && scale.max < 0) {
3130
+ scaleBase = scale.getPointPositionForValue(0, scale.max);
3131
+ } else if (scale.min > 0 && scale.max > 0) {
3132
+ scaleBase = scale.getPointPositionForValue(0, scale.min);
3133
+ } else {
3134
+ scaleBase = scale.getPointPositionForValue(0, 0);
3135
+ }
3136
+
3137
+ helpers.extend(this.getDataset().metaDataset, {
3138
+ // Utility
3139
+ _datasetIndex: this.index,
3140
+ // Data
3141
+ _children: this.getDataset().metaData,
3142
+ // Model
3143
+ _model: {
3144
+ // Appearance
3145
+ tension: line.custom && line.custom.tension ? line.custom.tension : helpers.getValueOrDefault(this.getDataset().tension, this.chart.options.elements.line.tension),
3146
+ backgroundColor: line.custom && line.custom.backgroundColor ? line.custom.backgroundColor : (this.getDataset().backgroundColor || this.chart.options.elements.line.backgroundColor),
3147
+ borderWidth: line.custom && line.custom.borderWidth ? line.custom.borderWidth : (this.getDataset().borderWidth || this.chart.options.elements.line.borderWidth),
3148
+ borderColor: line.custom && line.custom.borderColor ? line.custom.borderColor : (this.getDataset().borderColor || this.chart.options.elements.line.borderColor),
3149
+ fill: line.custom && line.custom.fill ? line.custom.fill : (this.getDataset().fill !== undefined ? this.getDataset().fill : this.chart.options.elements.line.fill),
3150
+ borderCapStyle: line.custom && line.custom.borderCapStyle ? line.custom.borderCapStyle : (this.getDataset().borderCapStyle || this.chart.options.elements.line.borderCapStyle),
3151
+ borderDash: line.custom && line.custom.borderDash ? line.custom.borderDash : (this.getDataset().borderDash || this.chart.options.elements.line.borderDash),
3152
+ borderDashOffset: line.custom && line.custom.borderDashOffset ? line.custom.borderDashOffset : (this.getDataset().borderDashOffset || this.chart.options.elements.line.borderDashOffset),
3153
+ borderJoinStyle: line.custom && line.custom.borderJoinStyle ? line.custom.borderJoinStyle : (this.getDataset().borderJoinStyle || this.chart.options.elements.line.borderJoinStyle),
3154
+
3155
+ // Scale
3156
+ scaleTop: scale.top,
3157
+ scaleBottom: scale.bottom,
3158
+ scaleZero: scaleBase
3159
+ }
3160
+ });
3161
+
3162
+ this.getDataset().metaDataset.pivot();
3163
+
3164
+ // Update Points
3165
+ helpers.each(points, function(point, index) {
3166
+ this.updateElement(point, index, reset);
3167
+ }, this);
3168
+
3169
+
3170
+ // Update bezier control points
3171
+ this.updateBezierControlPoints();
3172
+ },
3173
+ updateElement: function(point, index, reset) {
3174
+ var pointPosition = this.chart.scale.getPointPositionForValue(index, this.getDataset().data[index]);
3175
+
3176
+ helpers.extend(point, {
3177
+ // Utility
3178
+ _datasetIndex: this.index,
3179
+ _index: index,
3180
+ _scale: this.chart.scale,
3181
+
3182
+ // Desired view properties
3183
+ _model: {
3184
+ x: reset ? this.chart.scale.xCenter : pointPosition.x, // value not used in dataset scale, but we want a consistent API between scales
3185
+ y: reset ? this.chart.scale.yCenter : pointPosition.y,
3186
+
3187
+ // Appearance
3188
+ tension: point.custom && point.custom.tension ? point.custom.tension : helpers.getValueOrDefault(this.getDataset().tension, this.chart.options.elements.line.tension),
3189
+ radius: point.custom && point.custom.radius ? point.custom.radius : helpers.getValueAtIndexOrDefault(this.getDataset().pointRadius, index, this.chart.options.elements.point.radius),
3190
+ backgroundColor: point.custom && point.custom.backgroundColor ? point.custom.backgroundColor : helpers.getValueAtIndexOrDefault(this.getDataset().pointBackgroundColor, index, this.chart.options.elements.point.backgroundColor),
3191
+ borderColor: point.custom && point.custom.borderColor ? point.custom.borderColor : helpers.getValueAtIndexOrDefault(this.getDataset().pointBorderColor, index, this.chart.options.elements.point.borderColor),
3192
+ borderWidth: point.custom && point.custom.borderWidth ? point.custom.borderWidth : helpers.getValueAtIndexOrDefault(this.getDataset().pointBorderWidth, index, this.chart.options.elements.point.borderWidth),
3193
+ pointStyle: point.custom && point.custom.pointStyle ? point.custom.pointStyle : helpers.getValueAtIndexOrDefault(this.getDataset().pointStyle, index, this.chart.options.elements.point.pointStyle),
3194
+
3195
+ // Tooltip
3196
+ hitRadius: point.custom && point.custom.hitRadius ? point.custom.hitRadius : helpers.getValueAtIndexOrDefault(this.getDataset().hitRadius, index, this.chart.options.elements.point.hitRadius)
3197
+ }
3198
+ });
3199
+
3200
+ point._model.skip = point.custom && point.custom.skip ? point.custom.skip : (isNaN(point._model.x) || isNaN(point._model.y));
3201
+ },
3202
+ updateBezierControlPoints: function() {
3203
+ helpers.each(this.getDataset().metaData, function(point, index) {
3204
+ var controlPoints = helpers.splineCurve(
3205
+ helpers.previousItem(this.getDataset().metaData, index, true)._model,
3206
+ point._model,
3207
+ helpers.nextItem(this.getDataset().metaData, index, true)._model,
3208
+ point._model.tension
3209
+ );
3210
+
3211
+ // Prevent the bezier going outside of the bounds of the graph
3212
+ point._model.controlPointPreviousX = Math.max(Math.min(controlPoints.previous.x, this.chart.chartArea.right), this.chart.chartArea.left);
3213
+ point._model.controlPointPreviousY = Math.max(Math.min(controlPoints.previous.y, this.chart.chartArea.bottom), this.chart.chartArea.top);
3214
+
3215
+ point._model.controlPointNextX = Math.max(Math.min(controlPoints.next.x, this.chart.chartArea.right), this.chart.chartArea.left);
3216
+ point._model.controlPointNextY = Math.max(Math.min(controlPoints.next.y, this.chart.chartArea.bottom), this.chart.chartArea.top);
3217
+
3218
+ // Now pivot the point for animation
3219
+ point.pivot();
3220
+ }, this);
3221
+ },
3222
+
3223
+ draw: function(ease) {
3224
+ var easingDecimal = ease || 1;
3225
+
3226
+ // Transition Point Locations
3227
+ helpers.each(this.getDataset().metaData, function(point, index) {
3228
+ point.transition(easingDecimal);
3229
+ });
3230
+
3231
+ // Transition and Draw the line
3232
+ this.getDataset().metaDataset.transition(easingDecimal).draw();
3233
+
3234
+ // Draw the points
3235
+ helpers.each(this.getDataset().metaData, function(point) {
3236
+ point.draw();
3237
+ });
3238
+ },
3239
+
3240
+ setHoverStyle: function(point) {
3241
+ // Point
3242
+ var dataset = this.chart.data.datasets[point._datasetIndex];
3243
+ var index = point._index;
3244
+
3245
+ point._model.radius = point.custom && point.custom.hoverRadius ? point.custom.hoverRadius : helpers.getValueAtIndexOrDefault(dataset.pointHoverRadius, index, this.chart.options.elements.point.hoverRadius);
3246
+ point._model.backgroundColor = point.custom && point.custom.hoverBackgroundColor ? point.custom.hoverBackgroundColor : helpers.getValueAtIndexOrDefault(dataset.pointHoverBackgroundColor, index, helpers.color(point._model.backgroundColor).saturate(0.5).darken(0.1).rgbString());
3247
+ point._model.borderColor = point.custom && point.custom.hoverBorderColor ? point.custom.hoverBorderColor : helpers.getValueAtIndexOrDefault(dataset.pointHoverBorderColor, index, helpers.color(point._model.borderColor).saturate(0.5).darken(0.1).rgbString());
3248
+ point._model.borderWidth = point.custom && point.custom.hoverBorderWidth ? point.custom.hoverBorderWidth : helpers.getValueAtIndexOrDefault(dataset.pointHoverBorderWidth, index, point._model.borderWidth);
3249
+ },
3250
+
3251
+ removeHoverStyle: function(point) {
3252
+ var dataset = this.chart.data.datasets[point._datasetIndex];
3253
+ var index = point._index;
3254
+
3255
+ point._model.radius = point.custom && point.custom.radius ? point.custom.radius : helpers.getValueAtIndexOrDefault(this.getDataset().radius, index, this.chart.options.elements.point.radius);
3256
+ point._model.backgroundColor = point.custom && point.custom.backgroundColor ? point.custom.backgroundColor : helpers.getValueAtIndexOrDefault(this.getDataset().pointBackgroundColor, index, this.chart.options.elements.point.backgroundColor);
3257
+ point._model.borderColor = point.custom && point.custom.borderColor ? point.custom.borderColor : helpers.getValueAtIndexOrDefault(this.getDataset().pointBorderColor, index, this.chart.options.elements.point.borderColor);
3258
+ point._model.borderWidth = point.custom && point.custom.borderWidth ? point.custom.borderWidth : helpers.getValueAtIndexOrDefault(this.getDataset().pointBorderWidth, index, this.chart.options.elements.point.borderWidth);
3259
+ }
3260
+ });
3261
+ };
3262
+ },{}],21:[function(require,module,exports){
3263
+ /*global window: false */
3264
+ "use strict";
3265
+
3266
+ module.exports = function(Chart) {
3267
+
3268
+ var helpers = Chart.helpers;
3269
+
3270
+ Chart.defaults.global.animation = {
3271
+ duration: 1000,
3272
+ easing: "easeOutQuart",
3273
+ onProgress: helpers.noop,
3274
+ onComplete: helpers.noop
3275
+ };
3276
+
3277
+ Chart.Animation = Chart.Element.extend({
3278
+ currentStep: null, // the current animation step
3279
+ numSteps: 60, // default number of steps
3280
+ easing: "", // the easing to use for this animation
3281
+ render: null, // render function used by the animation service
3282
+
3283
+ onAnimationProgress: null, // user specified callback to fire on each step of the animation
3284
+ onAnimationComplete: null // user specified callback to fire when the animation finishes
3285
+ });
3286
+
3287
+ Chart.animationService = {
3288
+ frameDuration: 17,
3289
+ animations: [],
3290
+ dropFrames: 0,
3291
+ request: null,
3292
+ addAnimation: function(chartInstance, animationObject, duration, lazy) {
3293
+
3294
+ if (!lazy) {
3295
+ chartInstance.animating = true;
3296
+ }
3297
+
3298
+ for (var index = 0; index < this.animations.length; ++index) {
3299
+ if (this.animations[index].chartInstance === chartInstance) {
3300
+ // replacing an in progress animation
3301
+ this.animations[index].animationObject = animationObject;
3302
+ return;
3303
+ }
3304
+ }
3305
+
3306
+ this.animations.push({
3307
+ chartInstance: chartInstance,
3308
+ animationObject: animationObject
3309
+ });
3310
+
3311
+ // If there are no animations queued, manually kickstart a digest, for lack of a better word
3312
+ if (this.animations.length === 1) {
3313
+ this.requestAnimationFrame();
3314
+ }
3315
+ },
3316
+ // Cancel the animation for a given chart instance
3317
+ cancelAnimation: function(chartInstance) {
3318
+ var index = helpers.findIndex(this.animations, function(animationWrapper) {
3319
+ return animationWrapper.chartInstance === chartInstance;
3320
+ });
3321
+
3322
+ if (index !== -1) {
3323
+ this.animations.splice(index, 1);
3324
+ chartInstance.animating = false;
3325
+ }
3326
+ },
3327
+ requestAnimationFrame: function() {
3328
+ var me = this;
3329
+ if (me.request === null) {
3330
+ // Skip animation frame requests until the active one is executed.
3331
+ // This can happen when processing mouse events, e.g. 'mousemove'
3332
+ // and 'mouseout' events will trigger multiple renders.
3333
+ me.request = helpers.requestAnimFrame.call(window, function() {
3334
+ me.request = null;
3335
+ me.startDigest();
3336
+ });
3337
+ }
3338
+ },
3339
+ startDigest: function() {
3340
+
3341
+ var startTime = Date.now();
3342
+ var framesToDrop = 0;
3343
+
3344
+ if (this.dropFrames > 1) {
3345
+ framesToDrop = Math.floor(this.dropFrames);
3346
+ this.dropFrames = this.dropFrames % 1;
3347
+ }
3348
+
3349
+ var i = 0;
3350
+ while (i < this.animations.length) {
3351
+ if (this.animations[i].animationObject.currentStep === null) {
3352
+ this.animations[i].animationObject.currentStep = 0;
3353
+ }
3354
+
3355
+ this.animations[i].animationObject.currentStep += 1 + framesToDrop;
3356
+
3357
+ if (this.animations[i].animationObject.currentStep > this.animations[i].animationObject.numSteps) {
3358
+ this.animations[i].animationObject.currentStep = this.animations[i].animationObject.numSteps;
3359
+ }
3360
+
3361
+ this.animations[i].animationObject.render(this.animations[i].chartInstance, this.animations[i].animationObject);
3362
+ if (this.animations[i].animationObject.onAnimationProgress && this.animations[i].animationObject.onAnimationProgress.call) {
3363
+ this.animations[i].animationObject.onAnimationProgress.call(this.animations[i].chartInstance, this.animations[i]);
3364
+ }
3365
+
3366
+ if (this.animations[i].animationObject.currentStep === this.animations[i].animationObject.numSteps) {
3367
+ if (this.animations[i].animationObject.onAnimationComplete && this.animations[i].animationObject.onAnimationComplete.call) {
3368
+ this.animations[i].animationObject.onAnimationComplete.call(this.animations[i].chartInstance, this.animations[i]);
3369
+ }
3370
+
3371
+ // executed the last frame. Remove the animation.
3372
+ this.animations[i].chartInstance.animating = false;
3373
+
3374
+ this.animations.splice(i, 1);
3375
+ } else {
3376
+ ++i;
3377
+ }
3378
+ }
3379
+
3380
+ var endTime = Date.now();
3381
+ var dropFrames = (endTime - startTime) / this.frameDuration;
3382
+
3383
+ this.dropFrames += dropFrames;
3384
+
3385
+ // Do we have more stuff to animate?
3386
+ if (this.animations.length > 0) {
3387
+ this.requestAnimationFrame();
3388
+ }
3389
+ }
3390
+ };
3391
+ };
3392
+ },{}],22:[function(require,module,exports){
3393
+ "use strict";
3394
+
3395
+ module.exports = function(Chart) {
3396
+
3397
+ var helpers = Chart.helpers;
3398
+ //Create a dictionary of chart types, to allow for extension of existing types
3399
+ Chart.types = {};
3400
+
3401
+ //Store a reference to each instance - allowing us to globally resize chart instances on window resize.
3402
+ //Destroy method on the chart will remove the instance of the chart from this reference.
3403
+ Chart.instances = {};
3404
+
3405
+ // Controllers available for dataset visualization eg. bar, line, slice, etc.
3406
+ Chart.controllers = {};
3407
+
3408
+ // The main controller of a chart
3409
+ Chart.Controller = function(instance) {
3410
+
3411
+ this.chart = instance;
3412
+ this.config = instance.config;
3413
+ this.options = this.config.options = helpers.configMerge(Chart.defaults.global, Chart.defaults[this.config.type], this.config.options || {});
3414
+ this.id = helpers.uid();
3415
+
3416
+ Object.defineProperty(this, 'data', {
3417
+ get: function() {
3418
+ return this.config.data;
3419
+ }
3420
+ });
3421
+
3422
+ //Add the chart instance to the global namespace
3423
+ Chart.instances[this.id] = this;
3424
+
3425
+ if (this.options.responsive) {
3426
+ // Silent resize before chart draws
3427
+ this.resize(true);
3428
+ }
3429
+
3430
+ this.initialize();
3431
+
3432
+ return this;
3433
+ };
3434
+
3435
+ helpers.extend(Chart.Controller.prototype, {
3436
+
3437
+ initialize: function initialize() {
3438
+
3439
+ // TODO
3440
+ // If BeforeInit(this) doesn't return false, proceed
3441
+
3442
+ this.bindEvents();
3443
+
3444
+ // Make sure controllers are built first so that each dataset is bound to an axis before the scales
3445
+ // are built
3446
+ this.ensureScalesHaveIDs();
3447
+ this.buildOrUpdateControllers();
3448
+ this.buildScales();
3449
+ this.buildSurroundingItems();
3450
+ this.updateLayout();
3451
+ this.resetElements();
3452
+ this.initToolTip();
3453
+ this.update();
3454
+
3455
+ // TODO
3456
+ // If AfterInit(this) doesn't return false, proceed
3457
+
3458
+ return this;
3459
+ },
3460
+
3461
+ clear: function clear() {
3462
+ helpers.clear(this.chart);
3463
+ return this;
3464
+ },
3465
+
3466
+ stop: function stop() {
3467
+ // Stops any current animation loop occuring
3468
+ Chart.animationService.cancelAnimation(this);
3469
+ return this;
3470
+ },
3471
+
3472
+ resize: function resize(silent) {
3473
+ var canvas = this.chart.canvas;
3474
+ var newWidth = helpers.getMaximumWidth(this.chart.canvas);
3475
+ var newHeight = (this.options.maintainAspectRatio && isNaN(this.chart.aspectRatio) === false && isFinite(this.chart.aspectRatio) && this.chart.aspectRatio !== 0) ? newWidth / this.chart.aspectRatio : helpers.getMaximumHeight(this.chart.canvas);
3476
+
3477
+ var sizeChanged = this.chart.width !== newWidth || this.chart.height !== newHeight;
3478
+
3479
+ if (!sizeChanged)
3480
+ return this;
3481
+
3482
+ canvas.width = this.chart.width = newWidth;
3483
+ canvas.height = this.chart.height = newHeight;
3484
+
3485
+ helpers.retinaScale(this.chart);
3486
+
3487
+ if (!silent) {
3488
+ this.stop();
3489
+ this.update(this.options.responsiveAnimationDuration);
3490
+ }
3491
+
3492
+ return this;
3493
+ },
3494
+ ensureScalesHaveIDs: function ensureScalesHaveIDs() {
3495
+ var defaultXAxisID = 'x-axis-';
3496
+ var defaultYAxisID = 'y-axis-';
3497
+
3498
+ if (this.options.scales) {
3499
+ if (this.options.scales.xAxes && this.options.scales.xAxes.length) {
3500
+ helpers.each(this.options.scales.xAxes, function(xAxisOptions, index) {
3501
+ xAxisOptions.id = xAxisOptions.id || (defaultXAxisID + index);
3502
+ });
3503
+ }
3504
+
3505
+ if (this.options.scales.yAxes && this.options.scales.yAxes.length) {
3506
+ // Build the y axes
3507
+ helpers.each(this.options.scales.yAxes, function(yAxisOptions, index) {
3508
+ yAxisOptions.id = yAxisOptions.id || (defaultYAxisID + index);
3509
+ });
3510
+ }
3511
+ }
3512
+ },
3513
+ buildScales: function buildScales() {
3514
+ // Map of scale ID to scale object so we can lookup later
3515
+ this.scales = {};
3516
+
3517
+ // Build the x axes
3518
+ if (this.options.scales) {
3519
+ if (this.options.scales.xAxes && this.options.scales.xAxes.length) {
3520
+ helpers.each(this.options.scales.xAxes, function(xAxisOptions, index) {
3521
+ var xType = helpers.getValueOrDefault(xAxisOptions.type, 'category');
3522
+ var ScaleClass = Chart.scaleService.getScaleConstructor(xType);
3523
+ if (ScaleClass) {
3524
+ var scale = new ScaleClass({
3525
+ ctx: this.chart.ctx,
3526
+ options: xAxisOptions,
3527
+ chart: this,
3528
+ id: xAxisOptions.id
3529
+ });
3530
+
3531
+ this.scales[scale.id] = scale;
3532
+ }
3533
+ }, this);
3534
+ }
3535
+
3536
+ if (this.options.scales.yAxes && this.options.scales.yAxes.length) {
3537
+ // Build the y axes
3538
+ helpers.each(this.options.scales.yAxes, function(yAxisOptions, index) {
3539
+ var yType = helpers.getValueOrDefault(yAxisOptions.type, 'linear');
3540
+ var ScaleClass = Chart.scaleService.getScaleConstructor(yType);
3541
+ if (ScaleClass) {
3542
+ var scale = new ScaleClass({
3543
+ ctx: this.chart.ctx,
3544
+ options: yAxisOptions,
3545
+ chart: this,
3546
+ id: yAxisOptions.id
3547
+ });
3548
+
3549
+ this.scales[scale.id] = scale;
3550
+ }
3551
+ }, this);
3552
+ }
3553
+ }
3554
+ if (this.options.scale) {
3555
+ // Build radial axes
3556
+ var ScaleClass = Chart.scaleService.getScaleConstructor(this.options.scale.type);
3557
+ if (ScaleClass) {
3558
+ var scale = new ScaleClass({
3559
+ ctx: this.chart.ctx,
3560
+ options: this.options.scale,
3561
+ chart: this
3562
+ });
3563
+
3564
+ this.scale = scale;
3565
+
3566
+ this.scales.radialScale = scale;
3567
+ }
3568
+ }
3569
+
3570
+ Chart.scaleService.addScalesToLayout(this);
3571
+ },
3572
+
3573
+ buildSurroundingItems: function() {
3574
+ if (this.options.title) {
3575
+ this.titleBlock = new Chart.Title({
3576
+ ctx: this.chart.ctx,
3577
+ options: this.options.title,
3578
+ chart: this
3579
+ });
3580
+
3581
+ Chart.layoutService.addBox(this, this.titleBlock);
3582
+ }
3583
+
3584
+ if (this.options.legend) {
3585
+ this.legend = new Chart.Legend({
3586
+ ctx: this.chart.ctx,
3587
+ options: this.options.legend,
3588
+ chart: this
3589
+ });
3590
+
3591
+ Chart.layoutService.addBox(this, this.legend);
3592
+ }
3593
+ },
3594
+
3595
+ updateLayout: function() {
3596
+ Chart.layoutService.update(this, this.chart.width, this.chart.height);
3597
+ },
3598
+
3599
+ buildOrUpdateControllers: function buildOrUpdateControllers() {
3600
+ var types = [];
3601
+ var newControllers = [];
3602
+
3603
+ helpers.each(this.data.datasets, function(dataset, datasetIndex) {
3604
+ if (!dataset.type) {
3605
+ dataset.type = this.config.type;
3606
+ }
3607
+
3608
+ var type = dataset.type;
3609
+ types.push(type);
3610
+
3611
+ if (dataset.controller) {
3612
+ dataset.controller.updateIndex(datasetIndex);
3613
+ } else {
3614
+ dataset.controller = new Chart.controllers[type](this, datasetIndex);
3615
+ newControllers.push(dataset.controller);
3616
+ }
3617
+ }, this);
3618
+
3619
+ if (types.length > 1) {
3620
+ for (var i = 1; i < types.length; i++) {
3621
+ if (types[i] !== types[i - 1]) {
3622
+ this.isCombo = true;
3623
+ break;
3624
+ }
3625
+ }
3626
+ }
3627
+
3628
+ return newControllers;
3629
+ },
3630
+
3631
+ resetElements: function resetElements() {
3632
+ helpers.each(this.data.datasets, function(dataset, datasetIndex) {
3633
+ dataset.controller.reset();
3634
+ });
3635
+ },
3636
+
3637
+ update: function update(animationDuration, lazy) {
3638
+ // In case the entire data object changed
3639
+ this.tooltip._data = this.data;
3640
+
3641
+ // Make sure dataset controllers are updated and new controllers are reset
3642
+ var newControllers = this.buildOrUpdateControllers();
3643
+
3644
+ Chart.layoutService.update(this, this.chart.width, this.chart.height);
3645
+
3646
+ // Can only reset the new controllers after the scales have been updated
3647
+ helpers.each(newControllers, function(controller) {
3648
+ controller.reset();
3649
+ });
3650
+
3651
+ // Make sure all dataset controllers have correct meta data counts
3652
+ helpers.each(this.data.datasets, function(dataset, datasetIndex) {
3653
+ dataset.controller.buildOrUpdateElements();
3654
+ });
3655
+
3656
+ // This will loop through any data and do the appropriate element update for the type
3657
+ helpers.each(this.data.datasets, function(dataset, datasetIndex) {
3658
+ dataset.controller.update();
3659
+ });
3660
+ this.render(animationDuration, lazy);
3661
+ },
3662
+
3663
+ render: function render(duration, lazy) {
3664
+
3665
+ if (this.options.animation && ((typeof duration !== 'undefined' && duration !== 0) || (typeof duration === 'undefined' && this.options.animation.duration !== 0))) {
3666
+ var animation = new Chart.Animation();
3667
+ animation.numSteps = (duration || this.options.animation.duration) / 16.66; //60 fps
3668
+ animation.easing = this.options.animation.easing;
3669
+
3670
+ // render function
3671
+ animation.render = function(chartInstance, animationObject) {
3672
+ var easingFunction = helpers.easingEffects[animationObject.easing];
3673
+ var stepDecimal = animationObject.currentStep / animationObject.numSteps;
3674
+ var easeDecimal = easingFunction(stepDecimal);
3675
+
3676
+ chartInstance.draw(easeDecimal, stepDecimal, animationObject.currentStep);
3677
+ };
3678
+
3679
+ // user events
3680
+ animation.onAnimationProgress = this.options.animation.onProgress;
3681
+ animation.onAnimationComplete = this.options.animation.onComplete;
3682
+
3683
+ Chart.animationService.addAnimation(this, animation, duration, lazy);
3684
+ } else {
3685
+ this.draw();
3686
+ if (this.options.animation && this.options.animation.onComplete && this.options.animation.onComplete.call) {
3687
+ this.options.animation.onComplete.call(this);
3688
+ }
3689
+ }
3690
+ return this;
3691
+ },
3692
+
3693
+ draw: function(ease) {
3694
+ var easingDecimal = ease || 1;
3695
+ this.clear();
3696
+
3697
+ // Draw all the scales
3698
+ helpers.each(this.boxes, function(box) {
3699
+ box.draw(this.chartArea);
3700
+ }, this);
3701
+ if (this.scale) {
3702
+ this.scale.draw();
3703
+ }
3704
+
3705
+ // Clip out the chart area so that anything outside does not draw. This is necessary for zoom and pan to function
3706
+ this.chart.ctx.save();
3707
+ this.chart.ctx.beginPath();
3708
+ this.chart.ctx.rect(this.chartArea.left, this.chartArea.top, this.chartArea.right - this.chartArea.left, this.chartArea.bottom - this.chartArea.top);
3709
+ this.chart.ctx.clip();
3710
+
3711
+ // Draw each dataset via its respective controller (reversed to support proper line stacking)
3712
+ helpers.each(this.data.datasets, function(dataset, datasetIndex) {
3713
+ if (helpers.isDatasetVisible(dataset)) {
3714
+ dataset.controller.draw(ease);
3715
+ }
3716
+ }, null, true);
3717
+
3718
+ // Restore from the clipping operation
3719
+ this.chart.ctx.restore();
3720
+
3721
+ // Finally draw the tooltip
3722
+ this.tooltip.transition(easingDecimal).draw();
3723
+ },
3724
+
3725
+ // Get the single element that was clicked on
3726
+ // @return : An object containing the dataset index and element index of the matching element. Also contains the rectangle that was draw
3727
+ getElementAtEvent: function(e) {
3728
+
3729
+ var eventPosition = helpers.getRelativePosition(e, this.chart);
3730
+ var elementsArray = [];
3731
+
3732
+ helpers.each(this.data.datasets, function(dataset, datasetIndex) {
3733
+ if (helpers.isDatasetVisible(dataset)) {
3734
+ helpers.each(dataset.metaData, function(element, index) {
3735
+ if (element.inRange(eventPosition.x, eventPosition.y)) {
3736
+ elementsArray.push(element);
3737
+ return elementsArray;
3738
+ }
3739
+ });
3740
+ }
3741
+ });
3742
+
3743
+ return elementsArray;
3744
+ },
3745
+
3746
+ getElementsAtEvent: function(e) {
3747
+ var eventPosition = helpers.getRelativePosition(e, this.chart);
3748
+ var elementsArray = [];
3749
+
3750
+ var found = (function() {
3751
+ if (this.data.datasets) {
3752
+ for (var i = 0; i < this.data.datasets.length; i++) {
3753
+ if (helpers.isDatasetVisible(this.data.datasets[i])) {
3754
+ for (var j = 0; j < this.data.datasets[i].metaData.length; j++) {
3755
+ if (this.data.datasets[i].metaData[j].inRange(eventPosition.x, eventPosition.y)) {
3756
+ return this.data.datasets[i].metaData[j];
3757
+ }
3758
+ }
3759
+ }
3760
+ }
3761
+ }
3762
+ }).call(this);
3763
+
3764
+ if (!found) {
3765
+ return elementsArray;
3766
+ }
3767
+
3768
+ helpers.each(this.data.datasets, function(dataset, dsIndex) {
3769
+ if (helpers.isDatasetVisible(dataset)) {
3770
+ elementsArray.push(dataset.metaData[found._index]);
3771
+ }
3772
+ });
3773
+
3774
+ return elementsArray;
3775
+ },
3776
+
3777
+ getDatasetAtEvent: function(e) {
3778
+ var elementsArray = this.getElementAtEvent(e);
3779
+
3780
+ if (elementsArray.length > 0) {
3781
+ elementsArray = this.data.datasets[elementsArray[0]._datasetIndex].metaData;
3782
+ }
3783
+
3784
+ return elementsArray;
3785
+ },
3786
+
3787
+ generateLegend: function generateLegend() {
3788
+ return this.options.legendCallback(this);
3789
+ },
3790
+
3791
+ destroy: function destroy() {
3792
+ this.clear();
3793
+ helpers.unbindEvents(this, this.events);
3794
+ helpers.removeResizeListener(this.chart.canvas.parentNode);
3795
+
3796
+ // Reset canvas height/width attributes
3797
+ var canvas = this.chart.canvas;
3798
+ canvas.width = this.chart.width;
3799
+ canvas.height = this.chart.height;
3800
+
3801
+ // if we scaled the canvas in response to a devicePixelRatio !== 1, we need to undo that transform here
3802
+ if (this.chart.originalDevicePixelRatio !== undefined) {
3803
+ this.chart.ctx.scale(1 / this.chart.originalDevicePixelRatio, 1 / this.chart.originalDevicePixelRatio);
3804
+ }
3805
+
3806
+ // Reset to the old style since it may have been changed by the device pixel ratio changes
3807
+ canvas.style.width = this.chart.originalCanvasStyleWidth;
3808
+ canvas.style.height = this.chart.originalCanvasStyleHeight;
3809
+
3810
+ delete Chart.instances[this.id];
3811
+ },
3812
+
3813
+ toBase64Image: function toBase64Image() {
3814
+ return this.chart.canvas.toDataURL.apply(this.chart.canvas, arguments);
3815
+ },
3816
+
3817
+ initToolTip: function initToolTip() {
3818
+ this.tooltip = new Chart.Tooltip({
3819
+ _chart: this.chart,
3820
+ _chartInstance: this,
3821
+ _data: this.data,
3822
+ _options: this.options
3823
+ }, this);
3824
+ },
3825
+
3826
+ bindEvents: function bindEvents() {
3827
+ helpers.bindEvents(this, this.options.events, function(evt) {
3828
+ this.eventHandler(evt);
3829
+ });
3830
+ },
3831
+ eventHandler: function eventHandler(e) {
3832
+ this.lastActive = this.lastActive || [];
3833
+ this.lastTooltipActive = this.lastTooltipActive || [];
3834
+
3835
+ // Find Active Elements for hover and tooltips
3836
+ if (e.type === 'mouseout') {
3837
+ this.active = [];
3838
+ this.tooltipActive = [];
3839
+ } else {
3840
+
3841
+ var _this = this;
3842
+ var getItemsForMode = function(mode) {
3843
+ switch (mode) {
3844
+ case 'single':
3845
+ return _this.getElementAtEvent(e);
3846
+ case 'label':
3847
+ return _this.getElementsAtEvent(e);
3848
+ case 'dataset':
3849
+ return _this.getDatasetAtEvent(e);
3850
+ default:
3851
+ return e;
3852
+ }
3853
+ };
3854
+
3855
+ this.active = getItemsForMode(this.options.hover.mode);
3856
+ this.tooltipActive = getItemsForMode(this.options.tooltips.mode);
3857
+ }
3858
+
3859
+ // On Hover hook
3860
+ if (this.options.hover.onHover) {
3861
+ this.options.hover.onHover.call(this, this.active);
3862
+ }
3863
+
3864
+ if (e.type === 'mouseup' || e.type === 'click') {
3865
+ if (this.options.onClick) {
3866
+ this.options.onClick.call(this, e, this.active);
3867
+ }
3868
+
3869
+ if (this.legend && this.legend.handleEvent) {
3870
+ this.legend.handleEvent(e);
3871
+ }
3872
+ }
3873
+
3874
+ var dataset;
3875
+ var index;
3876
+
3877
+ // Remove styling for last active (even if it may still be active)
3878
+ if (this.lastActive.length) {
3879
+ switch (this.options.hover.mode) {
3880
+ case 'single':
3881
+ this.data.datasets[this.lastActive[0]._datasetIndex].controller.removeHoverStyle(this.lastActive[0], this.lastActive[0]._datasetIndex, this.lastActive[0]._index);
3882
+ break;
3883
+ case 'label':
3884
+ case 'dataset':
3885
+ for (var i = 0; i < this.lastActive.length; i++) {
3886
+ if (this.lastActive[i])
3887
+ this.data.datasets[this.lastActive[i]._datasetIndex].controller.removeHoverStyle(this.lastActive[i], this.lastActive[i]._datasetIndex, this.lastActive[i]._index);
3888
+ }
3889
+ break;
3890
+ default:
3891
+ // Don't change anything
3892
+ }
3893
+ }
3894
+
3895
+ // Built in hover styling
3896
+ if (this.active.length && this.options.hover.mode) {
3897
+ switch (this.options.hover.mode) {
3898
+ case 'single':
3899
+ this.data.datasets[this.active[0]._datasetIndex].controller.setHoverStyle(this.active[0]);
3900
+ break;
3901
+ case 'label':
3902
+ case 'dataset':
3903
+ for (var j = 0; j < this.active.length; j++) {
3904
+ if (this.active[j])
3905
+ this.data.datasets[this.active[j]._datasetIndex].controller.setHoverStyle(this.active[j]);
3906
+ }
3907
+ break;
3908
+ default:
3909
+ // Don't change anything
3910
+ }
3911
+ }
3912
+
3913
+
3914
+ // Built in Tooltips
3915
+ if (this.options.tooltips.enabled || this.options.tooltips.custom) {
3916
+
3917
+ // The usual updates
3918
+ this.tooltip.initialize();
3919
+ this.tooltip._active = this.tooltipActive;
3920
+ this.tooltip.update();
3921
+ }
3922
+
3923
+ // Hover animations
3924
+ this.tooltip.pivot();
3925
+
3926
+ if (!this.animating) {
3927
+ var changed;
3928
+
3929
+ helpers.each(this.active, function(element, index) {
3930
+ if (element !== this.lastActive[index]) {
3931
+ changed = true;
3932
+ }
3933
+ }, this);
3934
+
3935
+ helpers.each(this.tooltipActive, function(element, index) {
3936
+ if (element !== this.lastTooltipActive[index]) {
3937
+ changed = true;
3938
+ }
3939
+ }, this);
3940
+
3941
+ // If entering, leaving, or changing elements, animate the change via pivot
3942
+ if ((this.lastActive.length !== this.active.length) ||
3943
+ (this.lastTooltipActive.length !== this.tooltipActive.length) ||
3944
+ changed) {
3945
+
3946
+ this.stop();
3947
+
3948
+ if (this.options.tooltips.enabled || this.options.tooltips.custom) {
3949
+ this.tooltip.update(true);
3950
+ }
3951
+
3952
+ // We only need to render at this point. Updating will cause scales to be recomputed generating flicker & using more
3953
+ // memory than necessary.
3954
+ this.render(this.options.hover.animationDuration, true);
3955
+ }
3956
+ }
3957
+
3958
+ // Remember Last Actives
3959
+ this.lastActive = this.active;
3960
+ this.lastTooltipActive = this.tooltipActive;
3961
+ return this;
3962
+ }
3963
+ });
3964
+ };
3965
+
3966
+ },{}],23:[function(require,module,exports){
3967
+ "use strict";
3968
+
3969
+ module.exports = function(Chart) {
3970
+
3971
+ var helpers = Chart.helpers;
3972
+
3973
+ // Base class for all dataset controllers (line, bar, etc)
3974
+ Chart.DatasetController = function(chart, datasetIndex) {
3975
+ this.initialize.call(this, chart, datasetIndex);
3976
+ };
3977
+
3978
+ helpers.extend(Chart.DatasetController.prototype, {
3979
+ initialize: function(chart, datasetIndex) {
3980
+ this.chart = chart;
3981
+ this.index = datasetIndex;
3982
+ this.linkScales();
3983
+ this.addElements();
3984
+ },
3985
+ updateIndex: function(datasetIndex) {
3986
+ this.index = datasetIndex;
3987
+ },
3988
+
3989
+ linkScales: function() {
3990
+ if (!this.getDataset().xAxisID) {
3991
+ this.getDataset().xAxisID = this.chart.options.scales.xAxes[0].id;
3992
+ }
3993
+
3994
+ if (!this.getDataset().yAxisID) {
3995
+ this.getDataset().yAxisID = this.chart.options.scales.yAxes[0].id;
3996
+ }
3997
+ },
3998
+
3999
+ getDataset: function() {
4000
+ return this.chart.data.datasets[this.index];
4001
+ },
4002
+
4003
+ getScaleForId: function(scaleID) {
4004
+ return this.chart.scales[scaleID];
4005
+ },
4006
+
4007
+ reset: function() {
4008
+ this.update(true);
4009
+ },
4010
+
4011
+ buildOrUpdateElements: function buildOrUpdateElements() {
4012
+ // Handle the number of data points changing
4013
+ var numData = this.getDataset().data.length;
4014
+ var numMetaData = this.getDataset().metaData.length;
4015
+
4016
+ // Make sure that we handle number of datapoints changing
4017
+ if (numData < numMetaData) {
4018
+ // Remove excess bars for data points that have been removed
4019
+ this.getDataset().metaData.splice(numData, numMetaData - numData);
4020
+ } else if (numData > numMetaData) {
4021
+ // Add new elements
4022
+ for (var index = numMetaData; index < numData; ++index) {
4023
+ this.addElementAndReset(index);
4024
+ }
4025
+ }
4026
+ },
4027
+
4028
+ // Controllers should implement the following
4029
+ addElements: helpers.noop,
4030
+ addElementAndReset: helpers.noop,
4031
+ draw: helpers.noop,
4032
+ removeHoverStyle: helpers.noop,
4033
+ setHoverStyle: helpers.noop,
4034
+ update: helpers.noop
4035
+ });
4036
+
4037
+ Chart.DatasetController.extend = helpers.inherits;
4038
+
4039
+ };
4040
+ },{}],24:[function(require,module,exports){
4041
+ "use strict";
4042
+
4043
+ module.exports = function(Chart) {
4044
+
4045
+ var helpers = Chart.helpers;
4046
+
4047
+ Chart.elements = {};
4048
+
4049
+ Chart.Element = function(configuration) {
4050
+ helpers.extend(this, configuration);
4051
+ this.initialize.apply(this, arguments);
4052
+ };
4053
+ helpers.extend(Chart.Element.prototype, {
4054
+ initialize: function() {},
4055
+ pivot: function() {
4056
+ if (!this._view) {
4057
+ this._view = helpers.clone(this._model);
4058
+ }
4059
+ this._start = helpers.clone(this._view);
4060
+ return this;
4061
+ },
4062
+ transition: function(ease) {
4063
+ if (!this._view) {
4064
+ this._view = helpers.clone(this._model);
4065
+ }
4066
+
4067
+ // No animation -> No Transition
4068
+ if (ease === 1) {
4069
+ this._view = this._model;
4070
+ this._start = null;
4071
+ return this;
4072
+ }
4073
+
4074
+ if (!this._start) {
4075
+ this.pivot();
4076
+ }
4077
+
4078
+ helpers.each(this._model, function(value, key) {
4079
+
4080
+ if (key[0] === '_' || !this._model.hasOwnProperty(key)) {
4081
+ // Only non-underscored properties
4082
+ }
4083
+
4084
+ // Init if doesn't exist
4085
+ else if (!this._view.hasOwnProperty(key)) {
4086
+ if (typeof value === 'number' && !isNaN(this._view[key])) {
4087
+ this._view[key] = value * ease;
4088
+ } else {
4089
+ this._view[key] = value;
4090
+ }
4091
+ }
4092
+
4093
+ // No unnecessary computations
4094
+ else if (value === this._view[key]) {
4095
+ // It's the same! Woohoo!
4096
+ }
4097
+
4098
+ // Color transitions if possible
4099
+ else if (typeof value === 'string') {
4100
+ try {
4101
+ var color = helpers.color(this._start[key]).mix(helpers.color(this._model[key]), ease);
4102
+ this._view[key] = color.rgbString();
4103
+ } catch (err) {
4104
+ this._view[key] = value;
4105
+ }
4106
+ }
4107
+ // Number transitions
4108
+ else if (typeof value === 'number') {
4109
+ var startVal = this._start[key] !== undefined && isNaN(this._start[key]) === false ? this._start[key] : 0;
4110
+ this._view[key] = ((this._model[key] - startVal) * ease) + startVal;
4111
+ }
4112
+ // Everything else
4113
+ else {
4114
+ this._view[key] = value;
4115
+ }
4116
+ }, this);
4117
+
4118
+ return this;
4119
+ },
4120
+ tooltipPosition: function() {
4121
+ return {
4122
+ x: this._model.x,
4123
+ y: this._model.y
4124
+ };
4125
+ },
4126
+ hasValue: function() {
4127
+ return helpers.isNumber(this._model.x) && helpers.isNumber(this._model.y);
4128
+ }
4129
+ });
4130
+
4131
+ Chart.Element.extend = helpers.inherits;
4132
+
4133
+ };
4134
+
4135
+ },{}],25:[function(require,module,exports){
4136
+ /*global window: false */
4137
+ /*global document: false */
4138
+ "use strict";
4139
+
4140
+ var color = require('chartjs-color');
4141
+
4142
+ module.exports = function(Chart) {
4143
+
4144
+ //Global Chart helpers object for utility methods and classes
4145
+ var helpers = Chart.helpers = {};
4146
+
4147
+ //-- Basic js utility methods
4148
+ helpers.each = function(loopable, callback, self, reverse) {
4149
+ // Check to see if null or undefined firstly.
4150
+ var i, len;
4151
+ if (helpers.isArray(loopable)) {
4152
+ len = loopable.length;
4153
+ if (reverse) {
4154
+ for (i = len - 1; i >= 0; i--) {
4155
+ callback.call(self, loopable[i], i);
4156
+ }
4157
+ } else {
4158
+ for (i = 0; i < len; i++) {
4159
+ callback.call(self, loopable[i], i);
4160
+ }
4161
+ }
4162
+ } else if (typeof loopable === 'object') {
4163
+ var keys = Object.keys(loopable);
4164
+ len = keys.length;
4165
+ for (i = 0; i < len; i++) {
4166
+ callback.call(self, loopable[keys[i]], keys[i]);
4167
+ }
4168
+ }
4169
+ };
4170
+ helpers.clone = function(obj) {
4171
+ var objClone = {};
4172
+ helpers.each(obj, function(value, key) {
4173
+ if (obj.hasOwnProperty(key)) {
4174
+ if (helpers.isArray(value)) {
4175
+ objClone[key] = value.slice(0);
4176
+ } else if (typeof value === 'object' && value !== null) {
4177
+ objClone[key] = helpers.clone(value);
4178
+ } else {
4179
+ objClone[key] = value;
4180
+ }
4181
+ }
4182
+ });
4183
+ return objClone;
4184
+ };
4185
+ helpers.extend = function(base) {
4186
+ var len = arguments.length;
4187
+ var additionalArgs = [];
4188
+ for (var i = 1; i < len; i++) {
4189
+ additionalArgs.push(arguments[i]);
4190
+ }
4191
+ helpers.each(additionalArgs, function(extensionObject) {
4192
+ helpers.each(extensionObject, function(value, key) {
4193
+ if (extensionObject.hasOwnProperty(key)) {
4194
+ base[key] = value;
4195
+ }
4196
+ });
4197
+ });
4198
+ return base;
4199
+ };
4200
+ // Need a special merge function to chart configs since they are now grouped
4201
+ helpers.configMerge = function(_base) {
4202
+ var base = helpers.clone(_base);
4203
+ helpers.each(Array.prototype.slice.call(arguments, 1), function(extension) {
4204
+ helpers.each(extension, function(value, key) {
4205
+ if (extension.hasOwnProperty(key)) {
4206
+ if (key === 'scales') {
4207
+ // Scale config merging is complex. Add out own function here for that
4208
+ base[key] = helpers.scaleMerge(base.hasOwnProperty(key) ? base[key] : {}, value);
4209
+
4210
+ } else if (key === 'scale') {
4211
+ // Used in polar area & radar charts since there is only one scale
4212
+ base[key] = helpers.configMerge(base.hasOwnProperty(key) ? base[key] : {}, Chart.scaleService.getScaleDefaults(value.type), value);
4213
+ } else if (base.hasOwnProperty(key) && helpers.isArray(base[key]) && helpers.isArray(value)) {
4214
+ // In this case we have an array of objects replacing another array. Rather than doing a strict replace,
4215
+ // merge. This allows easy scale option merging
4216
+ var baseArray = base[key];
4217
+
4218
+ helpers.each(value, function(valueObj, index) {
4219
+
4220
+ if (index < baseArray.length) {
4221
+ if (typeof baseArray[index] === 'object' && baseArray[index] !== null && typeof valueObj === 'object' && valueObj !== null) {
4222
+ // Two objects are coming together. Do a merge of them.
4223
+ baseArray[index] = helpers.configMerge(baseArray[index], valueObj);
4224
+ } else {
4225
+ // Just overwrite in this case since there is nothing to merge
4226
+ baseArray[index] = valueObj;
4227
+ }
4228
+ } else {
4229
+ baseArray.push(valueObj); // nothing to merge
4230
+ }
4231
+ });
4232
+
4233
+ } else if (base.hasOwnProperty(key) && typeof base[key] === "object" && base[key] !== null && typeof value === "object") {
4234
+ // If we are overwriting an object with an object, do a merge of the properties.
4235
+ base[key] = helpers.configMerge(base[key], value);
4236
+
4237
+ } else {
4238
+ // can just overwrite the value in this case
4239
+ base[key] = value;
4240
+ }
4241
+ }
4242
+ });
4243
+ });
4244
+
4245
+ return base;
4246
+ };
4247
+ helpers.extendDeep = function(_base) {
4248
+ return _extendDeep.apply(this, arguments);
4249
+
4250
+ function _extendDeep(dst) {
4251
+ helpers.each(arguments, function(obj) {
4252
+ if (obj !== dst) {
4253
+ helpers.each(obj, function(value, key) {
4254
+ if (dst[key] && dst[key].constructor && dst[key].constructor === Object) {
4255
+ _extendDeep(dst[key], value);
4256
+ } else {
4257
+ dst[key] = value;
4258
+ }
4259
+ });
4260
+ }
4261
+ });
4262
+ return dst;
4263
+ }
4264
+ };
4265
+ helpers.scaleMerge = function(_base, extension) {
4266
+ var base = helpers.clone(_base);
4267
+
4268
+ helpers.each(extension, function(value, key) {
4269
+ if (extension.hasOwnProperty(key)) {
4270
+ if (key === 'xAxes' || key === 'yAxes') {
4271
+ // These properties are arrays of items
4272
+ if (base.hasOwnProperty(key)) {
4273
+ helpers.each(value, function(valueObj, index) {
4274
+ var axisType = helpers.getValueOrDefault(valueObj.type, key === 'xAxes' ? 'category' : 'linear');
4275
+ var axisDefaults = Chart.scaleService.getScaleDefaults(axisType);
4276
+ if (index >= base[key].length || !base[key][index].type) {
4277
+ base[key].push(helpers.configMerge(axisDefaults, valueObj));
4278
+ } else if (valueObj.type && valueObj.type !== base[key][index].type) {
4279
+ // Type changed. Bring in the new defaults before we bring in valueObj so that valueObj can override the correct scale defaults
4280
+ base[key][index] = helpers.configMerge(base[key][index], axisDefaults, valueObj);
4281
+ } else {
4282
+ // Type is the same
4283
+ base[key][index] = helpers.configMerge(base[key][index], valueObj);
4284
+ }
4285
+ });
4286
+ } else {
4287
+ base[key] = [];
4288
+ helpers.each(value, function(valueObj) {
4289
+ var axisType = helpers.getValueOrDefault(valueObj.type, key === 'xAxes' ? 'category' : 'linear');
4290
+ base[key].push(helpers.configMerge(Chart.scaleService.getScaleDefaults(axisType), valueObj));
4291
+ });
4292
+ }
4293
+ } else if (base.hasOwnProperty(key) && typeof base[key] === "object" && base[key] !== null && typeof value === "object") {
4294
+ // If we are overwriting an object with an object, do a merge of the properties.
4295
+ base[key] = helpers.configMerge(base[key], value);
4296
+
4297
+ } else {
4298
+ // can just overwrite the value in this case
4299
+ base[key] = value;
4300
+ }
4301
+ }
4302
+ });
4303
+
4304
+ return base;
4305
+ };
4306
+ helpers.getValueAtIndexOrDefault = function(value, index, defaultValue) {
4307
+ if (value === undefined || value === null) {
4308
+ return defaultValue;
4309
+ }
4310
+
4311
+ if (helpers.isArray(value)) {
4312
+ return index < value.length ? value[index] : defaultValue;
4313
+ }
4314
+
4315
+ return value;
4316
+ };
4317
+ helpers.getValueOrDefault = function(value, defaultValue) {
4318
+ return value === undefined ? defaultValue : value;
4319
+ };
4320
+ helpers.indexOf = function(arrayToSearch, item) {
4321
+ if (Array.prototype.indexOf) {
4322
+ return arrayToSearch.indexOf(item);
4323
+ } else {
4324
+ for (var i = 0; i < arrayToSearch.length; i++) {
4325
+ if (arrayToSearch[i] === item)
4326
+ return i;
4327
+ }
4328
+ return -1;
4329
+ }
4330
+ };
4331
+ helpers.where = function(collection, filterCallback) {
4332
+ var filtered = [];
4333
+
4334
+ helpers.each(collection, function(item) {
4335
+ if (filterCallback(item)) {
4336
+ filtered.push(item);
4337
+ }
4338
+ });
4339
+
4340
+ return filtered;
4341
+ };
4342
+ helpers.findIndex = function(arrayToSearch, callback, thisArg) {
4343
+ var index = -1;
4344
+ if (Array.prototype.findIndex) {
4345
+ index = arrayToSearch.findIndex(callback, thisArg);
4346
+ } else {
4347
+ for (var i = 0; i < arrayToSearch.length; ++i) {
4348
+ thisArg = thisArg !== undefined ? thisArg : arrayToSearch;
4349
+
4350
+ if (callback.call(thisArg, arrayToSearch[i], i, arrayToSearch)) {
4351
+ index = i;
4352
+ break;
4353
+ }
4354
+ }
4355
+ }
4356
+
4357
+ return index;
4358
+ };
4359
+ helpers.findNextWhere = function(arrayToSearch, filterCallback, startIndex) {
4360
+ // Default to start of the array
4361
+ if (startIndex === undefined || startIndex === null) {
4362
+ startIndex = -1;
4363
+ }
4364
+ for (var i = startIndex + 1; i < arrayToSearch.length; i++) {
4365
+ var currentItem = arrayToSearch[i];
4366
+ if (filterCallback(currentItem)) {
4367
+ return currentItem;
4368
+ }
4369
+ }
4370
+ };
4371
+ helpers.findPreviousWhere = function(arrayToSearch, filterCallback, startIndex) {
4372
+ // Default to end of the array
4373
+ if (startIndex === undefined || startIndex === null) {
4374
+ startIndex = arrayToSearch.length;
4375
+ }
4376
+ for (var i = startIndex - 1; i >= 0; i--) {
4377
+ var currentItem = arrayToSearch[i];
4378
+ if (filterCallback(currentItem)) {
4379
+ return currentItem;
4380
+ }
4381
+ }
4382
+ };
4383
+ helpers.inherits = function(extensions) {
4384
+ //Basic javascript inheritance based on the model created in Backbone.js
4385
+ var parent = this;
4386
+ var ChartElement = (extensions && extensions.hasOwnProperty("constructor")) ? extensions.constructor : function() {
4387
+ return parent.apply(this, arguments);
4388
+ };
4389
+
4390
+ var Surrogate = function() {
4391
+ this.constructor = ChartElement;
4392
+ };
4393
+ Surrogate.prototype = parent.prototype;
4394
+ ChartElement.prototype = new Surrogate();
4395
+
4396
+ ChartElement.extend = helpers.inherits;
4397
+
4398
+ if (extensions) {
4399
+ helpers.extend(ChartElement.prototype, extensions);
4400
+ }
4401
+
4402
+ ChartElement.__super__ = parent.prototype;
4403
+
4404
+ return ChartElement;
4405
+ };
4406
+ helpers.noop = function() {};
4407
+ helpers.uid = (function() {
4408
+ var id = 0;
4409
+ return function() {
4410
+ return "chart-" + id++;
4411
+ };
4412
+ })();
4413
+ helpers.warn = function(str) {
4414
+ //Method for warning of errors
4415
+ if (console && typeof console.warn === "function") {
4416
+ console.warn(str);
4417
+ }
4418
+ };
4419
+ //-- Math methods
4420
+ helpers.isNumber = function(n) {
4421
+ return !isNaN(parseFloat(n)) && isFinite(n);
4422
+ };
4423
+ helpers.almostEquals = function(x, y, epsilon) {
4424
+ return Math.abs(x - y) < epsilon;
4425
+ };
4426
+ helpers.max = function(array) {
4427
+ return array.reduce(function(max, value) {
4428
+ if (!isNaN(value)) {
4429
+ return Math.max(max, value);
4430
+ } else {
4431
+ return max;
4432
+ }
4433
+ }, Number.NEGATIVE_INFINITY);
4434
+ };
4435
+ helpers.min = function(array) {
4436
+ return array.reduce(function(min, value) {
4437
+ if (!isNaN(value)) {
4438
+ return Math.min(min, value);
4439
+ } else {
4440
+ return min;
4441
+ }
4442
+ }, Number.POSITIVE_INFINITY);
4443
+ };
4444
+ helpers.sign = function(x) {
4445
+ if (Math.sign) {
4446
+ return Math.sign(x);
4447
+ } else {
4448
+ x = +x; // convert to a number
4449
+ if (x === 0 || isNaN(x)) {
4450
+ return x;
4451
+ }
4452
+ return x > 0 ? 1 : -1;
4453
+ }
4454
+ };
4455
+ helpers.log10 = function(x) {
4456
+ if (Math.log10) {
4457
+ return Math.log10(x);
4458
+ } else {
4459
+ return Math.log(x) / Math.LN10;
4460
+ }
4461
+ };
4462
+ helpers.toRadians = function(degrees) {
4463
+ return degrees * (Math.PI / 180);
4464
+ };
4465
+ helpers.toDegrees = function(radians) {
4466
+ return radians * (180 / Math.PI);
4467
+ };
4468
+ // Gets the angle from vertical upright to the point about a centre.
4469
+ helpers.getAngleFromPoint = function(centrePoint, anglePoint) {
4470
+ var distanceFromXCenter = anglePoint.x - centrePoint.x,
4471
+ distanceFromYCenter = anglePoint.y - centrePoint.y,
4472
+ radialDistanceFromCenter = Math.sqrt(distanceFromXCenter * distanceFromXCenter + distanceFromYCenter * distanceFromYCenter);
4473
+
4474
+ var angle = Math.atan2(distanceFromYCenter, distanceFromXCenter);
4475
+
4476
+ if (angle < (-0.5 * Math.PI)) {
4477
+ angle += 2.0 * Math.PI; // make sure the returned angle is in the range of (-PI/2, 3PI/2]
4478
+ }
4479
+
4480
+ return {
4481
+ angle: angle,
4482
+ distance: radialDistanceFromCenter
4483
+ };
4484
+ };
4485
+ helpers.aliasPixel = function(pixelWidth) {
4486
+ return (pixelWidth % 2 === 0) ? 0 : 0.5;
4487
+ };
4488
+ helpers.splineCurve = function(firstPoint, middlePoint, afterPoint, t) {
4489
+ //Props to Rob Spencer at scaled innovation for his post on splining between points
4490
+ //http://scaledinnovation.com/analytics/splines/aboutSplines.html
4491
+
4492
+ // This function must also respect "skipped" points
4493
+
4494
+ var previous = firstPoint.skip ? middlePoint : firstPoint,
4495
+ current = middlePoint,
4496
+ next = afterPoint.skip ? middlePoint : afterPoint;
4497
+
4498
+ var d01 = Math.sqrt(Math.pow(current.x - previous.x, 2) + Math.pow(current.y - previous.y, 2));
4499
+ var d12 = Math.sqrt(Math.pow(next.x - current.x, 2) + Math.pow(next.y - current.y, 2));
4500
+
4501
+ var s01 = d01 / (d01 + d12);
4502
+ var s12 = d12 / (d01 + d12);
4503
+
4504
+ // If all points are the same, s01 & s02 will be inf
4505
+ s01 = isNaN(s01) ? 0 : s01;
4506
+ s12 = isNaN(s12) ? 0 : s12;
4507
+
4508
+ var fa = t * s01; // scaling factor for triangle Ta
4509
+ var fb = t * s12;
4510
+
4511
+ return {
4512
+ previous: {
4513
+ x: current.x - fa * (next.x - previous.x),
4514
+ y: current.y - fa * (next.y - previous.y)
4515
+ },
4516
+ next: {
4517
+ x: current.x + fb * (next.x - previous.x),
4518
+ y: current.y + fb * (next.y - previous.y)
4519
+ }
4520
+ };
4521
+ };
4522
+ helpers.nextItem = function(collection, index, loop) {
4523
+ if (loop) {
4524
+ return index >= collection.length - 1 ? collection[0] : collection[index + 1];
4525
+ }
4526
+
4527
+ return index >= collection.length - 1 ? collection[collection.length - 1] : collection[index + 1];
4528
+ };
4529
+ helpers.previousItem = function(collection, index, loop) {
4530
+ if (loop) {
4531
+ return index <= 0 ? collection[collection.length - 1] : collection[index - 1];
4532
+ }
4533
+ return index <= 0 ? collection[0] : collection[index - 1];
4534
+ };
4535
+ // Implementation of the nice number algorithm used in determining where axis labels will go
4536
+ helpers.niceNum = function(range, round) {
4537
+ var exponent = Math.floor(helpers.log10(range));
4538
+ var fraction = range / Math.pow(10, exponent);
4539
+ var niceFraction;
4540
+
4541
+ if (round) {
4542
+ if (fraction < 1.5) {
4543
+ niceFraction = 1;
4544
+ } else if (fraction < 3) {
4545
+ niceFraction = 2;
4546
+ } else if (fraction < 7) {
4547
+ niceFraction = 5;
4548
+ } else {
4549
+ niceFraction = 10;
4550
+ }
4551
+ } else {
4552
+ if (fraction <= 1.0) {
4553
+ niceFraction = 1;
4554
+ } else if (fraction <= 2) {
4555
+ niceFraction = 2;
4556
+ } else if (fraction <= 5) {
4557
+ niceFraction = 5;
4558
+ } else {
4559
+ niceFraction = 10;
4560
+ }
4561
+ }
4562
+
4563
+ return niceFraction * Math.pow(10, exponent);
4564
+ };
4565
+ //Easing functions adapted from Robert Penner's easing equations
4566
+ //http://www.robertpenner.com/easing/
4567
+ var easingEffects = helpers.easingEffects = {
4568
+ linear: function(t) {
4569
+ return t;
4570
+ },
4571
+ easeInQuad: function(t) {
4572
+ return t * t;
4573
+ },
4574
+ easeOutQuad: function(t) {
4575
+ return -1 * t * (t - 2);
4576
+ },
4577
+ easeInOutQuad: function(t) {
4578
+ if ((t /= 1 / 2) < 1) {
4579
+ return 1 / 2 * t * t;
4580
+ }
4581
+ return -1 / 2 * ((--t) * (t - 2) - 1);
4582
+ },
4583
+ easeInCubic: function(t) {
4584
+ return t * t * t;
4585
+ },
4586
+ easeOutCubic: function(t) {
4587
+ return 1 * ((t = t / 1 - 1) * t * t + 1);
4588
+ },
4589
+ easeInOutCubic: function(t) {
4590
+ if ((t /= 1 / 2) < 1) {
4591
+ return 1 / 2 * t * t * t;
4592
+ }
4593
+ return 1 / 2 * ((t -= 2) * t * t + 2);
4594
+ },
4595
+ easeInQuart: function(t) {
4596
+ return t * t * t * t;
4597
+ },
4598
+ easeOutQuart: function(t) {
4599
+ return -1 * ((t = t / 1 - 1) * t * t * t - 1);
4600
+ },
4601
+ easeInOutQuart: function(t) {
4602
+ if ((t /= 1 / 2) < 1) {
4603
+ return 1 / 2 * t * t * t * t;
4604
+ }
4605
+ return -1 / 2 * ((t -= 2) * t * t * t - 2);
4606
+ },
4607
+ easeInQuint: function(t) {
4608
+ return 1 * (t /= 1) * t * t * t * t;
4609
+ },
4610
+ easeOutQuint: function(t) {
4611
+ return 1 * ((t = t / 1 - 1) * t * t * t * t + 1);
4612
+ },
4613
+ easeInOutQuint: function(t) {
4614
+ if ((t /= 1 / 2) < 1) {
4615
+ return 1 / 2 * t * t * t * t * t;
4616
+ }
4617
+ return 1 / 2 * ((t -= 2) * t * t * t * t + 2);
4618
+ },
4619
+ easeInSine: function(t) {
4620
+ return -1 * Math.cos(t / 1 * (Math.PI / 2)) + 1;
4621
+ },
4622
+ easeOutSine: function(t) {
4623
+ return 1 * Math.sin(t / 1 * (Math.PI / 2));
4624
+ },
4625
+ easeInOutSine: function(t) {
4626
+ return -1 / 2 * (Math.cos(Math.PI * t / 1) - 1);
4627
+ },
4628
+ easeInExpo: function(t) {
4629
+ return (t === 0) ? 1 : 1 * Math.pow(2, 10 * (t / 1 - 1));
4630
+ },
4631
+ easeOutExpo: function(t) {
4632
+ return (t === 1) ? 1 : 1 * (-Math.pow(2, -10 * t / 1) + 1);
4633
+ },
4634
+ easeInOutExpo: function(t) {
4635
+ if (t === 0) {
4636
+ return 0;
4637
+ }
4638
+ if (t === 1) {
4639
+ return 1;
4640
+ }
4641
+ if ((t /= 1 / 2) < 1) {
4642
+ return 1 / 2 * Math.pow(2, 10 * (t - 1));
4643
+ }
4644
+ return 1 / 2 * (-Math.pow(2, -10 * --t) + 2);
4645
+ },
4646
+ easeInCirc: function(t) {
4647
+ if (t >= 1) {
4648
+ return t;
4649
+ }
4650
+ return -1 * (Math.sqrt(1 - (t /= 1) * t) - 1);
4651
+ },
4652
+ easeOutCirc: function(t) {
4653
+ return 1 * Math.sqrt(1 - (t = t / 1 - 1) * t);
4654
+ },
4655
+ easeInOutCirc: function(t) {
4656
+ if ((t /= 1 / 2) < 1) {
4657
+ return -1 / 2 * (Math.sqrt(1 - t * t) - 1);
4658
+ }
4659
+ return 1 / 2 * (Math.sqrt(1 - (t -= 2) * t) + 1);
4660
+ },
4661
+ easeInElastic: function(t) {
4662
+ var s = 1.70158;
4663
+ var p = 0;
4664
+ var a = 1;
4665
+ if (t === 0) {
4666
+ return 0;
4667
+ }
4668
+ if ((t /= 1) === 1) {
4669
+ return 1;
4670
+ }
4671
+ if (!p) {
4672
+ p = 1 * 0.3;
4673
+ }
4674
+ if (a < Math.abs(1)) {
4675
+ a = 1;
4676
+ s = p / 4;
4677
+ } else {
4678
+ s = p / (2 * Math.PI) * Math.asin(1 / a);
4679
+ }
4680
+ return -(a * Math.pow(2, 10 * (t -= 1)) * Math.sin((t * 1 - s) * (2 * Math.PI) / p));
4681
+ },
4682
+ easeOutElastic: function(t) {
4683
+ var s = 1.70158;
4684
+ var p = 0;
4685
+ var a = 1;
4686
+ if (t === 0) {
4687
+ return 0;
4688
+ }
4689
+ if ((t /= 1) === 1) {
4690
+ return 1;
4691
+ }
4692
+ if (!p) {
4693
+ p = 1 * 0.3;
4694
+ }
4695
+ if (a < Math.abs(1)) {
4696
+ a = 1;
4697
+ s = p / 4;
4698
+ } else {
4699
+ s = p / (2 * Math.PI) * Math.asin(1 / a);
4700
+ }
4701
+ return a * Math.pow(2, -10 * t) * Math.sin((t * 1 - s) * (2 * Math.PI) / p) + 1;
4702
+ },
4703
+ easeInOutElastic: function(t) {
4704
+ var s = 1.70158;
4705
+ var p = 0;
4706
+ var a = 1;
4707
+ if (t === 0) {
4708
+ return 0;
4709
+ }
4710
+ if ((t /= 1 / 2) === 2) {
4711
+ return 1;
4712
+ }
4713
+ if (!p) {
4714
+ p = 1 * (0.3 * 1.5);
4715
+ }
4716
+ if (a < Math.abs(1)) {
4717
+ a = 1;
4718
+ s = p / 4;
4719
+ } else {
4720
+ s = p / (2 * Math.PI) * Math.asin(1 / a);
4721
+ }
4722
+ if (t < 1) {
4723
+ return -0.5 * (a * Math.pow(2, 10 * (t -= 1)) * Math.sin((t * 1 - s) * (2 * Math.PI) / p));
4724
+ }
4725
+ return a * Math.pow(2, -10 * (t -= 1)) * Math.sin((t * 1 - s) * (2 * Math.PI) / p) * 0.5 + 1;
4726
+ },
4727
+ easeInBack: function(t) {
4728
+ var s = 1.70158;
4729
+ return 1 * (t /= 1) * t * ((s + 1) * t - s);
4730
+ },
4731
+ easeOutBack: function(t) {
4732
+ var s = 1.70158;
4733
+ return 1 * ((t = t / 1 - 1) * t * ((s + 1) * t + s) + 1);
4734
+ },
4735
+ easeInOutBack: function(t) {
4736
+ var s = 1.70158;
4737
+ if ((t /= 1 / 2) < 1) {
4738
+ return 1 / 2 * (t * t * (((s *= (1.525)) + 1) * t - s));
4739
+ }
4740
+ return 1 / 2 * ((t -= 2) * t * (((s *= (1.525)) + 1) * t + s) + 2);
4741
+ },
4742
+ easeInBounce: function(t) {
4743
+ return 1 - easingEffects.easeOutBounce(1 - t);
4744
+ },
4745
+ easeOutBounce: function(t) {
4746
+ if ((t /= 1) < (1 / 2.75)) {
4747
+ return 1 * (7.5625 * t * t);
4748
+ } else if (t < (2 / 2.75)) {
4749
+ return 1 * (7.5625 * (t -= (1.5 / 2.75)) * t + 0.75);
4750
+ } else if (t < (2.5 / 2.75)) {
4751
+ return 1 * (7.5625 * (t -= (2.25 / 2.75)) * t + 0.9375);
4752
+ } else {
4753
+ return 1 * (7.5625 * (t -= (2.625 / 2.75)) * t + 0.984375);
4754
+ }
4755
+ },
4756
+ easeInOutBounce: function(t) {
4757
+ if (t < 1 / 2) {
4758
+ return easingEffects.easeInBounce(t * 2) * 0.5;
4759
+ }
4760
+ return easingEffects.easeOutBounce(t * 2 - 1) * 0.5 + 1 * 0.5;
4761
+ }
4762
+ };
4763
+ //Request animation polyfill - http://www.paulirish.com/2011/requestanimationframe-for-smart-animating/
4764
+ helpers.requestAnimFrame = (function() {
4765
+ return window.requestAnimationFrame ||
4766
+ window.webkitRequestAnimationFrame ||
4767
+ window.mozRequestAnimationFrame ||
4768
+ window.oRequestAnimationFrame ||
4769
+ window.msRequestAnimationFrame ||
4770
+ function(callback) {
4771
+ return window.setTimeout(callback, 1000 / 60);
4772
+ };
4773
+ })();
4774
+ helpers.cancelAnimFrame = (function() {
4775
+ return window.cancelAnimationFrame ||
4776
+ window.webkitCancelAnimationFrame ||
4777
+ window.mozCancelAnimationFrame ||
4778
+ window.oCancelAnimationFrame ||
4779
+ window.msCancelAnimationFrame ||
4780
+ function(callback) {
4781
+ return window.clearTimeout(callback, 1000 / 60);
4782
+ };
4783
+ })();
4784
+ //-- DOM methods
4785
+ helpers.getRelativePosition = function(evt, chart) {
4786
+ var mouseX, mouseY;
4787
+ var e = evt.originalEvent || evt,
4788
+ canvas = evt.currentTarget || evt.srcElement,
4789
+ boundingRect = canvas.getBoundingClientRect();
4790
+
4791
+ if (e.touches && e.touches.length > 0) {
4792
+ mouseX = e.touches[0].clientX;
4793
+ mouseY = e.touches[0].clientY;
4794
+
4795
+ } else {
4796
+ mouseX = e.clientX;
4797
+ mouseY = e.clientY;
4798
+ }
4799
+
4800
+ // Scale mouse coordinates into canvas coordinates
4801
+ // by following the pattern laid out by 'jerryj' in the comments of
4802
+ // http://www.html5canvastutorials.com/advanced/html5-canvas-mouse-coordinates/
4803
+ var paddingLeft = parseFloat(helpers.getStyle(canvas, 'padding-left'));
4804
+ var paddingTop = parseFloat(helpers.getStyle(canvas, 'padding-top'));
4805
+ var paddingRight = parseFloat(helpers.getStyle(canvas, 'padding-right'));
4806
+ var paddingBottom = parseFloat(helpers.getStyle(canvas, 'padding-bottom'));
4807
+ var width = boundingRect.right - boundingRect.left - paddingLeft - paddingRight;
4808
+ var height = boundingRect.bottom - boundingRect.top - paddingTop - paddingBottom;
4809
+
4810
+ // We divide by the current device pixel ratio, because the canvas is scaled up by that amount in each direction. However
4811
+ // the backend model is in unscaled coordinates. Since we are going to deal with our model coordinates, we go back here
4812
+ mouseX = Math.round((mouseX - boundingRect.left - paddingLeft) / (width) * canvas.width / chart.currentDevicePixelRatio);
4813
+ mouseY = Math.round((mouseY - boundingRect.top - paddingTop) / (height) * canvas.height / chart.currentDevicePixelRatio);
4814
+
4815
+ return {
4816
+ x: mouseX,
4817
+ y: mouseY
4818
+ };
4819
+
4820
+ };
4821
+ helpers.addEvent = function(node, eventType, method) {
4822
+ if (node.addEventListener) {
4823
+ node.addEventListener(eventType, method);
4824
+ } else if (node.attachEvent) {
4825
+ node.attachEvent("on" + eventType, method);
4826
+ } else {
4827
+ node["on" + eventType] = method;
4828
+ }
4829
+ };
4830
+ helpers.removeEvent = function(node, eventType, handler) {
4831
+ if (node.removeEventListener) {
4832
+ node.removeEventListener(eventType, handler, false);
4833
+ } else if (node.detachEvent) {
4834
+ node.detachEvent("on" + eventType, handler);
4835
+ } else {
4836
+ node["on" + eventType] = helpers.noop;
4837
+ }
4838
+ };
4839
+ helpers.bindEvents = function(chartInstance, arrayOfEvents, handler) {
4840
+ // Create the events object if it's not already present
4841
+ if (!chartInstance.events)
4842
+ chartInstance.events = {};
4843
+
4844
+ helpers.each(arrayOfEvents, function(eventName) {
4845
+ chartInstance.events[eventName] = function() {
4846
+ handler.apply(chartInstance, arguments);
4847
+ };
4848
+ helpers.addEvent(chartInstance.chart.canvas, eventName, chartInstance.events[eventName]);
4849
+ });
4850
+ };
4851
+ helpers.unbindEvents = function(chartInstance, arrayOfEvents) {
4852
+ helpers.each(arrayOfEvents, function(handler, eventName) {
4853
+ helpers.removeEvent(chartInstance.chart.canvas, eventName, handler);
4854
+ });
4855
+ };
4856
+
4857
+ // Private helper function to convert max-width/max-height values that may be percentages into a number
4858
+ function parseMaxStyle(styleValue, node, parentProperty) {
4859
+ var valueInPixels;
4860
+ if (typeof(styleValue) === 'string') {
4861
+ valueInPixels = parseInt(styleValue, 10);
4862
+
4863
+ if (styleValue.indexOf('%') != -1) {
4864
+ // percentage * size in dimension
4865
+ valueInPixels = valueInPixels / 100 * node.parentNode[parentProperty];
4866
+ }
4867
+ } else {
4868
+ valueInPixels = styleValue;
4869
+ }
4870
+
4871
+ return valueInPixels;
4872
+ }
4873
+
4874
+ // Private helper to get a constraint dimension
4875
+ // @param domNode : the node to check the constraint on
4876
+ // @param maxStyle : the style that defines the maximum for the direction we are using (max-width / max-height)
4877
+ // @param percentageProperty : property of parent to use when calculating width as a percentage
4878
+ function getConstraintDimension(domNode, maxStyle, percentageProperty) {
4879
+ var constrainedDimension;
4880
+ var constrainedNode = document.defaultView.getComputedStyle(domNode)[maxStyle];
4881
+ var constrainedContainer = document.defaultView.getComputedStyle(domNode.parentNode)[maxStyle];
4882
+ var hasCNode = constrainedNode !== null && constrainedNode !== "none";
4883
+ var hasCContainer = constrainedContainer !== null && constrainedContainer !== "none";
4884
+
4885
+ if (hasCNode || hasCContainer) {
4886
+ constrainedDimension = Math.min((hasCNode ? parseMaxStyle(constrainedNode, domNode, percentageProperty) : Number.POSITIVE_INFINITY), (hasCContainer ? parseMaxStyle(constrainedContainer, domNode.parentNode, percentageProperty) : Number.POSITIVE_INFINITY));
4887
+ }
4888
+ return constrainedDimension;
4889
+ }
4890
+ // returns Number or undefined if no constraint
4891
+ helpers.getConstraintWidth = function(domNode) {
4892
+ return getConstraintDimension(domNode, 'max-width', 'clientWidth');
4893
+ };
4894
+ // returns Number or undefined if no constraint
4895
+ helpers.getConstraintHeight = function(domNode) {
4896
+ return getConstraintDimension(domNode, 'max-height', 'clientHeight');
4897
+ };
4898
+ helpers.getMaximumWidth = function(domNode) {
4899
+ var container = domNode.parentNode;
4900
+ var padding = parseInt(helpers.getStyle(container, 'padding-left')) + parseInt(helpers.getStyle(container, 'padding-right'));
4901
+
4902
+ var w = container.clientWidth - padding;
4903
+ var cw = helpers.getConstraintWidth(domNode);
4904
+ if (cw !== undefined) {
4905
+ w = Math.min(w, cw);
4906
+ }
4907
+
4908
+ return w;
4909
+ };
4910
+ helpers.getMaximumHeight = function(domNode) {
4911
+ var container = domNode.parentNode;
4912
+ var padding = parseInt(helpers.getStyle(container, 'padding-top')) + parseInt(helpers.getStyle(container, 'padding-bottom'));
4913
+
4914
+ var h = container.clientHeight - padding;
4915
+ var ch = helpers.getConstraintHeight(domNode);
4916
+ if (ch !== undefined) {
4917
+ h = Math.min(h, ch);
4918
+ }
4919
+
4920
+ return h;
4921
+ };
4922
+ helpers.getStyle = function(el, property) {
4923
+ return el.currentStyle ?
4924
+ el.currentStyle[property] :
4925
+ document.defaultView.getComputedStyle(el, null).getPropertyValue(property);
4926
+ };
4927
+ helpers.retinaScale = function(chart) {
4928
+ var ctx = chart.ctx;
4929
+ var width = chart.canvas.width;
4930
+ var height = chart.canvas.height;
4931
+ var pixelRatio = chart.currentDevicePixelRatio = window.devicePixelRatio || 1;
4932
+
4933
+ if (pixelRatio !== 1) {
4934
+ ctx.canvas.height = height * pixelRatio;
4935
+ ctx.canvas.width = width * pixelRatio;
4936
+ ctx.scale(pixelRatio, pixelRatio);
4937
+
4938
+ // Store the device pixel ratio so that we can go backwards in `destroy`.
4939
+ // The devicePixelRatio changes with zoom, so there are no guarantees that it is the same
4940
+ // when destroy is called
4941
+ chart.originalDevicePixelRatio = chart.originalDevicePixelRatio || pixelRatio;
4942
+ }
4943
+
4944
+ ctx.canvas.style.width = width + 'px';
4945
+ ctx.canvas.style.height = height + 'px';
4946
+ };
4947
+ //-- Canvas methods
4948
+ helpers.clear = function(chart) {
4949
+ chart.ctx.clearRect(0, 0, chart.width, chart.height);
4950
+ };
4951
+ helpers.fontString = function(pixelSize, fontStyle, fontFamily) {
4952
+ return fontStyle + " " + pixelSize + "px " + fontFamily;
4953
+ };
4954
+ helpers.longestText = function(ctx, font, arrayOfStrings, cache) {
4955
+ cache = cache || {};
4956
+ cache.data = cache.data || {};
4957
+ cache.garbageCollect = cache.garbageCollect || [];
4958
+
4959
+ if (cache.font !== font) {
4960
+ cache.data = {};
4961
+ cache.garbageCollect = [];
4962
+ cache.font = font;
4963
+ }
4964
+
4965
+ ctx.font = font;
4966
+ var longest = 0;
4967
+ helpers.each(arrayOfStrings, function(string) {
4968
+ // Undefined strings should not be measured
4969
+ if (string !== undefined && string !== null) {
4970
+ var textWidth = cache.data[string];
4971
+ if (!textWidth) {
4972
+ textWidth = cache.data[string] = ctx.measureText(string).width;
4973
+ cache.garbageCollect.push(string);
4974
+ }
4975
+
4976
+ if (textWidth > longest) {
4977
+ longest = textWidth;
4978
+ }
4979
+ }
4980
+ });
4981
+
4982
+ var gcLen = cache.garbageCollect.length / 2;
4983
+ if (gcLen > arrayOfStrings.length) {
4984
+ for (var i = 0; i < gcLen; i++) {
4985
+ delete cache.data[cache.garbageCollect[i]];
4986
+ }
4987
+ cache.garbageCollect.splice(0, gcLen);
4988
+ }
4989
+
4990
+ return longest;
4991
+ };
4992
+ helpers.drawRoundedRectangle = function(ctx, x, y, width, height, radius) {
4993
+ ctx.beginPath();
4994
+ ctx.moveTo(x + radius, y);
4995
+ ctx.lineTo(x + width - radius, y);
4996
+ ctx.quadraticCurveTo(x + width, y, x + width, y + radius);
4997
+ ctx.lineTo(x + width, y + height - radius);
4998
+ ctx.quadraticCurveTo(x + width, y + height, x + width - radius, y + height);
4999
+ ctx.lineTo(x + radius, y + height);
5000
+ ctx.quadraticCurveTo(x, y + height, x, y + height - radius);
5001
+ ctx.lineTo(x, y + radius);
5002
+ ctx.quadraticCurveTo(x, y, x + radius, y);
5003
+ ctx.closePath();
5004
+ };
5005
+ helpers.color = function(c) {
5006
+ if (!color) {
5007
+ console.log('Color.js not found!');
5008
+ return c;
5009
+ }
5010
+
5011
+ /* global CanvasGradient */
5012
+ if (c instanceof CanvasGradient) {
5013
+ return color(Chart.defaults.global.defaultColor);
5014
+ }
5015
+
5016
+ return color(c);
5017
+ };
5018
+ helpers.addResizeListener = function(node, callback) {
5019
+ // Hide an iframe before the node
5020
+ var hiddenIframe = document.createElement('iframe');
5021
+ var hiddenIframeClass = 'chartjs-hidden-iframe';
5022
+
5023
+ if (hiddenIframe.classlist) {
5024
+ // can use classlist
5025
+ hiddenIframe.classlist.add(hiddenIframeClass);
5026
+ } else {
5027
+ hiddenIframe.setAttribute('class', hiddenIframeClass);
5028
+ }
5029
+
5030
+ // Set the style
5031
+ hiddenIframe.style.width = '100%';
5032
+ hiddenIframe.style.display = 'block';
5033
+ hiddenIframe.style.border = 0;
5034
+ hiddenIframe.style.height = 0;
5035
+ hiddenIframe.style.margin = 0;
5036
+ hiddenIframe.style.position = 'absolute';
5037
+ hiddenIframe.style.left = 0;
5038
+ hiddenIframe.style.right = 0;
5039
+ hiddenIframe.style.top = 0;
5040
+ hiddenIframe.style.bottom = 0;
5041
+
5042
+ // Insert the iframe so that contentWindow is available
5043
+ node.insertBefore(hiddenIframe, node.firstChild);
5044
+
5045
+ (hiddenIframe.contentWindow || hiddenIframe).onresize = function() {
5046
+ if (callback) {
5047
+ callback();
5048
+ }
5049
+ };
5050
+ };
5051
+ helpers.removeResizeListener = function(node) {
5052
+ var hiddenIframe = node.querySelector('.chartjs-hidden-iframe');
5053
+
5054
+ // Remove the resize detect iframe
5055
+ if (hiddenIframe) {
5056
+ hiddenIframe.parentNode.removeChild(hiddenIframe);
5057
+ }
5058
+ };
5059
+ helpers.isArray = function(obj) {
5060
+ if (!Array.isArray) {
5061
+ return Object.prototype.toString.call(obj) === '[object Array]';
5062
+ }
5063
+ return Array.isArray(obj);
5064
+ };
5065
+ helpers.pushAllIfDefined = function(element, array) {
5066
+ if (typeof element === "undefined") {
5067
+ return;
5068
+ }
5069
+
5070
+ if (helpers.isArray(element)) {
5071
+ array.push.apply(array, element);
5072
+ } else {
5073
+ array.push(element);
5074
+ }
5075
+ };
5076
+ helpers.isDatasetVisible = function(dataset) {
5077
+ return !dataset.hidden;
5078
+ };
5079
+ helpers.callCallback = function(fn, args, _tArg) {
5080
+ if (fn && typeof fn.call === 'function') {
5081
+ fn.apply(_tArg, args);
5082
+ }
5083
+ };
5084
+
5085
+ };
5086
+
5087
+ },{"chartjs-color":6}],26:[function(require,module,exports){
5088
+ "use strict";
5089
+
5090
+ module.exports = function() {
5091
+
5092
+ //Occupy the global variable of Chart, and create a simple base class
5093
+ var Chart = function(context, config) {
5094
+ this.config = config;
5095
+
5096
+ // Support a jQuery'd canvas element
5097
+ if (context.length && context[0].getContext) {
5098
+ context = context[0];
5099
+ }
5100
+
5101
+ // Support a canvas domnode
5102
+ if (context.getContext) {
5103
+ context = context.getContext("2d");
5104
+ }
5105
+
5106
+ this.ctx = context;
5107
+ this.canvas = context.canvas;
5108
+
5109
+ // Figure out what the size of the chart will be.
5110
+ // If the canvas has a specified width and height, we use those else
5111
+ // we look to see if the canvas node has a CSS width and height.
5112
+ // If there is still no height, fill the parent container
5113
+ this.width = context.canvas.width || parseInt(Chart.helpers.getStyle(context.canvas, 'width')) || Chart.helpers.getMaximumWidth(context.canvas);
5114
+ this.height = context.canvas.height || parseInt(Chart.helpers.getStyle(context.canvas, 'height')) || Chart.helpers.getMaximumHeight(context.canvas);
5115
+
5116
+ this.aspectRatio = this.width / this.height;
5117
+
5118
+ if (isNaN(this.aspectRatio) || isFinite(this.aspectRatio) === false) {
5119
+ // If the canvas has no size, try and figure out what the aspect ratio will be.
5120
+ // Some charts prefer square canvases (pie, radar, etc). If that is specified, use that
5121
+ // else use the canvas default ratio of 2
5122
+ this.aspectRatio = config.aspectRatio !== undefined ? config.aspectRatio : 2;
5123
+ }
5124
+
5125
+ // Store the original style of the element so we can set it back
5126
+ this.originalCanvasStyleWidth = context.canvas.style.width;
5127
+ this.originalCanvasStyleHeight = context.canvas.style.height;
5128
+
5129
+ // High pixel density displays - multiply the size of the canvas height/width by the device pixel ratio, then scale.
5130
+ Chart.helpers.retinaScale(this);
5131
+
5132
+ if (config) {
5133
+ this.controller = new Chart.Controller(this);
5134
+ }
5135
+
5136
+ // Always bind this so that if the responsive state changes we still work
5137
+ var _this = this;
5138
+ Chart.helpers.addResizeListener(context.canvas.parentNode, function() {
5139
+ if (_this.controller && _this.controller.config.options.responsive) {
5140
+ _this.controller.resize();
5141
+ }
5142
+ });
5143
+
5144
+ return this.controller ? this.controller : this;
5145
+
5146
+ };
5147
+
5148
+ //Globally expose the defaults to allow for user updating/changing
5149
+ Chart.defaults = {
5150
+ global: {
5151
+ responsive: true,
5152
+ responsiveAnimationDuration: 0,
5153
+ maintainAspectRatio: true,
5154
+ events: ["mousemove", "mouseout", "click", "touchstart", "touchmove"],
5155
+ hover: {
5156
+ onHover: null,
5157
+ mode: 'single',
5158
+ animationDuration: 400
5159
+ },
5160
+ onClick: null,
5161
+ defaultColor: 'rgba(0,0,0,0.1)',
5162
+ defaultFontColor: '#666',
5163
+ defaultFontFamily: "'Helvetica Neue', 'Helvetica', 'Arial', sans-serif",
5164
+ defaultFontSize: 12,
5165
+ defaultFontStyle: 'normal',
5166
+ showLines: true,
5167
+
5168
+ // Element defaults defined in element extensions
5169
+ elements: {},
5170
+
5171
+ // Legend callback string
5172
+ legendCallback: function(chart) {
5173
+ var text = [];
5174
+ text.push('<ul class="' + chart.id + '-legend">');
5175
+ for (var i = 0; i < chart.data.datasets.length; i++) {
5176
+ text.push('<li><span style="background-color:' + chart.data.datasets[i].backgroundColor + '"></span>');
5177
+ if (chart.data.datasets[i].label) {
5178
+ text.push(chart.data.datasets[i].label);
5179
+ }
5180
+ text.push('</li>');
5181
+ }
5182
+ text.push('</ul>');
5183
+
5184
+ return text.join("");
5185
+ }
5186
+ }
5187
+ };
5188
+
5189
+ return Chart;
5190
+
5191
+ };
5192
+
5193
+ },{}],27:[function(require,module,exports){
5194
+ "use strict";
5195
+
5196
+ module.exports = function(Chart) {
5197
+
5198
+ var helpers = Chart.helpers;
5199
+
5200
+ // The layout service is very self explanatory. It's responsible for the layout within a chart.
5201
+ // Scales, Legends and Plugins all rely on the layout service and can easily register to be placed anywhere they need
5202
+ // It is this service's responsibility of carrying out that layout.
5203
+ Chart.layoutService = {
5204
+ defaults: {},
5205
+
5206
+ // Register a box to a chartInstance. A box is simply a reference to an object that requires layout. eg. Scales, Legend, Plugins.
5207
+ addBox: function(chartInstance, box) {
5208
+ if (!chartInstance.boxes) {
5209
+ chartInstance.boxes = [];
5210
+ }
5211
+ chartInstance.boxes.push(box);
5212
+ },
5213
+
5214
+ removeBox: function(chartInstance, box) {
5215
+ if (!chartInstance.boxes) {
5216
+ return;
5217
+ }
5218
+ chartInstance.boxes.splice(chartInstance.boxes.indexOf(box), 1);
5219
+ },
5220
+
5221
+ // The most important function
5222
+ update: function(chartInstance, width, height) {
5223
+
5224
+ if (!chartInstance) {
5225
+ return;
5226
+ }
5227
+
5228
+ var xPadding = 0;
5229
+ var yPadding = 0;
5230
+
5231
+ var leftBoxes = helpers.where(chartInstance.boxes, function(box) {
5232
+ return box.options.position === "left";
5233
+ });
5234
+ var rightBoxes = helpers.where(chartInstance.boxes, function(box) {
5235
+ return box.options.position === "right";
5236
+ });
5237
+ var topBoxes = helpers.where(chartInstance.boxes, function(box) {
5238
+ return box.options.position === "top";
5239
+ });
5240
+ var bottomBoxes = helpers.where(chartInstance.boxes, function(box) {
5241
+ return box.options.position === "bottom";
5242
+ });
5243
+
5244
+ // Boxes that overlay the chartarea such as the radialLinear scale
5245
+ var chartAreaBoxes = helpers.where(chartInstance.boxes, function(box) {
5246
+ return box.options.position === "chartArea";
5247
+ });
5248
+
5249
+ function fullWidthSorter(a, b) {
5250
+
5251
+ }
5252
+
5253
+ // Ensure that full width boxes are at the very top / bottom
5254
+ topBoxes.sort(function(a, b) {
5255
+ return (b.options.fullWidth ? 1 : 0) - (a.options.fullWidth ? 1 : 0);
5256
+ });
5257
+ bottomBoxes.sort(function(a, b) {
5258
+ return (a.options.fullWidth ? 1 : 0) - (b.options.fullWidth ? 1 : 0);
5259
+ });
5260
+
5261
+ // Essentially we now have any number of boxes on each of the 4 sides.
5262
+ // Our canvas looks like the following.
5263
+ // The areas L1 and L2 are the left axes. R1 is the right axis, T1 is the top axis and
5264
+ // B1 is the bottom axis
5265
+ // There are also 4 quadrant-like locations (left to right instead of clockwise) reserved for chart overlays
5266
+ // These locations are single-box locations only, when trying to register a chartArea location that is already taken,
5267
+ // an error will be thrown.
5268
+ //
5269
+ // |----------------------------------------------------|
5270
+ // | T1 (Full Width) |
5271
+ // |----------------------------------------------------|
5272
+ // | | | T2 | |
5273
+ // | |----|-------------------------------------|----|
5274
+ // | | | C1 | | C2 | |
5275
+ // | | |----| |----| |
5276
+ // | | | | |
5277
+ // | L1 | L2 | ChartArea (C0) | R1 |
5278
+ // | | | | |
5279
+ // | | |----| |----| |
5280
+ // | | | C3 | | C4 | |
5281
+ // | |----|-------------------------------------|----|
5282
+ // | | | B1 | |
5283
+ // |----------------------------------------------------|
5284
+ // | B2 (Full Width) |
5285
+ // |----------------------------------------------------|
5286
+ //
5287
+ // What we do to find the best sizing, we do the following
5288
+ // 1. Determine the minimum size of the chart area.
5289
+ // 2. Split the remaining width equally between each vertical axis
5290
+ // 3. Split the remaining height equally between each horizontal axis
5291
+ // 4. Give each layout the maximum size it can be. The layout will return it's minimum size
5292
+ // 5. Adjust the sizes of each axis based on it's minimum reported size.
5293
+ // 6. Refit each axis
5294
+ // 7. Position each axis in the final location
5295
+ // 8. Tell the chart the final location of the chart area
5296
+ // 9. Tell any axes that overlay the chart area the positions of the chart area
5297
+
5298
+ // Step 1
5299
+ var chartWidth = width - (2 * xPadding);
5300
+ var chartHeight = height - (2 * yPadding);
5301
+ var chartAreaWidth = chartWidth / 2; // min 50%
5302
+ var chartAreaHeight = chartHeight / 2; // min 50%
5303
+
5304
+ // Step 2
5305
+ var verticalBoxWidth = (width - chartAreaWidth) / (leftBoxes.length + rightBoxes.length);
5306
+
5307
+ // Step 3
5308
+ var horizontalBoxHeight = (height - chartAreaHeight) / (topBoxes.length + bottomBoxes.length);
5309
+
5310
+ // Step 4
5311
+ var maxChartAreaWidth = chartWidth;
5312
+ var maxChartAreaHeight = chartHeight;
5313
+ var minBoxSizes = [];
5314
+
5315
+ helpers.each(leftBoxes.concat(rightBoxes, topBoxes, bottomBoxes), getMinimumBoxSize);
5316
+
5317
+ function getMinimumBoxSize(box) {
5318
+ var minSize;
5319
+ var isHorizontal = box.isHorizontal();
5320
+
5321
+ if (isHorizontal) {
5322
+ minSize = box.update(box.options.fullWidth ? chartWidth : maxChartAreaWidth, horizontalBoxHeight);
5323
+ maxChartAreaHeight -= minSize.height;
5324
+ } else {
5325
+ minSize = box.update(verticalBoxWidth, chartAreaHeight);
5326
+ maxChartAreaWidth -= minSize.width;
5327
+ }
5328
+
5329
+ minBoxSizes.push({
5330
+ horizontal: isHorizontal,
5331
+ minSize: minSize,
5332
+ box: box
5333
+ });
5334
+ }
5335
+
5336
+ // At this point, maxChartAreaHeight and maxChartAreaWidth are the size the chart area could
5337
+ // be if the axes are drawn at their minimum sizes.
5338
+
5339
+ // Steps 5 & 6
5340
+ var totalLeftBoxesWidth = xPadding;
5341
+ var totalRightBoxesWidth = xPadding;
5342
+ var totalTopBoxesHeight = yPadding;
5343
+ var totalBottomBoxesHeight = yPadding;
5344
+
5345
+ // Update, and calculate the left and right margins for the horizontal boxes
5346
+ helpers.each(leftBoxes.concat(rightBoxes), fitBox);
5347
+
5348
+ helpers.each(leftBoxes, function(box) {
5349
+ totalLeftBoxesWidth += box.width;
5350
+ });
5351
+
5352
+ helpers.each(rightBoxes, function(box) {
5353
+ totalRightBoxesWidth += box.width;
5354
+ });
5355
+
5356
+ // Set the Left and Right margins for the horizontal boxes
5357
+ helpers.each(topBoxes.concat(bottomBoxes), fitBox);
5358
+
5359
+ // Function to fit a box
5360
+ function fitBox(box) {
5361
+ var minBoxSize = helpers.findNextWhere(minBoxSizes, function(minBoxSize) {
5362
+ return minBoxSize.box === box;
5363
+ });
5364
+
5365
+ if (minBoxSize) {
5366
+ if (box.isHorizontal()) {
5367
+ var scaleMargin = {
5368
+ left: totalLeftBoxesWidth,
5369
+ right: totalRightBoxesWidth,
5370
+ top: 0,
5371
+ bottom: 0
5372
+ };
5373
+
5374
+ // Don't use min size here because of label rotation. When the labels are rotated, their rotation highly depends
5375
+ // on the margin. Sometimes they need to increase in size slightly
5376
+ box.update(box.options.fullWidth ? chartWidth : maxChartAreaWidth, chartHeight / 2, scaleMargin);
5377
+ } else {
5378
+ box.update(minBoxSize.minSize.width, maxChartAreaHeight);
5379
+ }
5380
+ }
5381
+ }
5382
+
5383
+ // Figure out how much margin is on the top and bottom of the vertical boxes
5384
+ helpers.each(topBoxes, function(box) {
5385
+ totalTopBoxesHeight += box.height;
5386
+ });
5387
+
5388
+ helpers.each(bottomBoxes, function(box) {
5389
+ totalBottomBoxesHeight += box.height;
5390
+ });
5391
+
5392
+ // Let the left layout know the final margin
5393
+ helpers.each(leftBoxes.concat(rightBoxes), finalFitVerticalBox);
5394
+
5395
+ function finalFitVerticalBox(box) {
5396
+ var minBoxSize = helpers.findNextWhere(minBoxSizes, function(minBoxSize) {
5397
+ return minBoxSize.box === box;
5398
+ });
5399
+
5400
+ var scaleMargin = {
5401
+ left: 0,
5402
+ right: 0,
5403
+ top: totalTopBoxesHeight,
5404
+ bottom: totalBottomBoxesHeight
5405
+ };
5406
+
5407
+ if (minBoxSize) {
5408
+ box.update(minBoxSize.minSize.width, maxChartAreaHeight, scaleMargin);
5409
+ }
5410
+ }
5411
+
5412
+ // Recalculate because the size of each layout might have changed slightly due to the margins (label rotation for instance)
5413
+ totalLeftBoxesWidth = xPadding;
5414
+ totalRightBoxesWidth = xPadding;
5415
+ totalTopBoxesHeight = yPadding;
5416
+ totalBottomBoxesHeight = yPadding;
5417
+
5418
+ helpers.each(leftBoxes, function(box) {
5419
+ totalLeftBoxesWidth += box.width;
5420
+ });
5421
+
5422
+ helpers.each(rightBoxes, function(box) {
5423
+ totalRightBoxesWidth += box.width;
5424
+ });
5425
+
5426
+ helpers.each(topBoxes, function(box) {
5427
+ totalTopBoxesHeight += box.height;
5428
+ });
5429
+ helpers.each(bottomBoxes, function(box) {
5430
+ totalBottomBoxesHeight += box.height;
5431
+ });
5432
+
5433
+ // Figure out if our chart area changed. This would occur if the dataset layout label rotation
5434
+ // changed due to the application of the margins in step 6. Since we can only get bigger, this is safe to do
5435
+ // without calling `fit` again
5436
+ var newMaxChartAreaHeight = height - totalTopBoxesHeight - totalBottomBoxesHeight;
5437
+ var newMaxChartAreaWidth = width - totalLeftBoxesWidth - totalRightBoxesWidth;
5438
+
5439
+ if (newMaxChartAreaWidth !== maxChartAreaWidth || newMaxChartAreaHeight !== maxChartAreaHeight) {
5440
+ helpers.each(leftBoxes, function(box) {
5441
+ box.height = newMaxChartAreaHeight;
5442
+ });
5443
+
5444
+ helpers.each(rightBoxes, function(box) {
5445
+ box.height = newMaxChartAreaHeight;
5446
+ });
5447
+
5448
+ helpers.each(topBoxes, function(box) {
5449
+ box.width = newMaxChartAreaWidth;
5450
+ });
5451
+
5452
+ helpers.each(bottomBoxes, function(box) {
5453
+ box.width = newMaxChartAreaWidth;
5454
+ });
5455
+
5456
+ maxChartAreaHeight = newMaxChartAreaHeight;
5457
+ maxChartAreaWidth = newMaxChartAreaWidth;
5458
+ }
5459
+
5460
+ // Step 7 - Position the boxes
5461
+ var left = xPadding;
5462
+ var top = yPadding;
5463
+ var right = 0;
5464
+ var bottom = 0;
5465
+
5466
+ helpers.each(leftBoxes.concat(topBoxes), placeBox);
5467
+
5468
+ // Account for chart width and height
5469
+ left += maxChartAreaWidth;
5470
+ top += maxChartAreaHeight;
5471
+
5472
+ helpers.each(rightBoxes, placeBox);
5473
+ helpers.each(bottomBoxes, placeBox);
5474
+
5475
+ function placeBox(box) {
5476
+ if (box.isHorizontal()) {
5477
+ box.left = box.options.fullWidth ? xPadding : totalLeftBoxesWidth;
5478
+ box.right = box.options.fullWidth ? width - xPadding : totalLeftBoxesWidth + maxChartAreaWidth;
5479
+ box.top = top;
5480
+ box.bottom = top + box.height;
5481
+
5482
+ // Move to next point
5483
+ top = box.bottom;
5484
+
5485
+ } else {
5486
+
5487
+ box.left = left;
5488
+ box.right = left + box.width;
5489
+ box.top = totalTopBoxesHeight;
5490
+ box.bottom = totalTopBoxesHeight + maxChartAreaHeight;
5491
+
5492
+ // Move to next point
5493
+ left = box.right;
5494
+ }
5495
+ }
5496
+
5497
+ // Step 8
5498
+ chartInstance.chartArea = {
5499
+ left: totalLeftBoxesWidth,
5500
+ top: totalTopBoxesHeight,
5501
+ right: totalLeftBoxesWidth + maxChartAreaWidth,
5502
+ bottom: totalTopBoxesHeight + maxChartAreaHeight
5503
+ };
5504
+
5505
+ // Step 9
5506
+ helpers.each(chartAreaBoxes, function(box) {
5507
+ box.left = chartInstance.chartArea.left;
5508
+ box.top = chartInstance.chartArea.top;
5509
+ box.right = chartInstance.chartArea.right;
5510
+ box.bottom = chartInstance.chartArea.bottom;
5511
+
5512
+ box.update(maxChartAreaWidth, maxChartAreaHeight);
5513
+ });
5514
+ }
5515
+ };
5516
+ };
5517
+
5518
+ },{}],28:[function(require,module,exports){
5519
+ "use strict";
5520
+
5521
+ module.exports = function(Chart) {
5522
+
5523
+ var helpers = Chart.helpers;
5524
+
5525
+ Chart.defaults.global.legend = {
5526
+
5527
+ display: true,
5528
+ position: 'top',
5529
+ fullWidth: true, // marks that this box should take the full width of the canvas (pushing down other boxes)
5530
+ reverse: false,
5531
+
5532
+ // a callback that will handle
5533
+ onClick: function(e, legendItem) {
5534
+ var dataset = this.chart.data.datasets[legendItem.datasetIndex];
5535
+ dataset.hidden = !dataset.hidden;
5536
+
5537
+ // We hid a dataset ... rerender the chart
5538
+ this.chart.update();
5539
+ },
5540
+
5541
+ labels: {
5542
+ boxWidth: 40,
5543
+ padding: 10,
5544
+ // Generates labels shown in the legend
5545
+ // Valid properties to return:
5546
+ // text : text to display
5547
+ // fillStyle : fill of coloured box
5548
+ // strokeStyle: stroke of coloured box
5549
+ // hidden : if this legend item refers to a hidden item
5550
+ // lineCap : cap style for line
5551
+ // lineDash
5552
+ // lineDashOffset :
5553
+ // lineJoin :
5554
+ // lineWidth :
5555
+ generateLabels: function(data) {
5556
+ return helpers.isArray(data.datasets) ? data.datasets.map(function(dataset, i) {
5557
+ return {
5558
+ text: dataset.label,
5559
+ fillStyle: dataset.backgroundColor,
5560
+ hidden: dataset.hidden,
5561
+ lineCap: dataset.borderCapStyle,
5562
+ lineDash: dataset.borderDash,
5563
+ lineDashOffset: dataset.borderDashOffset,
5564
+ lineJoin: dataset.borderJoinStyle,
5565
+ lineWidth: dataset.borderWidth,
5566
+ strokeStyle: dataset.borderColor,
5567
+
5568
+ // Below is extra data used for toggling the datasets
5569
+ datasetIndex: i
5570
+ };
5571
+ }, this) : [];
5572
+ }
5573
+ }
5574
+ };
5575
+
5576
+ Chart.Legend = Chart.Element.extend({
5577
+
5578
+ initialize: function(config) {
5579
+ helpers.extend(this, config);
5580
+
5581
+ // Contains hit boxes for each dataset (in dataset order)
5582
+ this.legendHitBoxes = [];
5583
+
5584
+ // Are we in doughnut mode which has a different data type
5585
+ this.doughnutMode = false;
5586
+ },
5587
+
5588
+ // These methods are ordered by lifecyle. Utilities then follow.
5589
+ // Any function defined here is inherited by all legend types.
5590
+ // Any function can be extended by the legend type
5591
+
5592
+ beforeUpdate: helpers.noop,
5593
+ update: function(maxWidth, maxHeight, margins) {
5594
+
5595
+ // Update Lifecycle - Probably don't want to ever extend or overwrite this function ;)
5596
+ this.beforeUpdate();
5597
+
5598
+ // Absorb the master measurements
5599
+ this.maxWidth = maxWidth;
5600
+ this.maxHeight = maxHeight;
5601
+ this.margins = margins;
5602
+
5603
+ // Dimensions
5604
+ this.beforeSetDimensions();
5605
+ this.setDimensions();
5606
+ this.afterSetDimensions();
5607
+ // Labels
5608
+ this.beforeBuildLabels();
5609
+ this.buildLabels();
5610
+ this.afterBuildLabels();
5611
+
5612
+ // Fit
5613
+ this.beforeFit();
5614
+ this.fit();
5615
+ this.afterFit();
5616
+ //
5617
+ this.afterUpdate();
5618
+
5619
+ return this.minSize;
5620
+
5621
+ },
5622
+ afterUpdate: helpers.noop,
5623
+
5624
+ //
5625
+
5626
+ beforeSetDimensions: helpers.noop,
5627
+ setDimensions: function() {
5628
+ // Set the unconstrained dimension before label rotation
5629
+ if (this.isHorizontal()) {
5630
+ // Reset position before calculating rotation
5631
+ this.width = this.maxWidth;
5632
+ this.left = 0;
5633
+ this.right = this.width;
5634
+ } else {
5635
+ this.height = this.maxHeight;
5636
+
5637
+ // Reset position before calculating rotation
5638
+ this.top = 0;
5639
+ this.bottom = this.height;
5640
+ }
5641
+
5642
+ // Reset padding
5643
+ this.paddingLeft = 0;
5644
+ this.paddingTop = 0;
5645
+ this.paddingRight = 0;
5646
+ this.paddingBottom = 0;
5647
+
5648
+ // Reset minSize
5649
+ this.minSize = {
5650
+ width: 0,
5651
+ height: 0
5652
+ };
5653
+ },
5654
+ afterSetDimensions: helpers.noop,
5655
+
5656
+ //
5657
+
5658
+ beforeBuildLabels: helpers.noop,
5659
+ buildLabels: function() {
5660
+ this.legendItems = this.options.labels.generateLabels.call(this, this.chart.data);
5661
+ if(this.options.reverse){
5662
+ this.legendItems.reverse();
5663
+ }
5664
+ },
5665
+ afterBuildLabels: helpers.noop,
5666
+
5667
+ //
5668
+
5669
+ beforeFit: helpers.noop,
5670
+ fit: function() {
5671
+
5672
+ var ctx = this.ctx;
5673
+ var fontSize = helpers.getValueOrDefault(this.options.labels.fontSize, Chart.defaults.global.defaultFontSize);
5674
+ var fontStyle = helpers.getValueOrDefault(this.options.labels.fontStyle, Chart.defaults.global.defaultFontStyle);
5675
+ var fontFamily = helpers.getValueOrDefault(this.options.labels.fontFamily, Chart.defaults.global.defaultFontFamily);
5676
+ var labelFont = helpers.fontString(fontSize, fontStyle, fontFamily);
5677
+
5678
+ // Reset hit boxes
5679
+ this.legendHitBoxes = [];
5680
+
5681
+ // Width
5682
+ if (this.isHorizontal()) {
5683
+ this.minSize.width = this.maxWidth; // fill all the width
5684
+ } else {
5685
+ this.minSize.width = this.options.display ? 10 : 0;
5686
+ }
5687
+
5688
+ // height
5689
+ if (this.isHorizontal()) {
5690
+ this.minSize.height = this.options.display ? 10 : 0;
5691
+ } else {
5692
+ this.minSize.height = this.maxHeight; // fill all the height
5693
+ }
5694
+
5695
+ // Increase sizes here
5696
+ if (this.options.display) {
5697
+ if (this.isHorizontal()) {
5698
+ // Labels
5699
+
5700
+ // Width of each line of legend boxes. Labels wrap onto multiple lines when there are too many to fit on one
5701
+ this.lineWidths = [0];
5702
+ var totalHeight = this.legendItems.length ? fontSize + (this.options.labels.padding) : 0;
5703
+
5704
+ ctx.textAlign = "left";
5705
+ ctx.textBaseline = 'top';
5706
+ ctx.font = labelFont;
5707
+
5708
+ helpers.each(this.legendItems, function(legendItem, i) {
5709
+ var width = this.options.labels.boxWidth + (fontSize / 2) + ctx.measureText(legendItem.text).width;
5710
+ if (this.lineWidths[this.lineWidths.length - 1] + width + this.options.labels.padding >= this.width) {
5711
+ totalHeight += fontSize + (this.options.labels.padding);
5712
+ this.lineWidths[this.lineWidths.length] = this.left;
5713
+ }
5714
+
5715
+ // Store the hitbox width and height here. Final position will be updated in `draw`
5716
+ this.legendHitBoxes[i] = {
5717
+ left: 0,
5718
+ top: 0,
5719
+ width: width,
5720
+ height: fontSize
5721
+ };
5722
+
5723
+ this.lineWidths[this.lineWidths.length - 1] += width + this.options.labels.padding;
5724
+ }, this);
5725
+
5726
+ this.minSize.height += totalHeight;
5727
+
5728
+ } else {
5729
+ // TODO vertical
5730
+ }
5731
+ }
5732
+
5733
+ this.width = this.minSize.width;
5734
+ this.height = this.minSize.height;
5735
+
5736
+ },
5737
+ afterFit: helpers.noop,
5738
+
5739
+ // Shared Methods
5740
+ isHorizontal: function() {
5741
+ return this.options.position === "top" || this.options.position === "bottom";
5742
+ },
5743
+
5744
+ // Actualy draw the legend on the canvas
5745
+ draw: function() {
5746
+ if (this.options.display) {
5747
+ var ctx = this.ctx;
5748
+ var cursor = {
5749
+ x: this.left + ((this.width - this.lineWidths[0]) / 2),
5750
+ y: this.top + this.options.labels.padding,
5751
+ line: 0
5752
+ };
5753
+
5754
+ var fontColor = helpers.getValueOrDefault(this.options.labels.fontColor, Chart.defaults.global.defaultFontColor);
5755
+ var fontSize = helpers.getValueOrDefault(this.options.labels.fontSize, Chart.defaults.global.defaultFontSize);
5756
+ var fontStyle = helpers.getValueOrDefault(this.options.labels.fontStyle, Chart.defaults.global.defaultFontStyle);
5757
+ var fontFamily = helpers.getValueOrDefault(this.options.labels.fontFamily, Chart.defaults.global.defaultFontFamily);
5758
+ var labelFont = helpers.fontString(fontSize, fontStyle, fontFamily);
5759
+
5760
+ // Horizontal
5761
+ if (this.isHorizontal()) {
5762
+ // Labels
5763
+ ctx.textAlign = "left";
5764
+ ctx.textBaseline = 'top';
5765
+ ctx.lineWidth = 0.5;
5766
+ ctx.strokeStyle = fontColor; // for strikethrough effect
5767
+ ctx.fillStyle = fontColor; // render in correct colour
5768
+ ctx.font = labelFont;
5769
+
5770
+ helpers.each(this.legendItems, function(legendItem, i) {
5771
+ var textWidth = ctx.measureText(legendItem.text).width;
5772
+ var width = this.options.labels.boxWidth + (fontSize / 2) + textWidth;
5773
+
5774
+ if (cursor.x + width >= this.width) {
5775
+ cursor.y += fontSize + (this.options.labels.padding);
5776
+ cursor.line++;
5777
+ cursor.x = this.left + ((this.width - this.lineWidths[cursor.line]) / 2);
5778
+ }
5779
+
5780
+ // Set the ctx for the box
5781
+ ctx.save();
5782
+
5783
+ var itemOrDefault = function(item, defaulVal) {
5784
+ return item !== undefined ? item : defaulVal;
5785
+ };
5786
+
5787
+ ctx.fillStyle = itemOrDefault(legendItem.fillStyle, Chart.defaults.global.defaultColor);
5788
+ ctx.lineCap = itemOrDefault(legendItem.lineCap, Chart.defaults.global.elements.line.borderCapStyle);
5789
+ ctx.lineDashOffset = itemOrDefault(legendItem.lineDashOffset, Chart.defaults.global.elements.line.borderDashOffset);
5790
+ ctx.lineJoin = itemOrDefault(legendItem.lineJoin, Chart.defaults.global.elements.line.borderJoinStyle);
5791
+ ctx.lineWidth = itemOrDefault(legendItem.lineWidth, Chart.defaults.global.elements.line.borderWidth);
5792
+ ctx.strokeStyle = itemOrDefault(legendItem.strokeStyle, Chart.defaults.global.defaultColor);
5793
+
5794
+ if (ctx.setLineDash) {
5795
+ // IE 9 and 10 do not support line dash
5796
+ ctx.setLineDash(itemOrDefault(legendItem.lineDash, Chart.defaults.global.elements.line.borderDash));
5797
+ }
5798
+
5799
+ // Draw the box
5800
+ ctx.strokeRect(cursor.x, cursor.y, this.options.labels.boxWidth, fontSize);
5801
+ ctx.fillRect(cursor.x, cursor.y, this.options.labels.boxWidth, fontSize);
5802
+
5803
+ ctx.restore();
5804
+
5805
+ this.legendHitBoxes[i].left = cursor.x;
5806
+ this.legendHitBoxes[i].top = cursor.y;
5807
+
5808
+ // Fill the actual label
5809
+ ctx.fillText(legendItem.text, this.options.labels.boxWidth + (fontSize / 2) + cursor.x, cursor.y);
5810
+
5811
+ if (legendItem.hidden) {
5812
+ // Strikethrough the text if hidden
5813
+ ctx.beginPath();
5814
+ ctx.lineWidth = 2;
5815
+ ctx.moveTo(this.options.labels.boxWidth + (fontSize / 2) + cursor.x, cursor.y + (fontSize / 2));
5816
+ ctx.lineTo(this.options.labels.boxWidth + (fontSize / 2) + cursor.x + textWidth, cursor.y + (fontSize / 2));
5817
+ ctx.stroke();
5818
+ }
5819
+
5820
+ cursor.x += width + (this.options.labels.padding);
5821
+ }, this);
5822
+ } else {
5823
+
5824
+ }
5825
+ }
5826
+ },
5827
+
5828
+ // Handle an event
5829
+ handleEvent: function(e) {
5830
+ var position = helpers.getRelativePosition(e, this.chart.chart);
5831
+
5832
+ if (position.x >= this.left && position.x <= this.right && position.y >= this.top && position.y <= this.bottom) {
5833
+ // See if we are touching one of the dataset boxes
5834
+ for (var i = 0; i < this.legendHitBoxes.length; ++i) {
5835
+ var hitBox = this.legendHitBoxes[i];
5836
+
5837
+ if (position.x >= hitBox.left && position.x <= hitBox.left + hitBox.width && position.y >= hitBox.top && position.y <= hitBox.top + hitBox.height) {
5838
+ // Touching an element
5839
+ if (this.options.onClick) {
5840
+ this.options.onClick.call(this, e, this.legendItems[i]);
5841
+ }
5842
+ break;
5843
+ }
5844
+ }
5845
+ }
5846
+ }
5847
+ });
5848
+
5849
+ };
5850
+
5851
+ },{}],29:[function(require,module,exports){
5852
+ "use strict";
5853
+
5854
+ module.exports = function(Chart) {
5855
+
5856
+ var helpers = Chart.helpers;
5857
+
5858
+ Chart.defaults.scale = {
5859
+ display: true,
5860
+
5861
+ // grid line settings
5862
+ gridLines: {
5863
+ display: true,
5864
+ color: "rgba(0, 0, 0, 0.1)",
5865
+ lineWidth: 1,
5866
+ drawOnChartArea: true,
5867
+ drawTicks: true,
5868
+ zeroLineWidth: 1,
5869
+ zeroLineColor: "rgba(0,0,0,0.25)",
5870
+ offsetGridLines: false
5871
+ },
5872
+
5873
+ // scale label
5874
+ scaleLabel: {
5875
+ // actual label
5876
+ labelString: '',
5877
+
5878
+ // display property
5879
+ display: false
5880
+ },
5881
+
5882
+ // label settings
5883
+ ticks: {
5884
+ beginAtZero: false,
5885
+ maxRotation: 50,
5886
+ mirror: false,
5887
+ padding: 10,
5888
+ reverse: false,
5889
+ display: true,
5890
+ autoSkip: true,
5891
+ autoSkipPadding: 0,
5892
+ callback: function(value) {
5893
+ return '' + value;
5894
+ }
5895
+ }
5896
+ };
5897
+
5898
+ Chart.Scale = Chart.Element.extend({
5899
+
5900
+ // These methods are ordered by lifecyle. Utilities then follow.
5901
+ // Any function defined here is inherited by all scale types.
5902
+ // Any function can be extended by the scale type
5903
+
5904
+ beforeUpdate: function() {
5905
+ helpers.callCallback(this.options.beforeUpdate, [this]);
5906
+ },
5907
+ update: function(maxWidth, maxHeight, margins) {
5908
+
5909
+ // Update Lifecycle - Probably don't want to ever extend or overwrite this function ;)
5910
+ this.beforeUpdate();
5911
+
5912
+ // Absorb the master measurements
5913
+ this.maxWidth = maxWidth;
5914
+ this.maxHeight = maxHeight;
5915
+ this.margins = helpers.extend({
5916
+ left: 0,
5917
+ right: 0,
5918
+ top: 0,
5919
+ bottom: 0
5920
+ }, margins);
5921
+
5922
+ // Dimensions
5923
+ this.beforeSetDimensions();
5924
+ this.setDimensions();
5925
+ this.afterSetDimensions();
5926
+
5927
+ // Data min/max
5928
+ this.beforeDataLimits();
5929
+ this.determineDataLimits();
5930
+ this.afterDataLimits();
5931
+
5932
+ // Ticks
5933
+ this.beforeBuildTicks();
5934
+ this.buildTicks();
5935
+ this.afterBuildTicks();
5936
+
5937
+ this.beforeTickToLabelConversion();
5938
+ this.convertTicksToLabels();
5939
+ this.afterTickToLabelConversion();
5940
+
5941
+ // Tick Rotation
5942
+ this.beforeCalculateTickRotation();
5943
+ this.calculateTickRotation();
5944
+ this.afterCalculateTickRotation();
5945
+ // Fit
5946
+ this.beforeFit();
5947
+ this.fit();
5948
+ this.afterFit();
5949
+ //
5950
+ this.afterUpdate();
5951
+
5952
+ return this.minSize;
5953
+
5954
+ },
5955
+ afterUpdate: function() {
5956
+ helpers.callCallback(this.options.afterUpdate, [this]);
5957
+ },
5958
+
5959
+ //
5960
+
5961
+ beforeSetDimensions: function() {
5962
+ helpers.callCallback(this.options.beforeSetDimensions, [this]);
5963
+ },
5964
+ setDimensions: function() {
5965
+ // Set the unconstrained dimension before label rotation
5966
+ if (this.isHorizontal()) {
5967
+ // Reset position before calculating rotation
5968
+ this.width = this.maxWidth;
5969
+ this.left = 0;
5970
+ this.right = this.width;
5971
+ } else {
5972
+ this.height = this.maxHeight;
5973
+
5974
+ // Reset position before calculating rotation
5975
+ this.top = 0;
5976
+ this.bottom = this.height;
5977
+ }
5978
+
5979
+ // Reset padding
5980
+ this.paddingLeft = 0;
5981
+ this.paddingTop = 0;
5982
+ this.paddingRight = 0;
5983
+ this.paddingBottom = 0;
5984
+ },
5985
+ afterSetDimensions: function() {
5986
+ helpers.callCallback(this.options.afterSetDimensions, [this]);
5987
+ },
5988
+
5989
+ // Data limits
5990
+ beforeDataLimits: function() {
5991
+ helpers.callCallback(this.options.beforeDataLimits, [this]);
5992
+ },
5993
+ determineDataLimits: helpers.noop,
5994
+ afterDataLimits: function() {
5995
+ helpers.callCallback(this.options.afterDataLimits, [this]);
5996
+ },
5997
+
5998
+ //
5999
+ beforeBuildTicks: function() {
6000
+ helpers.callCallback(this.options.beforeBuildTicks, [this]);
6001
+ },
6002
+ buildTicks: helpers.noop,
6003
+ afterBuildTicks: function() {
6004
+ helpers.callCallback(this.options.afterBuildTicks, [this]);
6005
+ },
6006
+
6007
+ beforeTickToLabelConversion: function() {
6008
+ helpers.callCallback(this.options.beforeTickToLabelConversion, [this]);
6009
+ },
6010
+ convertTicksToLabels: function() {
6011
+ // Convert ticks to strings
6012
+ this.ticks = this.ticks.map(function(numericalTick, index, ticks) {
6013
+ if (this.options.ticks.userCallback) {
6014
+ return this.options.ticks.userCallback(numericalTick, index, ticks);
6015
+ }
6016
+ return this.options.ticks.callback(numericalTick, index, ticks);
6017
+ },
6018
+ this);
6019
+ },
6020
+ afterTickToLabelConversion: function() {
6021
+ helpers.callCallback(this.options.afterTickToLabelConversion, [this]);
6022
+ },
6023
+
6024
+ //
6025
+
6026
+ beforeCalculateTickRotation: function() {
6027
+ helpers.callCallback(this.options.beforeCalculateTickRotation, [this]);
6028
+ },
6029
+ calculateTickRotation: function() {
6030
+ //Get the width of each grid by calculating the difference
6031
+ //between x offsets between 0 and 1.
6032
+ var tickFontSize = helpers.getValueOrDefault(this.options.ticks.fontSize, Chart.defaults.global.defaultFontSize);
6033
+ var tickFontStyle = helpers.getValueOrDefault(this.options.ticks.fontStyle, Chart.defaults.global.defaultFontStyle);
6034
+ var tickFontFamily = helpers.getValueOrDefault(this.options.ticks.fontFamily, Chart.defaults.global.defaultFontFamily);
6035
+ var tickLabelFont = helpers.fontString(tickFontSize, tickFontStyle, tickFontFamily);
6036
+ this.ctx.font = tickLabelFont;
6037
+
6038
+ var firstWidth = this.ctx.measureText(this.ticks[0]).width;
6039
+ var lastWidth = this.ctx.measureText(this.ticks[this.ticks.length - 1]).width;
6040
+ var firstRotated;
6041
+
6042
+ this.labelRotation = 0;
6043
+ this.paddingRight = 0;
6044
+ this.paddingLeft = 0;
6045
+
6046
+ if (this.options.display) {
6047
+ if (this.isHorizontal()) {
6048
+ this.paddingRight = lastWidth / 2 + 3;
6049
+ this.paddingLeft = firstWidth / 2 + 3;
6050
+
6051
+ if (!this.longestTextCache) {
6052
+ this.longestTextCache = {};
6053
+ }
6054
+ var originalLabelWidth = helpers.longestText(this.ctx, tickLabelFont, this.ticks, this.longestTextCache);
6055
+ var labelWidth = originalLabelWidth;
6056
+ var cosRotation;
6057
+ var sinRotation;
6058
+
6059
+ // Allow 3 pixels x2 padding either side for label readability
6060
+ // only the index matters for a dataset scale, but we want a consistent interface between scales
6061
+ var tickWidth = this.getPixelForTick(1) - this.getPixelForTick(0) - 6;
6062
+
6063
+ //Max label rotation can be set or default to 90 - also act as a loop counter
6064
+ while (labelWidth > tickWidth && this.labelRotation < this.options.ticks.maxRotation) {
6065
+ cosRotation = Math.cos(helpers.toRadians(this.labelRotation));
6066
+ sinRotation = Math.sin(helpers.toRadians(this.labelRotation));
6067
+
6068
+ firstRotated = cosRotation * firstWidth;
6069
+
6070
+ // We're right aligning the text now.
6071
+ if (firstRotated + tickFontSize / 2 > this.yLabelWidth) {
6072
+ this.paddingLeft = firstRotated + tickFontSize / 2;
6073
+ }
6074
+
6075
+ this.paddingRight = tickFontSize / 2;
6076
+
6077
+ if (sinRotation * originalLabelWidth > this.maxHeight) {
6078
+ // go back one step
6079
+ this.labelRotation--;
6080
+ break;
6081
+ }
6082
+
6083
+ this.labelRotation++;
6084
+ labelWidth = cosRotation * originalLabelWidth;
6085
+ }
6086
+ }
6087
+ }
6088
+
6089
+ if (this.margins) {
6090
+ this.paddingLeft = Math.max(this.paddingLeft - this.margins.left, 0);
6091
+ this.paddingRight = Math.max(this.paddingRight - this.margins.right, 0);
6092
+ }
6093
+ },
6094
+ afterCalculateTickRotation: function() {
6095
+ helpers.callCallback(this.options.afterCalculateTickRotation, [this]);
6096
+ },
6097
+
6098
+ //
6099
+
6100
+ beforeFit: function() {
6101
+ helpers.callCallback(this.options.beforeFit, [this]);
6102
+ },
6103
+ fit: function() {
6104
+
6105
+ this.minSize = {
6106
+ width: 0,
6107
+ height: 0
6108
+ };
6109
+
6110
+ var tickFontSize = helpers.getValueOrDefault(this.options.ticks.fontSize, Chart.defaults.global.defaultFontSize);
6111
+ var tickFontStyle = helpers.getValueOrDefault(this.options.ticks.fontStyle, Chart.defaults.global.defaultFontStyle);
6112
+ var tickFontFamily = helpers.getValueOrDefault(this.options.ticks.fontFamily, Chart.defaults.global.defaultFontFamily);
6113
+ var tickLabelFont = helpers.fontString(tickFontSize, tickFontStyle, tickFontFamily);
6114
+
6115
+ var scaleLabelFontSize = helpers.getValueOrDefault(this.options.scaleLabel.fontSize, Chart.defaults.global.defaultFontSize);
6116
+ var scaleLabelFontStyle = helpers.getValueOrDefault(this.options.scaleLabel.fontStyle, Chart.defaults.global.defaultFontStyle);
6117
+ var scaleLabelFontFamily = helpers.getValueOrDefault(this.options.scaleLabel.fontFamily, Chart.defaults.global.defaultFontFamily);
6118
+ var scaleLabelFont = helpers.fontString(scaleLabelFontSize, scaleLabelFontStyle, scaleLabelFontFamily);
6119
+
6120
+ // Width
6121
+ if (this.isHorizontal()) {
6122
+ // subtract the margins to line up with the chartArea if we are a full width scale
6123
+ this.minSize.width = this.isFullWidth() ? this.maxWidth - this.margins.left - this.margins.right : this.maxWidth;
6124
+ } else {
6125
+ this.minSize.width = this.options.gridLines.display && this.options.display ? 10 : 0;
6126
+ }
6127
+
6128
+ // height
6129
+ if (this.isHorizontal()) {
6130
+ this.minSize.height = this.options.gridLines.display && this.options.display ? 10 : 0;
6131
+ } else {
6132
+ this.minSize.height = this.maxHeight; // fill all the height
6133
+ }
6134
+
6135
+ // Are we showing a title for the scale?
6136
+ if (this.options.scaleLabel.display) {
6137
+ if (this.isHorizontal()) {
6138
+ this.minSize.height += (scaleLabelFontSize * 1.5);
6139
+ } else {
6140
+ this.minSize.width += (scaleLabelFontSize * 1.5);
6141
+ }
6142
+ }
6143
+
6144
+ if (this.options.ticks.display && this.options.display) {
6145
+ // Don't bother fitting the ticks if we are not showing them
6146
+ if (!this.longestTextCache) {
6147
+ this.longestTextCache = {};
6148
+ }
6149
+
6150
+ var largestTextWidth = helpers.longestText(this.ctx, tickLabelFont, this.ticks, this.longestTextCache);
6151
+
6152
+ if (this.isHorizontal()) {
6153
+ // A horizontal axis is more constrained by the height.
6154
+ this.longestLabelWidth = largestTextWidth;
6155
+
6156
+ // TODO - improve this calculation
6157
+ var labelHeight = (Math.sin(helpers.toRadians(this.labelRotation)) * this.longestLabelWidth) + 1.5 * tickFontSize;
6158
+
6159
+ this.minSize.height = Math.min(this.maxHeight, this.minSize.height + labelHeight);
6160
+ this.ctx.font = tickLabelFont;
6161
+
6162
+ var firstLabelWidth = this.ctx.measureText(this.ticks[0]).width;
6163
+ var lastLabelWidth = this.ctx.measureText(this.ticks[this.ticks.length - 1]).width;
6164
+
6165
+ // Ensure that our ticks are always inside the canvas. When rotated, ticks are right aligned which means that the right padding is dominated
6166
+ // by the font height
6167
+ var cosRotation = Math.cos(helpers.toRadians(this.labelRotation));
6168
+ var sinRotation = Math.sin(helpers.toRadians(this.labelRotation));
6169
+ this.paddingLeft = this.labelRotation !== 0 ? (cosRotation * firstLabelWidth) + 3 : firstLabelWidth / 2 + 3; // add 3 px to move away from canvas edges
6170
+ this.paddingRight = this.labelRotation !== 0 ? (sinRotation * (tickFontSize / 2)) + 3 : lastLabelWidth / 2 + 3; // when rotated
6171
+ } else {
6172
+ // A vertical axis is more constrained by the width. Labels are the dominant factor here, so get that length first
6173
+ var maxLabelWidth = this.maxWidth - this.minSize.width;
6174
+
6175
+ // Account for padding
6176
+ if (!this.options.ticks.mirror) {
6177
+ largestTextWidth += this.options.ticks.padding;
6178
+ }
6179
+
6180
+ if (largestTextWidth < maxLabelWidth) {
6181
+ // We don't need all the room
6182
+ this.minSize.width += largestTextWidth;
6183
+ } else {
6184
+ // Expand to max size
6185
+ this.minSize.width = this.maxWidth;
6186
+ }
6187
+
6188
+ this.paddingTop = tickFontSize / 2;
6189
+ this.paddingBottom = tickFontSize / 2;
6190
+ }
6191
+ }
6192
+
6193
+ if (this.margins) {
6194
+ this.paddingLeft = Math.max(this.paddingLeft - this.margins.left, 0);
6195
+ this.paddingTop = Math.max(this.paddingTop - this.margins.top, 0);
6196
+ this.paddingRight = Math.max(this.paddingRight - this.margins.right, 0);
6197
+ this.paddingBottom = Math.max(this.paddingBottom - this.margins.bottom, 0);
6198
+ }
6199
+
6200
+ this.width = this.minSize.width;
6201
+ this.height = this.minSize.height;
6202
+
6203
+ },
6204
+ afterFit: function() {
6205
+ helpers.callCallback(this.options.afterFit, [this]);
6206
+ },
6207
+
6208
+ // Shared Methods
6209
+ isHorizontal: function() {
6210
+ return this.options.position === "top" || this.options.position === "bottom";
6211
+ },
6212
+ isFullWidth: function() {
6213
+ return (this.options.fullWidth);
6214
+ },
6215
+
6216
+ // 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
6217
+ getRightValue: function getRightValue(rawValue) {
6218
+ // Null and undefined values first
6219
+ if (rawValue === null || typeof(rawValue) === 'undefined') {
6220
+ return NaN;
6221
+ }
6222
+ // isNaN(object) returns true, so make sure NaN is checking for a number
6223
+ if (typeof(rawValue) === 'number' && isNaN(rawValue)) {
6224
+ return NaN;
6225
+ }
6226
+ // If it is in fact an object, dive in one more level
6227
+ if (typeof(rawValue) === "object") {
6228
+ if (rawValue instanceof Date) {
6229
+ return rawValue;
6230
+ } else {
6231
+ return getRightValue(this.isHorizontal() ? rawValue.x : rawValue.y);
6232
+ }
6233
+ }
6234
+
6235
+ // Value is good, return it
6236
+ return rawValue;
6237
+ },
6238
+
6239
+ // Used to get the value to display in the tooltip for the data at the given index
6240
+ // function getLabelForIndex(index, datasetIndex)
6241
+ getLabelForIndex: helpers.noop,
6242
+
6243
+ // Used to get data value locations. Value can either be an index or a numerical value
6244
+ getPixelForValue: helpers.noop,
6245
+
6246
+ // Used for tick location, should
6247
+ getPixelForTick: function(index, includeOffset) {
6248
+ if (this.isHorizontal()) {
6249
+ var innerWidth = this.width - (this.paddingLeft + this.paddingRight);
6250
+ var tickWidth = innerWidth / Math.max((this.ticks.length - ((this.options.gridLines.offsetGridLines) ? 0 : 1)), 1);
6251
+ var pixel = (tickWidth * index) + this.paddingLeft;
6252
+
6253
+ if (includeOffset) {
6254
+ pixel += tickWidth / 2;
6255
+ }
6256
+
6257
+ var finalVal = this.left + Math.round(pixel);
6258
+ finalVal += this.isFullWidth() ? this.margins.left : 0;
6259
+ return finalVal;
6260
+ } else {
6261
+ var innerHeight = this.height - (this.paddingTop + this.paddingBottom);
6262
+ return this.top + (index * (innerHeight / (this.ticks.length - 1)));
6263
+ }
6264
+ },
6265
+
6266
+ // Utility for getting the pixel location of a percentage of scale
6267
+ getPixelForDecimal: function(decimal /*, includeOffset*/ ) {
6268
+ if (this.isHorizontal()) {
6269
+ var innerWidth = this.width - (this.paddingLeft + this.paddingRight);
6270
+ var valueOffset = (innerWidth * decimal) + this.paddingLeft;
6271
+
6272
+ var finalVal = this.left + Math.round(valueOffset);
6273
+ finalVal += this.isFullWidth() ? this.margins.left : 0;
6274
+ return finalVal;
6275
+ } else {
6276
+ return this.top + (decimal * this.height);
6277
+ }
6278
+ },
6279
+
6280
+ // Actualy draw the scale on the canvas
6281
+ // @param {rectangle} chartArea : the area of the chart to draw full grid lines on
6282
+ draw: function(chartArea) {
6283
+ if (this.options.display) {
6284
+
6285
+ var setContextLineSettings;
6286
+ var isRotated = this.labelRotation !== 0;
6287
+ var skipRatio;
6288
+ var scaleLabelX;
6289
+ var scaleLabelY;
6290
+ var useAutoskipper = this.options.ticks.autoSkip;
6291
+
6292
+
6293
+ // figure out the maximum number of gridlines to show
6294
+ var maxTicks;
6295
+
6296
+ if (this.options.ticks.maxTicksLimit) {
6297
+ maxTicks = this.options.ticks.maxTicksLimit;
6298
+ }
6299
+
6300
+ var tickFontColor = helpers.getValueOrDefault(this.options.ticks.fontColor, Chart.defaults.global.defaultFontColor);
6301
+ var tickFontSize = helpers.getValueOrDefault(this.options.ticks.fontSize, Chart.defaults.global.defaultFontSize);
6302
+ var tickFontStyle = helpers.getValueOrDefault(this.options.ticks.fontStyle, Chart.defaults.global.defaultFontStyle);
6303
+ var tickFontFamily = helpers.getValueOrDefault(this.options.ticks.fontFamily, Chart.defaults.global.defaultFontFamily);
6304
+ var tickLabelFont = helpers.fontString(tickFontSize, tickFontStyle, tickFontFamily);
6305
+
6306
+ var scaleLabelFontColor = helpers.getValueOrDefault(this.options.scaleLabel.fontColor, Chart.defaults.global.defaultFontColor);
6307
+ var scaleLabelFontSize = helpers.getValueOrDefault(this.options.scaleLabel.fontSize, Chart.defaults.global.defaultFontSize);
6308
+ var scaleLabelFontStyle = helpers.getValueOrDefault(this.options.scaleLabel.fontStyle, Chart.defaults.global.defaultFontStyle);
6309
+ var scaleLabelFontFamily = helpers.getValueOrDefault(this.options.scaleLabel.fontFamily, Chart.defaults.global.defaultFontFamily);
6310
+ var scaleLabelFont = helpers.fontString(scaleLabelFontSize, scaleLabelFontStyle, scaleLabelFontFamily);
6311
+
6312
+ var cosRotation = Math.cos(helpers.toRadians(this.labelRotation));
6313
+ var sinRotation = Math.sin(helpers.toRadians(this.labelRotation));
6314
+ var longestRotatedLabel = this.longestLabelWidth * cosRotation;
6315
+ var rotatedLabelHeight = tickFontSize * sinRotation;
6316
+
6317
+ // Make sure we draw text in the correct color and font
6318
+ this.ctx.fillStyle = tickFontColor;
6319
+
6320
+ if (this.isHorizontal()) {
6321
+ setContextLineSettings = true;
6322
+ var yTickStart = this.options.position === "bottom" ? this.top : this.bottom - 10;
6323
+ var yTickEnd = this.options.position === "bottom" ? this.top + 10 : this.bottom;
6324
+ skipRatio = false;
6325
+
6326
+ if (((longestRotatedLabel / 2) + this.options.ticks.autoSkipPadding) * this.ticks.length > (this.width - (this.paddingLeft + this.paddingRight))) {
6327
+ skipRatio = 1 + Math.floor((((longestRotatedLabel / 2) + this.options.ticks.autoSkipPadding) * this.ticks.length) / (this.width - (this.paddingLeft + this.paddingRight)));
6328
+ }
6329
+
6330
+ // if they defined a max number of ticks,
6331
+ // increase skipRatio until that number is met
6332
+ if (maxTicks && this.ticks.length > maxTicks) {
6333
+ while (!skipRatio || this.ticks.length / (skipRatio || 1) > maxTicks) {
6334
+ if (!skipRatio) {
6335
+ skipRatio = 1;
6336
+ }
6337
+ skipRatio += 1;
6338
+ }
6339
+ }
6340
+
6341
+ if (!useAutoskipper) {
6342
+ skipRatio = false;
6343
+ }
6344
+
6345
+ helpers.each(this.ticks, function(label, index) {
6346
+ // Blank ticks
6347
+ var isLastTick = this.ticks.length === index + 1;
6348
+
6349
+ // Since we always show the last tick,we need may need to hide the last shown one before
6350
+ var shouldSkip = (skipRatio > 1 && index % skipRatio > 0) || (index % skipRatio === 0 && index + skipRatio > this.ticks.length);
6351
+ if (shouldSkip && !isLastTick || (label === undefined || label === null)) {
6352
+ return;
6353
+ }
6354
+ var xLineValue = this.getPixelForTick(index); // xvalues for grid lines
6355
+ var xLabelValue = this.getPixelForTick(index, this.options.gridLines.offsetGridLines); // x values for ticks (need to consider offsetLabel option)
6356
+
6357
+ if (this.options.gridLines.display) {
6358
+ if (index === (typeof this.zeroLineIndex !== 'undefined' ? this.zeroLineIndex : 0)) {
6359
+ // Draw the first index specially
6360
+ this.ctx.lineWidth = this.options.gridLines.zeroLineWidth;
6361
+ this.ctx.strokeStyle = this.options.gridLines.zeroLineColor;
6362
+ setContextLineSettings = true; // reset next time
6363
+ } else if (setContextLineSettings) {
6364
+ this.ctx.lineWidth = this.options.gridLines.lineWidth;
6365
+ this.ctx.strokeStyle = this.options.gridLines.color;
6366
+ setContextLineSettings = false;
6367
+ }
6368
+
6369
+ xLineValue += helpers.aliasPixel(this.ctx.lineWidth);
6370
+
6371
+ // Draw the label area
6372
+ this.ctx.beginPath();
6373
+
6374
+ if (this.options.gridLines.drawTicks) {
6375
+ this.ctx.moveTo(xLineValue, yTickStart);
6376
+ this.ctx.lineTo(xLineValue, yTickEnd);
6377
+ }
6378
+
6379
+ // Draw the chart area
6380
+ if (this.options.gridLines.drawOnChartArea) {
6381
+ this.ctx.moveTo(xLineValue, chartArea.top);
6382
+ this.ctx.lineTo(xLineValue, chartArea.bottom);
6383
+ }
6384
+
6385
+ // Need to stroke in the loop because we are potentially changing line widths & colours
6386
+ this.ctx.stroke();
6387
+ }
6388
+
6389
+ if (this.options.ticks.display) {
6390
+ this.ctx.save();
6391
+ this.ctx.translate(xLabelValue, (isRotated) ? this.top + 12 : this.options.position === "top" ? this.bottom - 10 : this.top + 10);
6392
+ this.ctx.rotate(helpers.toRadians(this.labelRotation) * -1);
6393
+ this.ctx.font = tickLabelFont;
6394
+ this.ctx.textAlign = (isRotated) ? "right" : "center";
6395
+ this.ctx.textBaseline = (isRotated) ? "middle" : this.options.position === "top" ? "bottom" : "top";
6396
+ this.ctx.fillText(label, 0, 0);
6397
+ this.ctx.restore();
6398
+ }
6399
+ }, this);
6400
+
6401
+ if (this.options.scaleLabel.display) {
6402
+ // Draw the scale label
6403
+ this.ctx.textAlign = "center";
6404
+ this.ctx.textBaseline = 'middle';
6405
+ this.ctx.fillStyle = scaleLabelFontColor; // render in correct colour
6406
+ this.ctx.font = scaleLabelFont;
6407
+
6408
+ scaleLabelX = this.left + ((this.right - this.left) / 2); // midpoint of the width
6409
+ scaleLabelY = this.options.position === 'bottom' ? this.bottom - (scaleLabelFontSize / 2) : this.top + (scaleLabelFontSize / 2);
6410
+
6411
+ this.ctx.fillText(this.options.scaleLabel.labelString, scaleLabelX, scaleLabelY);
6412
+ }
6413
+
6414
+ } else {
6415
+ setContextLineSettings = true;
6416
+ var xTickStart = this.options.position === "right" ? this.left : this.right - 5;
6417
+ var xTickEnd = this.options.position === "right" ? this.left + 5 : this.right;
6418
+
6419
+ helpers.each(this.ticks, function(label, index) {
6420
+ // If the callback returned a null or undefined value, do not draw this line
6421
+ if (label === undefined || label === null) {
6422
+ return;
6423
+ }
6424
+
6425
+ var yLineValue = this.getPixelForTick(index); // xvalues for grid lines
6426
+
6427
+ if (this.options.gridLines.display) {
6428
+ if (index === (typeof this.zeroLineIndex !== 'undefined' ? this.zeroLineIndex : 0)) {
6429
+ // Draw the first index specially
6430
+ this.ctx.lineWidth = this.options.gridLines.zeroLineWidth;
6431
+ this.ctx.strokeStyle = this.options.gridLines.zeroLineColor;
6432
+ setContextLineSettings = true; // reset next time
6433
+ } else if (setContextLineSettings) {
6434
+ this.ctx.lineWidth = this.options.gridLines.lineWidth;
6435
+ this.ctx.strokeStyle = this.options.gridLines.color;
6436
+ setContextLineSettings = false;
6437
+ }
6438
+
6439
+ yLineValue += helpers.aliasPixel(this.ctx.lineWidth);
6440
+
6441
+ // Draw the label area
6442
+ this.ctx.beginPath();
6443
+
6444
+ if (this.options.gridLines.drawTicks) {
6445
+ this.ctx.moveTo(xTickStart, yLineValue);
6446
+ this.ctx.lineTo(xTickEnd, yLineValue);
6447
+ }
6448
+
6449
+ // Draw the chart area
6450
+ if (this.options.gridLines.drawOnChartArea) {
6451
+ this.ctx.moveTo(chartArea.left, yLineValue);
6452
+ this.ctx.lineTo(chartArea.right, yLineValue);
6453
+ }
6454
+
6455
+ // Need to stroke in the loop because we are potentially changing line widths & colours
6456
+ this.ctx.stroke();
6457
+ }
6458
+
6459
+ if (this.options.ticks.display) {
6460
+ var xLabelValue;
6461
+ var yLabelValue = this.getPixelForTick(index, this.options.gridLines.offsetGridLines); // x values for ticks (need to consider offsetLabel option)
6462
+
6463
+ this.ctx.save();
6464
+
6465
+ if (this.options.position === "left") {
6466
+ if (this.options.ticks.mirror) {
6467
+ xLabelValue = this.right + this.options.ticks.padding;
6468
+ this.ctx.textAlign = "left";
6469
+ } else {
6470
+ xLabelValue = this.right - this.options.ticks.padding;
6471
+ this.ctx.textAlign = "right";
6472
+ }
6473
+ } else {
6474
+ // right side
6475
+ if (this.options.ticks.mirror) {
6476
+ xLabelValue = this.left - this.options.ticks.padding;
6477
+ this.ctx.textAlign = "right";
6478
+ } else {
6479
+ xLabelValue = this.left + this.options.ticks.padding;
6480
+ this.ctx.textAlign = "left";
6481
+ }
6482
+ }
6483
+
6484
+ this.ctx.translate(xLabelValue, yLabelValue);
6485
+ this.ctx.rotate(helpers.toRadians(this.labelRotation) * -1);
6486
+ this.ctx.font = tickLabelFont;
6487
+ this.ctx.textBaseline = "middle";
6488
+ this.ctx.fillText(label, 0, 0);
6489
+ this.ctx.restore();
6490
+ }
6491
+ }, this);
6492
+
6493
+ if (this.options.scaleLabel.display) {
6494
+ // Draw the scale label
6495
+ scaleLabelX = this.options.position === 'left' ? this.left + (scaleLabelFontSize / 2) : this.right - (scaleLabelFontSize / 2);
6496
+ scaleLabelY = this.top + ((this.bottom - this.top) / 2);
6497
+ var rotation = this.options.position === 'left' ? -0.5 * Math.PI : 0.5 * Math.PI;
6498
+
6499
+ this.ctx.save();
6500
+ this.ctx.translate(scaleLabelX, scaleLabelY);
6501
+ this.ctx.rotate(rotation);
6502
+ this.ctx.textAlign = "center";
6503
+ this.ctx.fillStyle =scaleLabelFontColor; // render in correct colour
6504
+ this.ctx.font = scaleLabelFont;
6505
+ this.ctx.textBaseline = 'middle';
6506
+ this.ctx.fillText(this.options.scaleLabel.labelString, 0, 0);
6507
+ this.ctx.restore();
6508
+ }
6509
+ }
6510
+
6511
+ // Draw the line at the edge of the axis
6512
+ this.ctx.lineWidth = this.options.gridLines.lineWidth;
6513
+ this.ctx.strokeStyle = this.options.gridLines.color;
6514
+ var x1 = this.left,
6515
+ x2 = this.right,
6516
+ y1 = this.top,
6517
+ y2 = this.bottom;
6518
+
6519
+ if (this.isHorizontal()) {
6520
+ y1 = y2 = this.options.position === 'top' ? this.bottom : this.top;
6521
+ y1 += helpers.aliasPixel(this.ctx.lineWidth);
6522
+ y2 += helpers.aliasPixel(this.ctx.lineWidth);
6523
+ } else {
6524
+ x1 = x2 = this.options.position === 'left' ? this.right : this.left;
6525
+ x1 += helpers.aliasPixel(this.ctx.lineWidth);
6526
+ x2 += helpers.aliasPixel(this.ctx.lineWidth);
6527
+ }
6528
+
6529
+ this.ctx.beginPath();
6530
+ this.ctx.moveTo(x1, y1);
6531
+ this.ctx.lineTo(x2, y2);
6532
+ this.ctx.stroke();
6533
+ }
6534
+ }
6535
+ });
6536
+ };
6537
+
6538
+ },{}],30:[function(require,module,exports){
6539
+ "use strict";
6540
+
6541
+ module.exports = function(Chart) {
6542
+
6543
+ var helpers = Chart.helpers;
6544
+
6545
+ Chart.scaleService = {
6546
+ // Scale registration object. Extensions can register new scale types (such as log or DB scales) and then
6547
+ // use the new chart options to grab the correct scale
6548
+ constructors: {},
6549
+ // Use a registration function so that we can move to an ES6 map when we no longer need to support
6550
+ // old browsers
6551
+
6552
+ // Scale config defaults
6553
+ defaults: {},
6554
+ registerScaleType: function(type, scaleConstructor, defaults) {
6555
+ this.constructors[type] = scaleConstructor;
6556
+ this.defaults[type] = helpers.clone(defaults);
6557
+ },
6558
+ getScaleConstructor: function(type) {
6559
+ return this.constructors.hasOwnProperty(type) ? this.constructors[type] : undefined;
6560
+ },
6561
+ getScaleDefaults: function(type) {
6562
+ // Return the scale defaults merged with the global settings so that we always use the latest ones
6563
+ return this.defaults.hasOwnProperty(type) ? helpers.scaleMerge(Chart.defaults.scale, this.defaults[type]) : {};
6564
+ },
6565
+ addScalesToLayout: function(chartInstance) {
6566
+ // Adds each scale to the chart.boxes array to be sized accordingly
6567
+ helpers.each(chartInstance.scales, function(scale) {
6568
+ Chart.layoutService.addBox(chartInstance, scale);
6569
+ });
6570
+ }
6571
+ };
6572
+ };
6573
+ },{}],31:[function(require,module,exports){
6574
+ "use strict";
6575
+
6576
+ module.exports = function(Chart) {
6577
+
6578
+ var helpers = Chart.helpers;
6579
+
6580
+ Chart.defaults.global.title = {
6581
+ display: false,
6582
+ position: 'top',
6583
+ fullWidth: true, // marks that this box should take the full width of the canvas (pushing down other boxes)
6584
+
6585
+ fontStyle: 'bold',
6586
+ padding: 10,
6587
+
6588
+ // actual title
6589
+ text: ''
6590
+ };
6591
+
6592
+ Chart.Title = Chart.Element.extend({
6593
+
6594
+ initialize: function(config) {
6595
+ helpers.extend(this, config);
6596
+ this.options = helpers.configMerge(Chart.defaults.global.title, config.options);
6597
+
6598
+ // Contains hit boxes for each dataset (in dataset order)
6599
+ this.legendHitBoxes = [];
6600
+ },
6601
+
6602
+ // These methods are ordered by lifecyle. Utilities then follow.
6603
+
6604
+ beforeUpdate: helpers.noop,
6605
+ update: function(maxWidth, maxHeight, margins) {
6606
+
6607
+ // Update Lifecycle - Probably don't want to ever extend or overwrite this function ;)
6608
+ this.beforeUpdate();
6609
+
6610
+ // Absorb the master measurements
6611
+ this.maxWidth = maxWidth;
6612
+ this.maxHeight = maxHeight;
6613
+ this.margins = margins;
6614
+
6615
+ // Dimensions
6616
+ this.beforeSetDimensions();
6617
+ this.setDimensions();
6618
+ this.afterSetDimensions();
6619
+ // Labels
6620
+ this.beforeBuildLabels();
6621
+ this.buildLabels();
6622
+ this.afterBuildLabels();
6623
+
6624
+ // Fit
6625
+ this.beforeFit();
6626
+ this.fit();
6627
+ this.afterFit();
6628
+ //
6629
+ this.afterUpdate();
6630
+
6631
+ return this.minSize;
6632
+
6633
+ },
6634
+ afterUpdate: helpers.noop,
6635
+
6636
+ //
6637
+
6638
+ beforeSetDimensions: helpers.noop,
6639
+ setDimensions: function() {
6640
+ // Set the unconstrained dimension before label rotation
6641
+ if (this.isHorizontal()) {
6642
+ // Reset position before calculating rotation
6643
+ this.width = this.maxWidth;
6644
+ this.left = 0;
6645
+ this.right = this.width;
6646
+ } else {
6647
+ this.height = this.maxHeight;
6648
+
6649
+ // Reset position before calculating rotation
6650
+ this.top = 0;
6651
+ this.bottom = this.height;
6652
+ }
6653
+
6654
+ // Reset padding
6655
+ this.paddingLeft = 0;
6656
+ this.paddingTop = 0;
6657
+ this.paddingRight = 0;
6658
+ this.paddingBottom = 0;
6659
+
6660
+ // Reset minSize
6661
+ this.minSize = {
6662
+ width: 0,
6663
+ height: 0
6664
+ };
6665
+ },
6666
+ afterSetDimensions: helpers.noop,
6667
+
6668
+ //
6669
+
6670
+ beforeBuildLabels: helpers.noop,
6671
+ buildLabels: helpers.noop,
6672
+ afterBuildLabels: helpers.noop,
6673
+
6674
+ //
6675
+
6676
+ beforeFit: helpers.noop,
6677
+ fit: function() {
6678
+
6679
+ var ctx = this.ctx;
6680
+ var fontSize = helpers.getValueOrDefault(this.options.fontSize, Chart.defaults.global.defaultFontSize);
6681
+ var fontStyle = helpers.getValueOrDefault(this.options.fontStyle, Chart.defaults.global.defaultFontStyle);
6682
+ var fontFamily = helpers.getValueOrDefault(this.options.fontFamily, Chart.defaults.global.defaultFontFamily);
6683
+ var titleFont = helpers.fontString(fontSize, fontStyle, fontFamily);
6684
+
6685
+ // Width
6686
+ if (this.isHorizontal()) {
6687
+ this.minSize.width = this.maxWidth; // fill all the width
6688
+ } else {
6689
+ this.minSize.width = 0;
6690
+ }
6691
+
6692
+ // height
6693
+ if (this.isHorizontal()) {
6694
+ this.minSize.height = 0;
6695
+ } else {
6696
+ this.minSize.height = this.maxHeight; // fill all the height
6697
+ }
6698
+
6699
+ // Increase sizes here
6700
+ if (this.isHorizontal()) {
6701
+
6702
+ // Title
6703
+ if (this.options.display) {
6704
+ this.minSize.height += fontSize + (this.options.padding * 2);
6705
+ }
6706
+ } else {
6707
+ if (this.options.display) {
6708
+ this.minSize.width += fontSize + (this.options.padding * 2);
6709
+ }
6710
+ }
6711
+
6712
+ this.width = this.minSize.width;
6713
+ this.height = this.minSize.height;
6714
+
6715
+ },
6716
+ afterFit: helpers.noop,
6717
+
6718
+ // Shared Methods
6719
+ isHorizontal: function() {
6720
+ return this.options.position === "top" || this.options.position === "bottom";
6721
+ },
6722
+
6723
+ // Actualy draw the title block on the canvas
6724
+ draw: function() {
6725
+ if (this.options.display) {
6726
+ var ctx = this.ctx;
6727
+ var titleX, titleY;
6728
+
6729
+ var fontColor = helpers.getValueOrDefault(this.options.fontColor, Chart.defaults.global.defaultFontColor);
6730
+ var fontSize = helpers.getValueOrDefault(this.options.fontSize, Chart.defaults.global.defaultFontSize);
6731
+ var fontStyle = helpers.getValueOrDefault(this.options.fontStyle, Chart.defaults.global.defaultFontStyle);
6732
+ var fontFamily = helpers.getValueOrDefault(this.options.fontFamily, Chart.defaults.global.defaultFontFamily);
6733
+ var titleFont = helpers.fontString(fontSize, fontStyle, fontFamily);
6734
+
6735
+ ctx.fillStyle = fontColor; // render in correct colour
6736
+ ctx.font = titleFont;
6737
+
6738
+ // Horizontal
6739
+ if (this.isHorizontal()) {
6740
+ // Title
6741
+ ctx.textAlign = "center";
6742
+ ctx.textBaseline = 'middle';
6743
+
6744
+ titleX = this.left + ((this.right - this.left) / 2); // midpoint of the width
6745
+ titleY = this.top + ((this.bottom - this.top) / 2); // midpoint of the height
6746
+
6747
+ ctx.fillText(this.options.text, titleX, titleY);
6748
+ } else {
6749
+
6750
+ // Title
6751
+ titleX = this.options.position === 'left' ? this.left + (fontSize / 2) : this.right - (fontSize / 2);
6752
+ titleY = this.top + ((this.bottom - this.top) / 2);
6753
+ var rotation = this.options.position === 'left' ? -0.5 * Math.PI : 0.5 * Math.PI;
6754
+
6755
+ ctx.save();
6756
+ ctx.translate(titleX, titleY);
6757
+ ctx.rotate(rotation);
6758
+ ctx.textAlign = "center";
6759
+ ctx.textBaseline = 'middle';
6760
+ ctx.fillText(this.options.text, 0, 0);
6761
+ ctx.restore();
6762
+ }
6763
+ }
6764
+ }
6765
+ });
6766
+ };
6767
+ },{}],32:[function(require,module,exports){
6768
+ "use strict";
6769
+
6770
+ module.exports = function(Chart) {
6771
+
6772
+ var helpers = Chart.helpers;
6773
+
6774
+ Chart.defaults.global.tooltips = {
6775
+ enabled: true,
6776
+ custom: null,
6777
+ mode: 'single',
6778
+ backgroundColor: "rgba(0,0,0,0.8)",
6779
+ titleFontStyle: "bold",
6780
+ titleSpacing: 2,
6781
+ titleMarginBottom: 6,
6782
+ titleColor: "#fff",
6783
+ titleAlign: "left",
6784
+ bodySpacing: 2,
6785
+ bodyColor: "#fff",
6786
+ bodyAlign: "left",
6787
+ footerFontStyle: "bold",
6788
+ footerSpacing: 2,
6789
+ footerMarginTop: 6,
6790
+ footerColor: "#fff",
6791
+ footerAlign: "left",
6792
+ yPadding: 6,
6793
+ xPadding: 6,
6794
+ yAlign : 'center',
6795
+ xAlign : 'center',
6796
+ caretSize: 5,
6797
+ cornerRadius: 6,
6798
+ multiKeyBackground: '#fff',
6799
+ callbacks: {
6800
+ // Args are: (tooltipItems, data)
6801
+ beforeTitle: helpers.noop,
6802
+ title: function(tooltipItems, data) {
6803
+ // Pick first xLabel for now
6804
+ var title = '';
6805
+
6806
+ if (tooltipItems.length > 0) {
6807
+ if (tooltipItems[0].xLabel) {
6808
+ title = tooltipItems[0].xLabel;
6809
+ } else if (data.labels.length > 0 && tooltipItems[0].index < data.labels.length) {
6810
+ title = data.labels[tooltipItems[0].index];
6811
+ }
6812
+ }
6813
+
6814
+ return title;
6815
+ },
6816
+ afterTitle: helpers.noop,
6817
+
6818
+ // Args are: (tooltipItems, data)
6819
+ beforeBody: helpers.noop,
6820
+
6821
+ // Args are: (tooltipItem, data)
6822
+ beforeLabel: helpers.noop,
6823
+ label: function(tooltipItem, data) {
6824
+ var datasetLabel = data.datasets[tooltipItem.datasetIndex].label || '';
6825
+ return datasetLabel + ': ' + tooltipItem.yLabel;
6826
+ },
6827
+ afterLabel: helpers.noop,
6828
+
6829
+ // Args are: (tooltipItems, data)
6830
+ afterBody: helpers.noop,
6831
+
6832
+ // Args are: (tooltipItems, data)
6833
+ beforeFooter: helpers.noop,
6834
+ footer: helpers.noop,
6835
+ afterFooter: helpers.noop
6836
+ }
6837
+ };
6838
+
6839
+ // Helper to push or concat based on if the 2nd parameter is an array or not
6840
+ function pushOrConcat(base, toPush) {
6841
+ if (toPush) {
6842
+ if (helpers.isArray(toPush)) {
6843
+ base = base.concat(toPush);
6844
+ } else {
6845
+ base.push(toPush);
6846
+ }
6847
+ }
6848
+
6849
+ return base;
6850
+ }
6851
+
6852
+ Chart.Tooltip = Chart.Element.extend({
6853
+ initialize: function() {
6854
+ var options = this._options;
6855
+ helpers.extend(this, {
6856
+ _model: {
6857
+ // Positioning
6858
+ xPadding: options.tooltips.xPadding,
6859
+ yPadding: options.tooltips.yPadding,
6860
+ xAlign : options.tooltips.yAlign,
6861
+ yAlign : options.tooltips.xAlign,
6862
+
6863
+ // Body
6864
+ bodyColor: options.tooltips.bodyColor,
6865
+ _bodyFontFamily: helpers.getValueOrDefault(options.tooltips.bodyFontFamily, Chart.defaults.global.defaultFontFamily),
6866
+ _bodyFontStyle: helpers.getValueOrDefault(options.tooltips.bodyFontStyle, Chart.defaults.global.defaultFontStyle),
6867
+ _bodyAlign: options.tooltips.bodyAlign,
6868
+ bodyFontSize: helpers.getValueOrDefault(options.tooltips.bodyFontSize, Chart.defaults.global.defaultFontSize),
6869
+ bodySpacing: options.tooltips.bodySpacing,
6870
+
6871
+ // Title
6872
+ titleColor: options.tooltips.titleColor,
6873
+ _titleFontFamily: helpers.getValueOrDefault(options.tooltips.titleFontFamily, Chart.defaults.global.defaultFontFamily),
6874
+ _titleFontStyle: helpers.getValueOrDefault(options.tooltips.titleFontStyle, Chart.defaults.global.defaultFontStyle),
6875
+ titleFontSize: helpers.getValueOrDefault(options.tooltips.titleFontSize, Chart.defaults.global.defaultFontSize),
6876
+ _titleAlign: options.tooltips.titleAlign,
6877
+ titleSpacing: options.tooltips.titleSpacing,
6878
+ titleMarginBottom: options.tooltips.titleMarginBottom,
6879
+
6880
+ // Footer
6881
+ footerColor: options.tooltips.footerColor,
6882
+ _footerFontFamily: helpers.getValueOrDefault(options.tooltips.footerFontFamily, Chart.defaults.global.defaultFontFamily),
6883
+ _footerFontStyle: helpers.getValueOrDefault(options.tooltips.footerFontStyle, Chart.defaults.global.defaultFontStyle),
6884
+ footerFontSize: helpers.getValueOrDefault(options.tooltips.footerFontSize, Chart.defaults.global.defaultFontSize),
6885
+ _footerAlign: options.tooltips.footerAlign,
6886
+ footerSpacing: options.tooltips.footerSpacing,
6887
+ footerMarginTop: options.tooltips.footerMarginTop,
6888
+
6889
+ // Appearance
6890
+ caretSize: options.tooltips.caretSize,
6891
+ cornerRadius: options.tooltips.cornerRadius,
6892
+ backgroundColor: options.tooltips.backgroundColor,
6893
+ opacity: 0,
6894
+ legendColorBackground: options.tooltips.multiKeyBackground
6895
+ }
6896
+ });
6897
+ },
6898
+
6899
+ // Get the title
6900
+ // Args are: (tooltipItem, data)
6901
+ getTitle: function() {
6902
+ var beforeTitle = this._options.tooltips.callbacks.beforeTitle.apply(this, arguments),
6903
+ title = this._options.tooltips.callbacks.title.apply(this, arguments),
6904
+ afterTitle = this._options.tooltips.callbacks.afterTitle.apply(this, arguments);
6905
+
6906
+ var lines = [];
6907
+ lines = pushOrConcat(lines, beforeTitle);
6908
+ lines = pushOrConcat(lines, title);
6909
+ lines = pushOrConcat(lines, afterTitle);
6910
+
6911
+ return lines;
6912
+ },
6913
+
6914
+ // Args are: (tooltipItem, data)
6915
+ getBeforeBody: function() {
6916
+ var lines = this._options.tooltips.callbacks.beforeBody.apply(this, arguments);
6917
+ return helpers.isArray(lines) ? lines : lines !== undefined ? [lines] : [];
6918
+ },
6919
+
6920
+ // Args are: (tooltipItem, data)
6921
+ getBody: function(tooltipItems, data) {
6922
+ var lines = [];
6923
+
6924
+ helpers.each(tooltipItems, function(bodyItem) {
6925
+ helpers.pushAllIfDefined(this._options.tooltips.callbacks.beforeLabel.call(this, bodyItem, data), lines);
6926
+ helpers.pushAllIfDefined(this._options.tooltips.callbacks.label.call(this, bodyItem, data), lines);
6927
+ helpers.pushAllIfDefined(this._options.tooltips.callbacks.afterLabel.call(this, bodyItem, data), lines);
6928
+ }, this);
6929
+
6930
+ return lines;
6931
+ },
6932
+
6933
+ // Args are: (tooltipItem, data)
6934
+ getAfterBody: function() {
6935
+ var lines = this._options.tooltips.callbacks.afterBody.apply(this, arguments);
6936
+ return helpers.isArray(lines) ? lines : lines !== undefined ? [lines] : [];
6937
+ },
6938
+
6939
+ // Get the footer and beforeFooter and afterFooter lines
6940
+ // Args are: (tooltipItem, data)
6941
+ getFooter: function() {
6942
+ var beforeFooter = this._options.tooltips.callbacks.beforeFooter.apply(this, arguments);
6943
+ var footer = this._options.tooltips.callbacks.footer.apply(this, arguments);
6944
+ var afterFooter = this._options.tooltips.callbacks.afterFooter.apply(this, arguments);
6945
+
6946
+ var lines = [];
6947
+ lines = pushOrConcat(lines, beforeFooter);
6948
+ lines = pushOrConcat(lines, footer);
6949
+ lines = pushOrConcat(lines, afterFooter);
6950
+
6951
+ return lines;
6952
+ },
6953
+
6954
+ getAveragePosition: function(elements) {
6955
+
6956
+ if (!elements.length) {
6957
+ return false;
6958
+ }
6959
+
6960
+ var xPositions = [];
6961
+ var yPositions = [];
6962
+
6963
+ helpers.each(elements, function(el) {
6964
+ if (el) {
6965
+ var pos = el.tooltipPosition();
6966
+ xPositions.push(pos.x);
6967
+ yPositions.push(pos.y);
6968
+ }
6969
+ });
6970
+
6971
+ var x = 0,
6972
+ y = 0;
6973
+ for (var i = 0; i < xPositions.length; i++) {
6974
+ x += xPositions[i];
6975
+ y += yPositions[i];
6976
+ }
6977
+
6978
+ return {
6979
+ x: Math.round(x / xPositions.length),
6980
+ y: Math.round(y / xPositions.length)
6981
+ };
6982
+
6983
+ },
6984
+
6985
+ update: function(changed) {
6986
+ if (this._active.length) {
6987
+ this._model.opacity = 1;
6988
+
6989
+ var element = this._active[0],
6990
+ labelColors = [],
6991
+ tooltipPosition;
6992
+
6993
+ var tooltipItems = [];
6994
+
6995
+ if (this._options.tooltips.mode === 'single') {
6996
+ var yScale = element._yScale || element._scale; // handle radar || polarArea charts
6997
+ tooltipItems.push({
6998
+ xLabel: element._xScale ? element._xScale.getLabelForIndex(element._index, element._datasetIndex) : '',
6999
+ yLabel: yScale ? yScale.getLabelForIndex(element._index, element._datasetIndex) : '',
7000
+ index: element._index,
7001
+ datasetIndex: element._datasetIndex
7002
+ });
7003
+ tooltipPosition = this.getAveragePosition(this._active);
7004
+ } else {
7005
+ helpers.each(this._data.datasets, function(dataset, datasetIndex) {
7006
+ if (!helpers.isDatasetVisible(dataset)) {
7007
+ return;
7008
+ }
7009
+ var currentElement = dataset.metaData[element._index];
7010
+ if (currentElement) {
7011
+ var yScale = element._yScale || element._scale; // handle radar || polarArea charts
7012
+
7013
+ tooltipItems.push({
7014
+ xLabel: currentElement._xScale ? currentElement._xScale.getLabelForIndex(currentElement._index, currentElement._datasetIndex) : '',
7015
+ yLabel: yScale ? yScale.getLabelForIndex(currentElement._index, currentElement._datasetIndex) : '',
7016
+ index: element._index,
7017
+ datasetIndex: datasetIndex
7018
+ });
7019
+ }
7020
+ }, null, element._yScale.options.stacked);
7021
+
7022
+ helpers.each(this._active, function(active) {
7023
+ if (active) {
7024
+ labelColors.push({
7025
+ borderColor: active._view.borderColor,
7026
+ backgroundColor: active._view.backgroundColor
7027
+ });
7028
+ }
7029
+ }, null, element._yScale.options.stacked);
7030
+
7031
+ tooltipPosition = this.getAveragePosition(this._active);
7032
+ tooltipPosition.y = this._active[0]._yScale.getPixelForDecimal(0.5);
7033
+ }
7034
+
7035
+ // Build the Text Lines
7036
+ helpers.extend(this._model, {
7037
+ title: this.getTitle(tooltipItems, this._data),
7038
+ beforeBody: this.getBeforeBody(tooltipItems, this._data),
7039
+ body: this.getBody(tooltipItems, this._data),
7040
+ afterBody: this.getAfterBody(tooltipItems, this._data),
7041
+ footer: this.getFooter(tooltipItems, this._data)
7042
+ });
7043
+
7044
+ helpers.extend(this._model, {
7045
+ x: Math.round(tooltipPosition.x),
7046
+ y: Math.round(tooltipPosition.y),
7047
+ caretPadding: helpers.getValueOrDefault(tooltipPosition.padding, 2),
7048
+ labelColors: labelColors
7049
+ });
7050
+
7051
+ // We need to determine alignment of
7052
+ var tooltipSize = this.getTooltipSize(this._model);
7053
+ this.determineAlignment(tooltipSize); // Smart Tooltip placement to stay on the canvas
7054
+
7055
+ helpers.extend(this._model, this.getBackgroundPoint(this._model, tooltipSize));
7056
+ } else {
7057
+ this._model.opacity = 0;
7058
+ }
7059
+
7060
+ if (changed && this._options.tooltips.custom) {
7061
+ this._options.tooltips.custom.call(this, this._model);
7062
+ }
7063
+
7064
+ return this;
7065
+ },
7066
+ getTooltipSize: function getTooltipSize(vm) {
7067
+ var ctx = this._chart.ctx;
7068
+
7069
+ var size = {
7070
+ height: vm.yPadding * 2, // Tooltip Padding
7071
+ width: 0
7072
+ };
7073
+ var combinedBodyLength = vm.body.length + vm.beforeBody.length + vm.afterBody.length;
7074
+
7075
+ size.height += vm.title.length * vm.titleFontSize; // Title Lines
7076
+ size.height += (vm.title.length - 1) * vm.titleSpacing; // Title Line Spacing
7077
+ size.height += vm.title.length ? vm.titleMarginBottom : 0; // Title's bottom Margin
7078
+ size.height += combinedBodyLength * vm.bodyFontSize; // Body Lines
7079
+ size.height += combinedBodyLength ? (combinedBodyLength - 1) * vm.bodySpacing : 0; // Body Line Spacing
7080
+ size.height += vm.footer.length ? vm.footerMarginTop : 0; // Footer Margin
7081
+ size.height += vm.footer.length * (vm.footerFontSize); // Footer Lines
7082
+ size.height += vm.footer.length ? (vm.footer.length - 1) * vm.footerSpacing : 0; // Footer Line Spacing
7083
+
7084
+ // Width
7085
+ ctx.font = helpers.fontString(vm.titleFontSize, vm._titleFontStyle, vm._titleFontFamily);
7086
+ helpers.each(vm.title, function(line) {
7087
+ size.width = Math.max(size.width, ctx.measureText(line).width);
7088
+ });
7089
+
7090
+ ctx.font = helpers.fontString(vm.bodyFontSize, vm._bodyFontStyle, vm._bodyFontFamily);
7091
+ helpers.each(vm.beforeBody.concat(vm.afterBody), function(line) {
7092
+ size.width = Math.max(size.width, ctx.measureText(line).width);
7093
+ });
7094
+ helpers.each(vm.body, function(line) {
7095
+ size.width = Math.max(size.width, ctx.measureText(line).width + (this._options.tooltips.mode !== 'single' ? (vm.bodyFontSize + 2) : 0));
7096
+ }, this);
7097
+
7098
+ ctx.font = helpers.fontString(vm.footerFontSize, vm._footerFontStyle, vm._footerFontFamily);
7099
+ helpers.each(vm.footer, function(line) {
7100
+ size.width = Math.max(size.width, ctx.measureText(line).width);
7101
+ });
7102
+ size.width += 2 * vm.xPadding;
7103
+
7104
+ return size;
7105
+ },
7106
+ determineAlignment: function determineAlignment(size) {
7107
+ if (this._model.y < size.height) {
7108
+ this._model.yAlign = 'top';
7109
+ } else if (this._model.y > (this._chart.height - size.height)) {
7110
+ this._model.yAlign = 'bottom';
7111
+ }
7112
+
7113
+ var lf, rf; // functions to determine left, right alignment
7114
+ var olf, orf; // functions to determine if left/right alignment causes tooltip to go outside chart
7115
+ var yf; // function to get the y alignment if the tooltip goes outside of the left or right edges
7116
+ var _this = this;
7117
+ var midX = (this._chartInstance.chartArea.left + this._chartInstance.chartArea.right) / 2;
7118
+ var midY = (this._chartInstance.chartArea.top + this._chartInstance.chartArea.bottom) / 2;
7119
+
7120
+ if (this._model.yAlign === 'center') {
7121
+ lf = function(x) {
7122
+ return x <= midX;
7123
+ };
7124
+ rf = function(x) {
7125
+ return x > midX;
7126
+ };
7127
+ } else {
7128
+ lf = function(x) {
7129
+ return x <= (size.width / 2);
7130
+ };
7131
+ rf = function(x) {
7132
+ return x >= (_this._chart.width - (size.width / 2));
7133
+ };
7134
+ }
7135
+
7136
+ olf = function(x) {
7137
+ return x + size.width > _this._chart.width;
7138
+ };
7139
+ orf = function(x) {
7140
+ return x - size.width < 0;
7141
+ };
7142
+ yf = function(y) {
7143
+ return y <= midY ? 'top' : 'bottom';
7144
+ };
7145
+
7146
+ if (lf(this._model.x)) {
7147
+ this._model.xAlign = 'left';
7148
+
7149
+ // Is tooltip too wide and goes over the right side of the chart.?
7150
+ if (olf(this._model.x)) {
7151
+ this._model.xAlign = 'center';
7152
+ this._model.yAlign = yf(this._model.y);
7153
+ }
7154
+ } else if (rf(this._model.x)) {
7155
+ this._model.xAlign = 'right';
7156
+
7157
+ // Is tooltip too wide and goes outside left edge of canvas?
7158
+ if (orf(this._model.x)) {
7159
+ this._model.xAlign = 'center';
7160
+ this._model.yAlign = yf(this._model.y);
7161
+ }
7162
+ }
7163
+ },
7164
+ getBackgroundPoint: function getBackgroundPoint(vm, size) {
7165
+ // Background Position
7166
+ var pt = {
7167
+ x: vm.x,
7168
+ y: vm.y
7169
+ };
7170
+
7171
+ if (vm.xAlign === 'right') {
7172
+ pt.x -= size.width;
7173
+ } else if (vm.xAlign === 'center') {
7174
+ pt.x -= (size.width / 2);
7175
+ }
7176
+
7177
+ if (vm.yAlign === 'top') {
7178
+ pt.y += vm.caretPadding + vm.caretSize;
7179
+ } else if (vm.yAlign === 'bottom') {
7180
+ pt.y -= size.height + vm.caretPadding + vm.caretSize;
7181
+ } else {
7182
+ pt.y -= (size.height / 2);
7183
+ }
7184
+
7185
+ if (vm.yAlign === 'center') {
7186
+ if (vm.xAlign === 'left') {
7187
+ pt.x += vm.caretPadding + vm.caretSize;
7188
+ } else if (vm.xAlign === 'right') {
7189
+ pt.x -= vm.caretPadding + vm.caretSize;
7190
+ }
7191
+ } else {
7192
+ if (vm.xAlign === 'left') {
7193
+ pt.x -= vm.cornerRadius + vm.caretPadding;
7194
+ } else if (vm.xAlign === 'right') {
7195
+ pt.x += vm.cornerRadius + vm.caretPadding;
7196
+ }
7197
+ }
7198
+
7199
+ return pt;
7200
+ },
7201
+ drawCaret: function drawCaret(tooltipPoint, size, opacity, caretPadding) {
7202
+ var vm = this._view;
7203
+ var ctx = this._chart.ctx;
7204
+ var x1, x2, x3;
7205
+ var y1, y2, y3;
7206
+
7207
+ if (vm.yAlign === 'center') {
7208
+ // Left or right side
7209
+ if (vm.xAlign === 'left') {
7210
+ x1 = tooltipPoint.x;
7211
+ x2 = x1 - vm.caretSize;
7212
+ x3 = x1;
7213
+ } else {
7214
+ x1 = tooltipPoint.x + size.width;
7215
+ x2 = x1 + vm.caretSize;
7216
+ x3 = x1;
7217
+ }
7218
+
7219
+ y2 = tooltipPoint.y + (size.height / 2);
7220
+ y1 = y2 - vm.caretSize;
7221
+ y3 = y2 + vm.caretSize;
7222
+ } else {
7223
+ if (vm.xAlign === 'left') {
7224
+ x1 = tooltipPoint.x + vm.cornerRadius;
7225
+ x2 = x1 + vm.caretSize;
7226
+ x3 = x2 + vm.caretSize;
7227
+ } else if (vm.xAlign === 'right') {
7228
+ x1 = tooltipPoint.x + size.width - vm.cornerRadius;
7229
+ x2 = x1 - vm.caretSize;
7230
+ x3 = x2 - vm.caretSize;
7231
+ } else {
7232
+ x2 = tooltipPoint.x + (size.width / 2);
7233
+ x1 = x2 - vm.caretSize;
7234
+ x3 = x2 + vm.caretSize;
7235
+ }
7236
+
7237
+ if (vm.yAlign === 'top') {
7238
+ y1 = tooltipPoint.y;
7239
+ y2 = y1 - vm.caretSize;
7240
+ y3 = y1;
7241
+ } else {
7242
+ y1 = tooltipPoint.y + size.height;
7243
+ y2 = y1 + vm.caretSize;
7244
+ y3 = y1;
7245
+ }
7246
+ }
7247
+
7248
+ var bgColor = helpers.color(vm.backgroundColor);
7249
+ ctx.fillStyle = bgColor.alpha(opacity * bgColor.alpha()).rgbString();
7250
+ ctx.beginPath();
7251
+ ctx.moveTo(x1, y1);
7252
+ ctx.lineTo(x2, y2);
7253
+ ctx.lineTo(x3, y3);
7254
+ ctx.closePath();
7255
+ ctx.fill();
7256
+ },
7257
+ drawTitle: function drawTitle(pt, vm, ctx, opacity) {
7258
+ if (vm.title.length) {
7259
+ ctx.textAlign = vm._titleAlign;
7260
+ ctx.textBaseline = "top";
7261
+
7262
+ var titleColor = helpers.color(vm.titleColor);
7263
+ ctx.fillStyle = titleColor.alpha(opacity * titleColor.alpha()).rgbString();
7264
+ ctx.font = helpers.fontString(vm.titleFontSize, vm._titleFontStyle, vm._titleFontFamily);
7265
+
7266
+ helpers.each(vm.title, function(title, i) {
7267
+ ctx.fillText(title, pt.x, pt.y);
7268
+ pt.y += vm.titleFontSize + vm.titleSpacing; // Line Height and spacing
7269
+
7270
+ if (i + 1 === vm.title.length) {
7271
+ pt.y += vm.titleMarginBottom - vm.titleSpacing; // If Last, add margin, remove spacing
7272
+ }
7273
+ });
7274
+ }
7275
+ },
7276
+ drawBody: function drawBody(pt, vm, ctx, opacity) {
7277
+ ctx.textAlign = vm._bodyAlign;
7278
+ ctx.textBaseline = "top";
7279
+
7280
+ var bodyColor = helpers.color(vm.bodyColor);
7281
+ ctx.fillStyle = bodyColor.alpha(opacity * bodyColor.alpha()).rgbString();
7282
+ ctx.font = helpers.fontString(vm.bodyFontSize, vm._bodyFontStyle, vm._bodyFontFamily);
7283
+
7284
+ // Before Body
7285
+ helpers.each(vm.beforeBody, function(beforeBody) {
7286
+ ctx.fillText(beforeBody, pt.x, pt.y);
7287
+ pt.y += vm.bodyFontSize + vm.bodySpacing;
7288
+ });
7289
+
7290
+ helpers.each(vm.body, function(body, i) {
7291
+ // Draw Legend-like boxes if needed
7292
+ if (this._options.tooltips.mode !== 'single') {
7293
+ // Fill a white rect so that colours merge nicely if the opacity is < 1
7294
+ ctx.fillStyle = helpers.color(vm.legendColorBackground).alpha(opacity).rgbaString();
7295
+ ctx.fillRect(pt.x, pt.y, vm.bodyFontSize, vm.bodyFontSize);
7296
+
7297
+ // Border
7298
+ ctx.strokeStyle = helpers.color(vm.labelColors[i].borderColor).alpha(opacity).rgbaString();
7299
+ ctx.strokeRect(pt.x, pt.y, vm.bodyFontSize, vm.bodyFontSize);
7300
+
7301
+ // Inner square
7302
+ ctx.fillStyle = helpers.color(vm.labelColors[i].backgroundColor).alpha(opacity).rgbaString();
7303
+ ctx.fillRect(pt.x + 1, pt.y + 1, vm.bodyFontSize - 2, vm.bodyFontSize - 2);
7304
+
7305
+ ctx.fillStyle = helpers.color(vm.bodyColor).alpha(opacity).rgbaString(); // Return fill style for text
7306
+ }
7307
+
7308
+ // Body Line
7309
+ ctx.fillText(body, pt.x + (this._options.tooltips.mode !== 'single' ? (vm.bodyFontSize + 2) : 0), pt.y);
7310
+
7311
+ pt.y += vm.bodyFontSize + vm.bodySpacing;
7312
+ }, this);
7313
+
7314
+ // After Body
7315
+ helpers.each(vm.afterBody, function(afterBody) {
7316
+ ctx.fillText(afterBody, pt.x, pt.y);
7317
+ pt.y += vm.bodyFontSize;
7318
+ });
7319
+
7320
+ pt.y -= vm.bodySpacing; // Remove last body spacing
7321
+ },
7322
+ drawFooter: function drawFooter(pt, vm, ctx, opacity) {
7323
+ if (vm.footer.length) {
7324
+ pt.y += vm.footerMarginTop;
7325
+
7326
+ ctx.textAlign = vm._footerAlign;
7327
+ ctx.textBaseline = "top";
7328
+
7329
+ var footerColor = helpers.color(vm.footerColor);
7330
+ ctx.fillStyle = footerColor.alpha(opacity * footerColor.alpha()).rgbString();
7331
+ ctx.font = helpers.fontString(vm.footerFontSize, vm._footerFontStyle, vm._footerFontFamily);
7332
+
7333
+ helpers.each(vm.footer, function(footer) {
7334
+ ctx.fillText(footer, pt.x, pt.y);
7335
+ pt.y += vm.footerFontSize + vm.footerSpacing;
7336
+ });
7337
+ }
7338
+ },
7339
+ draw: function draw() {
7340
+ var ctx = this._chart.ctx;
7341
+ var vm = this._view;
7342
+
7343
+ if (vm.opacity === 0) {
7344
+ return;
7345
+ }
7346
+
7347
+ var caretPadding = vm.caretPadding;
7348
+ var tooltipSize = this.getTooltipSize(vm);
7349
+ var pt = {
7350
+ x: vm.x,
7351
+ y: vm.y
7352
+ };
7353
+
7354
+ // IE11/Edge does not like very small opacities, so snap to 0
7355
+ var opacity = Math.abs(vm.opacity < 1e-3) ? 0 : vm.opacity;
7356
+
7357
+ if (this._options.tooltips.enabled) {
7358
+ // Draw Background
7359
+ var bgColor = helpers.color(vm.backgroundColor);
7360
+ ctx.fillStyle = bgColor.alpha(opacity * bgColor.alpha()).rgbString();
7361
+ helpers.drawRoundedRectangle(ctx, pt.x, pt.y, tooltipSize.width, tooltipSize.height, vm.cornerRadius);
7362
+ ctx.fill();
7363
+
7364
+ // Draw Caret
7365
+ this.drawCaret(pt, tooltipSize, opacity, caretPadding);
7366
+
7367
+ // Draw Title, Body, and Footer
7368
+ pt.x += vm.xPadding;
7369
+ pt.y += vm.yPadding;
7370
+
7371
+ // Titles
7372
+ this.drawTitle(pt, vm, ctx, opacity);
7373
+
7374
+ // Body
7375
+ this.drawBody(pt, vm, ctx, opacity);
7376
+
7377
+ // Footer
7378
+ this.drawFooter(pt, vm, ctx, opacity);
7379
+ }
7380
+ }
7381
+ });
7382
+ };
7383
+
7384
+ },{}],33:[function(require,module,exports){
7385
+ "use strict";
7386
+
7387
+ module.exports = function(Chart, moment) {
7388
+
7389
+ var helpers = Chart.helpers;
7390
+
7391
+ Chart.defaults.global.elements.arc = {
7392
+ backgroundColor: Chart.defaults.global.defaultColor,
7393
+ borderColor: "#fff",
7394
+ borderWidth: 2
7395
+ };
7396
+
7397
+ Chart.elements.Arc = Chart.Element.extend({
7398
+ inLabelRange: function(mouseX) {
7399
+ var vm = this._view;
7400
+
7401
+ if (vm) {
7402
+ return (Math.pow(mouseX - vm.x, 2) < Math.pow(vm.radius + vm.hoverRadius, 2));
7403
+ } else {
7404
+ return false;
7405
+ }
7406
+ },
7407
+ inRange: function(chartX, chartY) {
7408
+
7409
+ var vm = this._view;
7410
+
7411
+ if (vm) {
7412
+ var pointRelativePosition = helpers.getAngleFromPoint(vm, {
7413
+ x: chartX,
7414
+ y: chartY
7415
+ });
7416
+
7417
+ //Sanitise angle range
7418
+ var startAngle = vm.startAngle;
7419
+ var endAngle = vm.endAngle;
7420
+ while (endAngle < startAngle) {
7421
+ endAngle += 2.0 * Math.PI;
7422
+ }
7423
+ while (pointRelativePosition.angle > endAngle) {
7424
+ pointRelativePosition.angle -= 2.0 * Math.PI;
7425
+ }
7426
+ while (pointRelativePosition.angle < startAngle) {
7427
+ pointRelativePosition.angle += 2.0 * Math.PI;
7428
+ }
7429
+
7430
+ //Check if within the range of the open/close angle
7431
+ var betweenAngles = (pointRelativePosition.angle >= startAngle && pointRelativePosition.angle <= endAngle),
7432
+ withinRadius = (pointRelativePosition.distance >= vm.innerRadius && pointRelativePosition.distance <= vm.outerRadius);
7433
+
7434
+ return (betweenAngles && withinRadius);
7435
+ } else {
7436
+ return false;
7437
+ }
7438
+ },
7439
+ tooltipPosition: function() {
7440
+ var vm = this._view;
7441
+
7442
+ var centreAngle = vm.startAngle + ((vm.endAngle - vm.startAngle) / 2),
7443
+ rangeFromCentre = (vm.outerRadius - vm.innerRadius) / 2 + vm.innerRadius;
7444
+ return {
7445
+ x: vm.x + (Math.cos(centreAngle) * rangeFromCentre),
7446
+ y: vm.y + (Math.sin(centreAngle) * rangeFromCentre)
7447
+ };
7448
+ },
7449
+ draw: function() {
7450
+
7451
+ var ctx = this._chart.ctx;
7452
+ var vm = this._view;
7453
+
7454
+ ctx.beginPath();
7455
+
7456
+ ctx.arc(vm.x, vm.y, vm.outerRadius, vm.startAngle, vm.endAngle);
7457
+
7458
+ ctx.arc(vm.x, vm.y, vm.innerRadius, vm.endAngle, vm.startAngle, true);
7459
+
7460
+ ctx.closePath();
7461
+ ctx.strokeStyle = vm.borderColor;
7462
+ ctx.lineWidth = vm.borderWidth;
7463
+
7464
+ ctx.fillStyle = vm.backgroundColor;
7465
+
7466
+ ctx.fill();
7467
+ ctx.lineJoin = 'bevel';
7468
+
7469
+ if (vm.borderWidth) {
7470
+ ctx.stroke();
7471
+ }
7472
+ }
7473
+ });
7474
+ };
7475
+
7476
+ },{}],34:[function(require,module,exports){
7477
+ "use strict";
7478
+
7479
+ module.exports = function(Chart) {
7480
+
7481
+ var helpers = Chart.helpers;
7482
+
7483
+ Chart.defaults.global.elements.line = {
7484
+ tension: 0.4,
7485
+ backgroundColor: Chart.defaults.global.defaultColor,
7486
+ borderWidth: 3,
7487
+ borderColor: Chart.defaults.global.defaultColor,
7488
+ borderCapStyle: 'butt',
7489
+ borderDash: [],
7490
+ borderDashOffset: 0.0,
7491
+ borderJoinStyle: 'miter',
7492
+ fill: true // do we fill in the area between the line and its base axis
7493
+ };
7494
+
7495
+ Chart.elements.Line = Chart.Element.extend({
7496
+ lineToNextPoint: function(previousPoint, point, nextPoint, skipHandler, previousSkipHandler) {
7497
+ var ctx = this._chart.ctx;
7498
+
7499
+ if (point._view.skip) {
7500
+ skipHandler.call(this, previousPoint, point, nextPoint);
7501
+ } else if (previousPoint._view.skip) {
7502
+ previousSkipHandler.call(this, previousPoint, point, nextPoint);
7503
+ } else if (point._view.tension === 0) {
7504
+ ctx.lineTo(point._view.x, point._view.y);
7505
+ } else {
7506
+ // Line between points
7507
+ ctx.bezierCurveTo(
7508
+ previousPoint._view.controlPointNextX,
7509
+ previousPoint._view.controlPointNextY,
7510
+ point._view.controlPointPreviousX,
7511
+ point._view.controlPointPreviousY,
7512
+ point._view.x,
7513
+ point._view.y
7514
+ );
7515
+ }
7516
+ },
7517
+
7518
+ draw: function() {
7519
+ var _this = this;
7520
+
7521
+ var vm = this._view;
7522
+ var ctx = this._chart.ctx;
7523
+ var first = this._children[0];
7524
+ var last = this._children[this._children.length - 1];
7525
+
7526
+ function loopBackToStart(drawLineToCenter) {
7527
+ if (!first._view.skip && !last._view.skip) {
7528
+ // Draw a bezier line from last to first
7529
+ ctx.bezierCurveTo(
7530
+ last._view.controlPointNextX,
7531
+ last._view.controlPointNextY,
7532
+ first._view.controlPointPreviousX,
7533
+ first._view.controlPointPreviousY,
7534
+ first._view.x,
7535
+ first._view.y
7536
+ );
7537
+ } else if (drawLineToCenter) {
7538
+ // Go to center
7539
+ ctx.lineTo(_this._view.scaleZero.x, _this._view.scaleZero.y);
7540
+ }
7541
+ }
7542
+
7543
+ ctx.save();
7544
+
7545
+ // If we had points and want to fill this line, do so.
7546
+ if (this._children.length > 0 && vm.fill) {
7547
+ // Draw the background first (so the border is always on top)
7548
+ ctx.beginPath();
7549
+
7550
+ helpers.each(this._children, function(point, index) {
7551
+ var previous = helpers.previousItem(this._children, index);
7552
+ var next = helpers.nextItem(this._children, index);
7553
+
7554
+ // First point moves to it's starting position no matter what
7555
+ if (index === 0) {
7556
+ if (this._loop) {
7557
+ ctx.moveTo(vm.scaleZero.x, vm.scaleZero.y);
7558
+ } else {
7559
+ ctx.moveTo(point._view.x, vm.scaleZero);
7560
+ }
7561
+
7562
+ if (point._view.skip) {
7563
+ if (!this._loop) {
7564
+ ctx.moveTo(next._view.x, this._view.scaleZero);
7565
+ }
7566
+ } else {
7567
+ ctx.lineTo(point._view.x, point._view.y);
7568
+ }
7569
+ } else {
7570
+ this.lineToNextPoint(previous, point, next, function(previousPoint, point, nextPoint) {
7571
+ if (this._loop) {
7572
+ // Go to center
7573
+ ctx.lineTo(this._view.scaleZero.x, this._view.scaleZero.y);
7574
+ } else {
7575
+ ctx.lineTo(previousPoint._view.x, this._view.scaleZero);
7576
+ ctx.moveTo(nextPoint._view.x, this._view.scaleZero);
7577
+ }
7578
+ }, function(previousPoint, point) {
7579
+ // If we skipped the last point, draw a line to ourselves so that the fill is nice
7580
+ ctx.lineTo(point._view.x, point._view.y);
7581
+ });
7582
+ }
7583
+ }, this);
7584
+
7585
+ // For radial scales, loop back around to the first point
7586
+ if (this._loop) {
7587
+ loopBackToStart(true);
7588
+ } else {
7589
+ //Round off the line by going to the base of the chart, back to the start, then fill.
7590
+ ctx.lineTo(this._children[this._children.length - 1]._view.x, vm.scaleZero);
7591
+ ctx.lineTo(this._children[0]._view.x, vm.scaleZero);
7592
+ }
7593
+
7594
+ ctx.fillStyle = vm.backgroundColor || Chart.defaults.global.defaultColor;
7595
+ ctx.closePath();
7596
+ ctx.fill();
7597
+ }
7598
+
7599
+ // Now draw the line between all the points with any borders
7600
+ ctx.lineCap = vm.borderCapStyle || Chart.defaults.global.elements.line.borderCapStyle;
7601
+
7602
+ // IE 9 and 10 do not support line dash
7603
+ if (ctx.setLineDash) {
7604
+ ctx.setLineDash(vm.borderDash || Chart.defaults.global.elements.line.borderDash);
7605
+ }
7606
+
7607
+ ctx.lineDashOffset = vm.borderDashOffset || Chart.defaults.global.elements.line.borderDashOffset;
7608
+ ctx.lineJoin = vm.borderJoinStyle || Chart.defaults.global.elements.line.borderJoinStyle;
7609
+ ctx.lineWidth = vm.borderWidth || Chart.defaults.global.elements.line.borderWidth;
7610
+ ctx.strokeStyle = vm.borderColor || Chart.defaults.global.defaultColor;
7611
+ ctx.beginPath();
7612
+
7613
+ helpers.each(this._children, function(point, index) {
7614
+ var previous = helpers.previousItem(this._children, index);
7615
+ var next = helpers.nextItem(this._children, index);
7616
+
7617
+ if (index === 0) {
7618
+ ctx.moveTo(point._view.x, point._view.y);
7619
+ } else {
7620
+ this.lineToNextPoint(previous, point, next, function(previousPoint, point, nextPoint) {
7621
+ ctx.moveTo(nextPoint._view.x, nextPoint._view.y);
7622
+ }, function(previousPoint, point) {
7623
+ // If we skipped the last point, move up to our point preventing a line from being drawn
7624
+ ctx.moveTo(point._view.x, point._view.y);
7625
+ });
7626
+ }
7627
+ }, this);
7628
+
7629
+ if (this._loop && this._children.length > 0) {
7630
+ loopBackToStart();
7631
+ }
7632
+
7633
+ ctx.stroke();
7634
+ ctx.restore();
7635
+ }
7636
+ });
7637
+ };
7638
+ },{}],35:[function(require,module,exports){
7639
+ "use strict";
7640
+
7641
+ module.exports = function(Chart) {
7642
+
7643
+ var helpers = Chart.helpers;
7644
+
7645
+ Chart.defaults.global.elements.point = {
7646
+ radius: 3,
7647
+ pointStyle: 'circle',
7648
+ backgroundColor: Chart.defaults.global.defaultColor,
7649
+ borderWidth: 1,
7650
+ borderColor: Chart.defaults.global.defaultColor,
7651
+ // Hover
7652
+ hitRadius: 1,
7653
+ hoverRadius: 4,
7654
+ hoverBorderWidth: 1
7655
+ };
7656
+
7657
+
7658
+ Chart.elements.Point = Chart.Element.extend({
7659
+ inRange: function(mouseX, mouseY) {
7660
+ var vm = this._view;
7661
+
7662
+ if (vm) {
7663
+ var hoverRange = vm.hitRadius + vm.radius;
7664
+ return ((Math.pow(mouseX - vm.x, 2) + Math.pow(mouseY - vm.y, 2)) < Math.pow(hoverRange, 2));
7665
+ } else {
7666
+ return false;
7667
+ }
7668
+ },
7669
+ inLabelRange: function(mouseX) {
7670
+ var vm = this._view;
7671
+
7672
+ if (vm) {
7673
+ return (Math.pow(mouseX - vm.x, 2) < Math.pow(vm.radius + vm.hitRadius, 2));
7674
+ } else {
7675
+ return false;
7676
+ }
7677
+ },
7678
+ tooltipPosition: function() {
7679
+ var vm = this._view;
7680
+ return {
7681
+ x: vm.x,
7682
+ y: vm.y,
7683
+ padding: vm.radius + vm.borderWidth
7684
+ };
7685
+ },
7686
+ draw: function() {
7687
+
7688
+ var vm = this._view;
7689
+ var ctx = this._chart.ctx;
7690
+
7691
+
7692
+ if (vm.skip) {
7693
+ return;
7694
+ }
7695
+
7696
+ if (typeof vm.pointStyle === 'object' && ((vm.pointStyle.toString() === '[object HTMLImageElement]') || (vm.pointStyle.toString() === '[object HTMLCanvasElement]'))) {
7697
+ ctx.drawImage(vm.pointStyle, vm.x - vm.pointStyle.width / 2, vm.y - vm.pointStyle.height / 2);
7698
+ return;
7699
+ }
7700
+
7701
+ if (!isNaN(vm.radius) && vm.radius > 0) {
7702
+
7703
+ ctx.strokeStyle = vm.borderColor || Chart.defaults.global.defaultColor;
7704
+ ctx.lineWidth = helpers.getValueOrDefault(vm.borderWidth, Chart.defaults.global.elements.point.borderWidth);
7705
+
7706
+ ctx.fillStyle = vm.backgroundColor || Chart.defaults.global.defaultColor;
7707
+
7708
+ var radius = vm.radius;
7709
+
7710
+ var xOffset;
7711
+ var yOffset;
7712
+
7713
+ switch (vm.pointStyle) {
7714
+ // Default includes circle
7715
+ default: ctx.beginPath();
7716
+ ctx.arc(vm.x, vm.y, radius, 0, Math.PI * 2);
7717
+ ctx.closePath();
7718
+ ctx.fill();
7719
+ break;
7720
+ case 'triangle':
7721
+ ctx.beginPath();
7722
+ var edgeLength = 3 * radius / Math.sqrt(3);
7723
+ var height = edgeLength * Math.sqrt(3) / 2;
7724
+ ctx.moveTo(vm.x - edgeLength / 2, vm.y + height / 3);
7725
+ ctx.lineTo(vm.x + edgeLength / 2, vm.y + height / 3);
7726
+ ctx.lineTo(vm.x, vm.y - 2 * height / 3);
7727
+ ctx.closePath();
7728
+ ctx.fill();
7729
+ break;
7730
+ case 'rect':
7731
+ ctx.fillRect(vm.x - 1 / Math.SQRT2 * radius, vm.y - 1 / Math.SQRT2 * radius, 2 / Math.SQRT2 * radius, 2 / Math.SQRT2 * radius);
7732
+ ctx.strokeRect(vm.x - 1 / Math.SQRT2 * radius, vm.y - 1 / Math.SQRT2 * radius, 2 / Math.SQRT2 * radius, 2 / Math.SQRT2 * radius);
7733
+ break;
7734
+ case 'rectRot':
7735
+ ctx.translate(vm.x, vm.y);
7736
+ ctx.rotate(Math.PI / 4);
7737
+ ctx.fillRect(-1 / Math.SQRT2 * radius, -1 / Math.SQRT2 * radius, 2 / Math.SQRT2 * radius, 2 / Math.SQRT2 * radius);
7738
+ ctx.strokeRect(-1 / Math.SQRT2 * radius, -1 / Math.SQRT2 * radius, 2 / Math.SQRT2 * radius, 2 / Math.SQRT2 * radius);
7739
+ ctx.setTransform(1, 0, 0, 1, 0, 0);
7740
+ break;
7741
+ case 'cross':
7742
+ ctx.beginPath();
7743
+ ctx.moveTo(vm.x, vm.y + radius);
7744
+ ctx.lineTo(vm.x, vm.y - radius);
7745
+ ctx.moveTo(vm.x - radius, vm.y);
7746
+ ctx.lineTo(vm.x + radius, vm.y);
7747
+ ctx.closePath();
7748
+ break;
7749
+ case 'crossRot':
7750
+ ctx.beginPath();
7751
+ xOffset = Math.cos(Math.PI / 4) * radius;
7752
+ yOffset = Math.sin(Math.PI / 4) * radius;
7753
+ ctx.moveTo(vm.x - xOffset, vm.y - yOffset);
7754
+ ctx.lineTo(vm.x + xOffset, vm.y + yOffset);
7755
+ ctx.moveTo(vm.x - xOffset, vm.y + yOffset);
7756
+ ctx.lineTo(vm.x + xOffset, vm.y - yOffset);
7757
+ ctx.closePath();
7758
+ break;
7759
+ case 'star':
7760
+ ctx.beginPath();
7761
+ ctx.moveTo(vm.x, vm.y + radius);
7762
+ ctx.lineTo(vm.x, vm.y - radius);
7763
+ ctx.moveTo(vm.x - radius, vm.y);
7764
+ ctx.lineTo(vm.x + radius, vm.y);
7765
+ xOffset = Math.cos(Math.PI / 4) * radius;
7766
+ yOffset = Math.sin(Math.PI / 4) * radius;
7767
+ ctx.moveTo(vm.x - xOffset, vm.y - yOffset);
7768
+ ctx.lineTo(vm.x + xOffset, vm.y + yOffset);
7769
+ ctx.moveTo(vm.x - xOffset, vm.y + yOffset);
7770
+ ctx.lineTo(vm.x + xOffset, vm.y - yOffset);
7771
+ ctx.closePath();
7772
+ break;
7773
+ case 'line':
7774
+ ctx.beginPath();
7775
+ ctx.moveTo(vm.x - radius, vm.y);
7776
+ ctx.lineTo(vm.x + radius, vm.y);
7777
+ ctx.closePath();
7778
+ break;
7779
+ case 'dash':
7780
+ ctx.beginPath();
7781
+ ctx.moveTo(vm.x, vm.y);
7782
+ ctx.lineTo(vm.x + radius, vm.y);
7783
+ ctx.closePath();
7784
+ break;
7785
+ }
7786
+
7787
+ ctx.stroke();
7788
+ }
7789
+ }
7790
+ });
7791
+ };
7792
+ },{}],36:[function(require,module,exports){
7793
+ "use strict";
7794
+
7795
+ module.exports = function(Chart) {
7796
+
7797
+ var helpers = Chart.helpers;
7798
+
7799
+ Chart.defaults.global.elements.rectangle = {
7800
+ backgroundColor: Chart.defaults.global.defaultColor,
7801
+ borderWidth: 0,
7802
+ borderColor: Chart.defaults.global.defaultColor,
7803
+ borderSkipped: 'bottom'
7804
+ };
7805
+
7806
+ Chart.elements.Rectangle = Chart.Element.extend({
7807
+ draw: function() {
7808
+
7809
+ var ctx = this._chart.ctx;
7810
+ var vm = this._view;
7811
+
7812
+ var halfWidth = vm.width / 2,
7813
+ leftX = vm.x - halfWidth,
7814
+ rightX = vm.x + halfWidth,
7815
+ top = vm.base - (vm.base - vm.y),
7816
+ halfStroke = vm.borderWidth / 2;
7817
+
7818
+ // Canvas doesn't allow us to stroke inside the width so we can
7819
+ // adjust the sizes to fit if we're setting a stroke on the line
7820
+ if (vm.borderWidth) {
7821
+ leftX += halfStroke;
7822
+ rightX -= halfStroke;
7823
+ top += halfStroke;
7824
+ }
7825
+
7826
+ ctx.beginPath();
7827
+
7828
+ ctx.fillStyle = vm.backgroundColor;
7829
+ ctx.strokeStyle = vm.borderColor;
7830
+ ctx.lineWidth = vm.borderWidth;
7831
+
7832
+ // Corner points, from bottom-left to bottom-right clockwise
7833
+ // | 1 2 |
7834
+ // | 0 3 |
7835
+ var corners = [
7836
+ [leftX, vm.base],
7837
+ [leftX, top],
7838
+ [rightX, top],
7839
+ [rightX, vm.base]
7840
+ ];
7841
+
7842
+ // Find first (starting) corner with fallback to 'bottom'
7843
+ var borders = ['bottom', 'left', 'top', 'right'];
7844
+ var startCorner = borders.indexOf(vm.borderSkipped, 0);
7845
+ if (startCorner === -1)
7846
+ startCorner = 0;
7847
+
7848
+ function cornerAt(index) {
7849
+ return corners[(startCorner + index) % 4];
7850
+ }
7851
+
7852
+ // Draw rectangle from 'startCorner'
7853
+ ctx.moveTo.apply(ctx, cornerAt(0));
7854
+ for (var i = 1; i < 4; i++)
7855
+ ctx.lineTo.apply(ctx, cornerAt(i));
7856
+
7857
+ ctx.fill();
7858
+ if (vm.borderWidth) {
7859
+ ctx.stroke();
7860
+ }
7861
+ },
7862
+ height: function() {
7863
+ var vm = this._view;
7864
+ return vm.base - vm.y;
7865
+ },
7866
+ inRange: function(mouseX, mouseY) {
7867
+ var vm = this._view;
7868
+ var inRange = false;
7869
+
7870
+ if (vm) {
7871
+ if (vm.y < vm.base) {
7872
+ inRange = (mouseX >= vm.x - vm.width / 2 && mouseX <= vm.x + vm.width / 2) && (mouseY >= vm.y && mouseY <= vm.base);
7873
+ } else {
7874
+ inRange = (mouseX >= vm.x - vm.width / 2 && mouseX <= vm.x + vm.width / 2) && (mouseY >= vm.base && mouseY <= vm.y);
7875
+ }
7876
+ }
7877
+
7878
+ return inRange;
7879
+ },
7880
+ inLabelRange: function(mouseX) {
7881
+ var vm = this._view;
7882
+
7883
+ if (vm) {
7884
+ return (mouseX >= vm.x - vm.width / 2 && mouseX <= vm.x + vm.width / 2);
7885
+ } else {
7886
+ return false;
7887
+ }
7888
+ },
7889
+ tooltipPosition: function() {
7890
+ var vm = this._view;
7891
+ return {
7892
+ x: vm.x,
7893
+ y: vm.y
7894
+ };
7895
+ }
7896
+ });
7897
+
7898
+ };
7899
+ },{}],37:[function(require,module,exports){
7900
+ "use strict";
7901
+
7902
+ module.exports = function(Chart) {
7903
+
7904
+ var helpers = Chart.helpers;
7905
+ // Default config for a category scale
7906
+ var defaultConfig = {
7907
+ position: "bottom"
7908
+ };
7909
+
7910
+ var DatasetScale = Chart.Scale.extend({
7911
+ buildTicks: function(index) {
7912
+ this.startIndex = 0;
7913
+ this.endIndex = this.chart.data.labels.length;
7914
+ var findIndex;
7915
+
7916
+ if (this.options.ticks.min !== undefined) {
7917
+ // user specified min value
7918
+ findIndex = helpers.indexOf(this.chart.data.labels, this.options.ticks.min);
7919
+ this.startIndex = findIndex !== -1 ? findIndex : this.startIndex;
7920
+ }
7921
+
7922
+ if (this.options.ticks.max !== undefined) {
7923
+ // user specified max value
7924
+ findIndex = helpers.indexOf(this.chart.data.labels, this.options.ticks.max);
7925
+ this.endIndex = findIndex !== -1 ? findIndex : this.endIndex;
7926
+ }
7927
+
7928
+ // If we are viewing some subset of labels, slice the original array
7929
+ this.ticks = (this.startIndex === 0 && this.endIndex === this.chart.data.labels.length) ? this.chart.data.labels : this.chart.data.labels.slice(this.startIndex, this.endIndex + 1);
7930
+ },
7931
+
7932
+ getLabelForIndex: function(index, datasetIndex) {
7933
+ return this.ticks[index];
7934
+ },
7935
+
7936
+ // Used to get data value locations. Value can either be an index or a numerical value
7937
+ getPixelForValue: function(value, index, datasetIndex, includeOffset) {
7938
+ // 1 is added because we need the length but we have the indexes
7939
+ var offsetAmt = Math.max((this.ticks.length - ((this.options.gridLines.offsetGridLines) ? 0 : 1)), 1);
7940
+
7941
+ if (this.isHorizontal()) {
7942
+ var innerWidth = this.width - (this.paddingLeft + this.paddingRight);
7943
+ var valueWidth = innerWidth / offsetAmt;
7944
+ var widthOffset = (valueWidth * (index - this.startIndex)) + this.paddingLeft;
7945
+
7946
+ if (this.options.gridLines.offsetGridLines && includeOffset) {
7947
+ widthOffset += (valueWidth / 2);
7948
+ }
7949
+
7950
+ return this.left + Math.round(widthOffset);
7951
+ } else {
7952
+ var innerHeight = this.height - (this.paddingTop + this.paddingBottom);
7953
+ var valueHeight = innerHeight / offsetAmt;
7954
+ var heightOffset = (valueHeight * (index - this.startIndex)) + this.paddingTop;
7955
+
7956
+ if (this.options.gridLines.offsetGridLines && includeOffset) {
7957
+ heightOffset += (valueHeight / 2);
7958
+ }
7959
+
7960
+ return this.top + Math.round(heightOffset);
7961
+ }
7962
+ },
7963
+ getPixelForTick: function(index, includeOffset) {
7964
+ return this.getPixelForValue(this.ticks[index], index + this.startIndex, null, includeOffset);
7965
+ }
7966
+ });
7967
+
7968
+ Chart.scaleService.registerScaleType("category", DatasetScale, defaultConfig);
7969
+
7970
+ };
7971
+ },{}],38:[function(require,module,exports){
7972
+ "use strict";
7973
+
7974
+ module.exports = function(Chart) {
7975
+
7976
+ var helpers = Chart.helpers;
7977
+
7978
+ var defaultConfig = {
7979
+ position: "left",
7980
+ ticks: {
7981
+ callback: function(tickValue, index, ticks) {
7982
+ var delta = ticks[1] - ticks[0];
7983
+
7984
+ // If we have a number like 2.5 as the delta, figure out how many decimal places we need
7985
+ if (Math.abs(delta) > 1) {
7986
+ if (tickValue !== Math.floor(tickValue)) {
7987
+ // not an integer
7988
+ delta = tickValue - Math.floor(tickValue);
7989
+ }
7990
+ }
7991
+
7992
+ var logDelta = helpers.log10(Math.abs(delta));
7993
+ var tickString = '';
7994
+
7995
+ if (tickValue !== 0) {
7996
+ var numDecimal = -1 * Math.floor(logDelta);
7997
+ numDecimal = Math.max(Math.min(numDecimal, 20), 0); // toFixed has a max of 20 decimal places
7998
+ tickString = tickValue.toFixed(numDecimal);
7999
+ } else {
8000
+ tickString = '0'; // never show decimal places for 0
8001
+ }
8002
+
8003
+ return tickString;
8004
+ }
8005
+ }
8006
+ };
8007
+
8008
+ var LinearScale = Chart.Scale.extend({
8009
+ determineDataLimits: function() {
8010
+ // First Calculate the range
8011
+ this.min = null;
8012
+ this.max = null;
8013
+
8014
+ if (this.options.stacked) {
8015
+ var valuesPerType = {};
8016
+ var hasPositiveValues = false;
8017
+ var hasNegativeValues = false;
8018
+
8019
+ helpers.each(this.chart.data.datasets, function(dataset) {
8020
+ if (valuesPerType[dataset.type] === undefined) {
8021
+ valuesPerType[dataset.type] = {
8022
+ positiveValues: [],
8023
+ negativeValues: []
8024
+ };
8025
+ }
8026
+
8027
+ // Store these per type
8028
+ var positiveValues = valuesPerType[dataset.type].positiveValues;
8029
+ var negativeValues = valuesPerType[dataset.type].negativeValues;
8030
+
8031
+ if (helpers.isDatasetVisible(dataset) && (this.isHorizontal() ? dataset.xAxisID === this.id : dataset.yAxisID === this.id)) {
8032
+ helpers.each(dataset.data, function(rawValue, index) {
8033
+
8034
+ var value = +this.getRightValue(rawValue);
8035
+ if (isNaN(value)) {
8036
+ return;
8037
+ }
8038
+
8039
+ positiveValues[index] = positiveValues[index] || 0;
8040
+ negativeValues[index] = negativeValues[index] || 0;
8041
+
8042
+ if (this.options.relativePoints) {
8043
+ positiveValues[index] = 100;
8044
+ } else {
8045
+ if (value < 0) {
8046
+ hasNegativeValues = true;
8047
+ negativeValues[index] += value;
8048
+ } else {
8049
+ hasPositiveValues = true;
8050
+ positiveValues[index] += value;
8051
+ }
8052
+ }
8053
+ }, this);
8054
+ }
8055
+ }, this);
8056
+
8057
+ helpers.each(valuesPerType, function(valuesForType) {
8058
+ var values = valuesForType.positiveValues.concat(valuesForType.negativeValues);
8059
+ var minVal = helpers.min(values);
8060
+ var maxVal = helpers.max(values);
8061
+ this.min = this.min === null ? minVal : Math.min(this.min, minVal);
8062
+ this.max = this.max === null ? maxVal : Math.max(this.max, maxVal);
8063
+ }, this);
8064
+
8065
+ } else {
8066
+ helpers.each(this.chart.data.datasets, function(dataset) {
8067
+ if (helpers.isDatasetVisible(dataset) && (this.isHorizontal() ? dataset.xAxisID === this.id : dataset.yAxisID === this.id)) {
8068
+ helpers.each(dataset.data, function(rawValue, index) {
8069
+ var value = +this.getRightValue(rawValue);
8070
+ if (isNaN(value)) {
8071
+ return;
8072
+ }
8073
+
8074
+ if (this.min === null) {
8075
+ this.min = value;
8076
+ } else if (value < this.min) {
8077
+ this.min = value;
8078
+ }
8079
+
8080
+ if (this.max === null) {
8081
+ this.max = value;
8082
+ } else if (value > this.max) {
8083
+ this.max = value;
8084
+ }
8085
+ }, this);
8086
+ }
8087
+ }, this);
8088
+ }
8089
+
8090
+ // If we are forcing it to begin at 0, but 0 will already be rendered on the chart,
8091
+ // do nothing since that would make the chart weird. If the user really wants a weird chart
8092
+ // axis, they can manually override it
8093
+ if (this.options.ticks.beginAtZero) {
8094
+ var minSign = helpers.sign(this.min);
8095
+ var maxSign = helpers.sign(this.max);
8096
+
8097
+ if (minSign < 0 && maxSign < 0) {
8098
+ // move the top up to 0
8099
+ this.max = 0;
8100
+ } else if (minSign > 0 && maxSign > 0) {
8101
+ // move the botttom down to 0
8102
+ this.min = 0;
8103
+ }
8104
+ }
8105
+
8106
+ if (this.options.ticks.min !== undefined) {
8107
+ this.min = this.options.ticks.min;
8108
+ } else if (this.options.ticks.suggestedMin !== undefined) {
8109
+ this.min = Math.min(this.min, this.options.ticks.suggestedMin);
8110
+ }
8111
+
8112
+ if (this.options.ticks.max !== undefined) {
8113
+ this.max = this.options.ticks.max;
8114
+ } else if (this.options.ticks.suggestedMax !== undefined) {
8115
+ this.max = Math.max(this.max, this.options.ticks.suggestedMax);
8116
+ }
8117
+
8118
+ if (this.min === this.max) {
8119
+ this.min--;
8120
+ this.max++;
8121
+ }
8122
+ },
8123
+ buildTicks: function() {
8124
+
8125
+ // Then calulate the ticks
8126
+ this.ticks = [];
8127
+
8128
+ // Figure out what the max number of ticks we can support it is based on the size of
8129
+ // the axis area. For now, we say that the minimum tick spacing in pixels must be 50
8130
+ // We also limit the maximum number of ticks to 11 which gives a nice 10 squares on
8131
+ // the graph
8132
+
8133
+ var maxTicks;
8134
+
8135
+ if (this.isHorizontal()) {
8136
+ maxTicks = Math.min(this.options.ticks.maxTicksLimit ? this.options.ticks.maxTicksLimit : 11, Math.ceil(this.width / 50));
8137
+ } else {
8138
+ // The factor of 2 used to scale the font size has been experimentally determined.
8139
+ var tickFontSize = helpers.getValueOrDefault(this.options.ticks.fontSize, Chart.defaults.global.defaultFontSize);
8140
+ maxTicks = Math.min(this.options.ticks.maxTicksLimit ? this.options.ticks.maxTicksLimit : 11, Math.ceil(this.height / (2 * tickFontSize)));
8141
+ }
8142
+
8143
+ // Make sure we always have at least 2 ticks
8144
+ maxTicks = Math.max(2, maxTicks);
8145
+
8146
+ // To get a "nice" value for the tick spacing, we will use the appropriately named
8147
+ // "nice number" algorithm. See http://stackoverflow.com/questions/8506881/nice-label-algorithm-for-charts-with-minimum-ticks
8148
+ // for details.
8149
+
8150
+ var spacing;
8151
+ var fixedStepSizeSet = (this.options.ticks.fixedStepSize && this.options.ticks.fixedStepSize > 0) || (this.options.ticks.stepSize && this.options.ticks.stepSize > 0);
8152
+ if (fixedStepSizeSet) {
8153
+ spacing = helpers.getValueOrDefault(this.options.ticks.fixedStepSize, this.options.ticks.stepSize);
8154
+ } else {
8155
+ var niceRange = helpers.niceNum(this.max - this.min, false);
8156
+ spacing = helpers.niceNum(niceRange / (maxTicks - 1), true);
8157
+ }
8158
+ var niceMin = Math.floor(this.min / spacing) * spacing;
8159
+ var niceMax = Math.ceil(this.max / spacing) * spacing;
8160
+ var numSpaces = (niceMax - niceMin) / spacing;
8161
+
8162
+ // If very close to our rounded value, use it.
8163
+ if (helpers.almostEquals(numSpaces, Math.round(numSpaces), spacing / 1000)) {
8164
+ numSpaces = Math.round(numSpaces);
8165
+ } else {
8166
+ numSpaces = Math.ceil(numSpaces);
8167
+ }
8168
+
8169
+ // Put the values into the ticks array
8170
+ this.ticks.push(this.options.ticks.min !== undefined ? this.options.ticks.min : niceMin);
8171
+ for (var j = 1; j < numSpaces; ++j) {
8172
+ this.ticks.push(niceMin + (j * spacing));
8173
+ }
8174
+ this.ticks.push(this.options.ticks.max !== undefined ? this.options.ticks.max : niceMax);
8175
+
8176
+ if (this.options.position === "left" || this.options.position === "right") {
8177
+ // We are in a vertical orientation. The top value is the highest. So reverse the array
8178
+ this.ticks.reverse();
8179
+ }
8180
+
8181
+ // At this point, we need to update our max and min given the tick values since we have expanded the
8182
+ // range of the scale
8183
+ this.max = helpers.max(this.ticks);
8184
+ this.min = helpers.min(this.ticks);
8185
+
8186
+ if (this.options.ticks.reverse) {
8187
+ this.ticks.reverse();
8188
+
8189
+ this.start = this.max;
8190
+ this.end = this.min;
8191
+ } else {
8192
+ this.start = this.min;
8193
+ this.end = this.max;
8194
+ }
8195
+ },
8196
+ getLabelForIndex: function(index, datasetIndex) {
8197
+ return +this.getRightValue(this.chart.data.datasets[datasetIndex].data[index]);
8198
+ },
8199
+ convertTicksToLabels: function() {
8200
+ this.ticksAsNumbers = this.ticks.slice();
8201
+ this.zeroLineIndex = this.ticks.indexOf(0);
8202
+
8203
+ Chart.Scale.prototype.convertTicksToLabels.call(this);
8204
+ },
8205
+ // Utils
8206
+ getPixelForValue: function(value, index, datasetIndex, includeOffset) {
8207
+ // This must be called after fit has been run so that
8208
+ // this.left, this.top, this.right, and this.bottom have been defined
8209
+ var rightValue = +this.getRightValue(value);
8210
+ var pixel;
8211
+ var range = this.end - this.start;
8212
+
8213
+ if (this.isHorizontal()) {
8214
+ var innerWidth = this.width - (this.paddingLeft + this.paddingRight);
8215
+ pixel = this.left + (innerWidth / range * (rightValue - this.start));
8216
+ return Math.round(pixel + this.paddingLeft);
8217
+ } else {
8218
+ var innerHeight = this.height - (this.paddingTop + this.paddingBottom);
8219
+ pixel = (this.bottom - this.paddingBottom) - (innerHeight / range * (rightValue - this.start));
8220
+ return Math.round(pixel);
8221
+ }
8222
+ },
8223
+ getPixelForTick: function(index, includeOffset) {
8224
+ return this.getPixelForValue(this.ticksAsNumbers[index], null, null, includeOffset);
8225
+ }
8226
+ });
8227
+ Chart.scaleService.registerScaleType("linear", LinearScale, defaultConfig);
8228
+
8229
+ };
8230
+ },{}],39:[function(require,module,exports){
8231
+ "use strict";
8232
+
8233
+ module.exports = function(Chart) {
8234
+
8235
+ var helpers = Chart.helpers;
8236
+
8237
+ var defaultConfig = {
8238
+ position: "left",
8239
+
8240
+ // label settings
8241
+ ticks: {
8242
+ callback: function(value, index, arr) {
8243
+ var remain = value / (Math.pow(10, Math.floor(Chart.helpers.log10(value))));
8244
+
8245
+ if (remain === 1 || remain === 2 || remain === 5 || index === 0 || index === arr.length - 1) {
8246
+ return value.toExponential();
8247
+ } else {
8248
+ return '';
8249
+ }
8250
+ }
8251
+ }
8252
+ };
8253
+
8254
+ var LogarithmicScale = Chart.Scale.extend({
8255
+ determineDataLimits: function() {
8256
+ // Calculate Range
8257
+ this.min = null;
8258
+ this.max = null;
8259
+
8260
+ if (this.options.stacked) {
8261
+ var valuesPerType = {};
8262
+
8263
+ helpers.each(this.chart.data.datasets, function(dataset) {
8264
+ if (helpers.isDatasetVisible(dataset) && (this.isHorizontal() ? dataset.xAxisID === this.id : dataset.yAxisID === this.id)) {
8265
+ if (valuesPerType[dataset.type] === undefined) {
8266
+ valuesPerType[dataset.type] = [];
8267
+ }
8268
+
8269
+ helpers.each(dataset.data, function(rawValue, index) {
8270
+ var values = valuesPerType[dataset.type];
8271
+ var value = +this.getRightValue(rawValue);
8272
+ if (isNaN(value)) {
8273
+ return;
8274
+ }
8275
+
8276
+ values[index] = values[index] || 0;
8277
+
8278
+ if (this.options.relativePoints) {
8279
+ values[index] = 100;
8280
+ } else {
8281
+ // Don't need to split positive and negative since the log scale can't handle a 0 crossing
8282
+ values[index] += value;
8283
+ }
8284
+ }, this);
8285
+ }
8286
+ }, this);
8287
+
8288
+ helpers.each(valuesPerType, function(valuesForType) {
8289
+ var minVal = helpers.min(valuesForType);
8290
+ var maxVal = helpers.max(valuesForType);
8291
+ this.min = this.min === null ? minVal : Math.min(this.min, minVal);
8292
+ this.max = this.max === null ? maxVal : Math.max(this.max, maxVal);
8293
+ }, this);
8294
+
8295
+ } else {
8296
+ helpers.each(this.chart.data.datasets, function(dataset) {
8297
+ if (helpers.isDatasetVisible(dataset) && (this.isHorizontal() ? dataset.xAxisID === this.id : dataset.yAxisID === this.id)) {
8298
+ helpers.each(dataset.data, function(rawValue, index) {
8299
+ var value = +this.getRightValue(rawValue);
8300
+ if (isNaN(value)) {
8301
+ return;
8302
+ }
8303
+
8304
+ if (this.min === null) {
8305
+ this.min = value;
8306
+ } else if (value < this.min) {
8307
+ this.min = value;
8308
+ }
8309
+
8310
+ if (this.max === null) {
8311
+ this.max = value;
8312
+ } else if (value > this.max) {
8313
+ this.max = value;
8314
+ }
8315
+ }, this);
8316
+ }
8317
+ }, this);
8318
+ }
8319
+
8320
+ this.min = this.options.ticks.min !== undefined ? this.options.ticks.min : this.min;
8321
+ this.max = this.options.ticks.max !== undefined ? this.options.ticks.max : this.max;
8322
+
8323
+ if (this.min === this.max) {
8324
+ if (this.min !== 0 && this.min !== null) {
8325
+ this.min = Math.pow(10, Math.floor(helpers.log10(this.min)) - 1);
8326
+ this.max = Math.pow(10, Math.floor(helpers.log10(this.max)) + 1);
8327
+ } else {
8328
+ this.min = 1;
8329
+ this.max = 10;
8330
+ }
8331
+ }
8332
+ },
8333
+ buildTicks: function() {
8334
+ // Reset the ticks array. Later on, we will draw a grid line at these positions
8335
+ // The array simply contains the numerical value of the spots where ticks will be
8336
+ this.ticks = [];
8337
+
8338
+ // Figure out what the max number of ticks we can support it is based on the size of
8339
+ // the axis area. For now, we say that the minimum tick spacing in pixels must be 50
8340
+ // We also limit the maximum number of ticks to 11 which gives a nice 10 squares on
8341
+ // the graph
8342
+
8343
+ var tickVal = this.options.ticks.min !== undefined ? this.options.ticks.min : Math.pow(10, Math.floor(helpers.log10(this.min)));
8344
+
8345
+ while (tickVal < this.max) {
8346
+ this.ticks.push(tickVal);
8347
+
8348
+ var exp = Math.floor(helpers.log10(tickVal));
8349
+ var significand = Math.floor(tickVal / Math.pow(10, exp)) + 1;
8350
+
8351
+ if (significand === 10) {
8352
+ significand = 1;
8353
+ ++exp;
8354
+ }
8355
+
8356
+ tickVal = significand * Math.pow(10, exp);
8357
+ }
8358
+
8359
+ var lastTick = this.options.ticks.max !== undefined ? this.options.ticks.max : tickVal;
8360
+ this.ticks.push(lastTick);
8361
+
8362
+ if (this.options.position === "left" || this.options.position === "right") {
8363
+ // We are in a vertical orientation. The top value is the highest. So reverse the array
8364
+ this.ticks.reverse();
8365
+ }
8366
+
8367
+ // At this point, we need to update our max and min given the tick values since we have expanded the
8368
+ // range of the scale
8369
+ this.max = helpers.max(this.ticks);
8370
+ this.min = helpers.min(this.ticks);
8371
+
8372
+ if (this.options.ticks.reverse) {
8373
+ this.ticks.reverse();
8374
+
8375
+ this.start = this.max;
8376
+ this.end = this.min;
8377
+ } else {
8378
+ this.start = this.min;
8379
+ this.end = this.max;
8380
+ }
8381
+ },
8382
+ convertTicksToLabels: function() {
8383
+ this.tickValues = this.ticks.slice();
8384
+
8385
+ Chart.Scale.prototype.convertTicksToLabels.call(this);
8386
+ },
8387
+ // Get the correct tooltip label
8388
+ getLabelForIndex: function(index, datasetIndex) {
8389
+ return +this.getRightValue(this.chart.data.datasets[datasetIndex].data[index]);
8390
+ },
8391
+ getPixelForTick: function(index, includeOffset) {
8392
+ return this.getPixelForValue(this.tickValues[index], null, null, includeOffset);
8393
+ },
8394
+ getPixelForValue: function(value, index, datasetIndex, includeOffset) {
8395
+ var pixel;
8396
+
8397
+ var newVal = +this.getRightValue(value);
8398
+ var range = helpers.log10(this.end) - helpers.log10(this.start);
8399
+
8400
+ if (this.isHorizontal()) {
8401
+
8402
+ if (newVal === 0) {
8403
+ pixel = this.left + this.paddingLeft;
8404
+ } else {
8405
+ var innerWidth = this.width - (this.paddingLeft + this.paddingRight);
8406
+ pixel = this.left + (innerWidth / range * (helpers.log10(newVal) - helpers.log10(this.start)));
8407
+ pixel += this.paddingLeft;
8408
+ }
8409
+ } else {
8410
+ // Bottom - top since pixels increase downard on a screen
8411
+ if (newVal === 0) {
8412
+ pixel = this.top + this.paddingTop;
8413
+ } else {
8414
+ var innerHeight = this.height - (this.paddingTop + this.paddingBottom);
8415
+ pixel = (this.bottom - this.paddingBottom) - (innerHeight / range * (helpers.log10(newVal) - helpers.log10(this.start)));
8416
+ }
8417
+ }
8418
+
8419
+ return pixel;
8420
+ }
8421
+
8422
+ });
8423
+ Chart.scaleService.registerScaleType("logarithmic", LogarithmicScale, defaultConfig);
8424
+
8425
+ };
8426
+ },{}],40:[function(require,module,exports){
8427
+ "use strict";
8428
+
8429
+ module.exports = function(Chart) {
8430
+
8431
+ var helpers = Chart.helpers;
8432
+
8433
+ var defaultConfig = {
8434
+ display: true,
8435
+
8436
+ //Boolean - Whether to animate scaling the chart from the centre
8437
+ animate: true,
8438
+ lineArc: false,
8439
+ position: "chartArea",
8440
+
8441
+ angleLines: {
8442
+ display: true,
8443
+ color: "rgba(0, 0, 0, 0.1)",
8444
+ lineWidth: 1
8445
+ },
8446
+
8447
+ // label settings
8448
+ ticks: {
8449
+ //Boolean - Show a backdrop to the scale label
8450
+ showLabelBackdrop: true,
8451
+
8452
+ //String - The colour of the label backdrop
8453
+ backdropColor: "rgba(255,255,255,0.75)",
8454
+
8455
+ //Number - The backdrop padding above & below the label in pixels
8456
+ backdropPaddingY: 2,
8457
+
8458
+ //Number - The backdrop padding to the side of the label in pixels
8459
+ backdropPaddingX: 2
8460
+ },
8461
+
8462
+ pointLabels: {
8463
+ //Number - Point label font size in pixels
8464
+ fontSize: 10,
8465
+
8466
+ //Function - Used to convert point labels
8467
+ callback: function(label) {
8468
+ return label;
8469
+ }
8470
+ }
8471
+ };
8472
+
8473
+ var LinearRadialScale = Chart.Scale.extend({
8474
+ getValueCount: function() {
8475
+ return this.chart.data.labels.length;
8476
+ },
8477
+ setDimensions: function() {
8478
+ // Set the unconstrained dimension before label rotation
8479
+ this.width = this.maxWidth;
8480
+ this.height = this.maxHeight;
8481
+ this.xCenter = Math.round(this.width / 2);
8482
+ this.yCenter = Math.round(this.height / 2);
8483
+
8484
+ var minSize = helpers.min([this.height, this.width]);
8485
+ var tickFontSize = helpers.getValueOrDefault(this.options.ticks.fontSize, Chart.defaults.global.defaultFontSize);
8486
+ this.drawingArea = (this.options.display) ? (minSize / 2) - (tickFontSize / 2 + this.options.ticks.backdropPaddingY) : (minSize / 2);
8487
+ },
8488
+ determineDataLimits: function() {
8489
+ this.min = null;
8490
+ this.max = null;
8491
+
8492
+ helpers.each(this.chart.data.datasets, function(dataset) {
8493
+ if (helpers.isDatasetVisible(dataset)) {
8494
+ helpers.each(dataset.data, function(rawValue, index) {
8495
+ var value = +this.getRightValue(rawValue);
8496
+ if (isNaN(value)) {
8497
+ return;
8498
+ }
8499
+
8500
+ if (this.min === null) {
8501
+ this.min = value;
8502
+ } else if (value < this.min) {
8503
+ this.min = value;
8504
+ }
8505
+
8506
+ if (this.max === null) {
8507
+ this.max = value;
8508
+ } else if (value > this.max) {
8509
+ this.max = value;
8510
+ }
8511
+ }, this);
8512
+ }
8513
+ }, this);
8514
+
8515
+ // If we are forcing it to begin at 0, but 0 will already be rendered on the chart,
8516
+ // do nothing since that would make the chart weird. If the user really wants a weird chart
8517
+ // axis, they can manually override it
8518
+ if (this.options.ticks.beginAtZero) {
8519
+ var minSign = helpers.sign(this.min);
8520
+ var maxSign = helpers.sign(this.max);
8521
+
8522
+ if (minSign < 0 && maxSign < 0) {
8523
+ // move the top up to 0
8524
+ this.max = 0;
8525
+ } else if (minSign > 0 && maxSign > 0) {
8526
+ // move the botttom down to 0
8527
+ this.min = 0;
8528
+ }
8529
+ }
8530
+
8531
+ if (this.options.ticks.min !== undefined) {
8532
+ this.min = this.options.ticks.min;
8533
+ } else if (this.options.ticks.suggestedMin !== undefined) {
8534
+ this.min = Math.min(this.min, this.options.ticks.suggestedMin);
8535
+ }
8536
+
8537
+ if (this.options.ticks.max !== undefined) {
8538
+ this.max = this.options.ticks.max;
8539
+ } else if (this.options.ticks.suggestedMax !== undefined) {
8540
+ this.max = Math.max(this.max, this.options.ticks.suggestedMax);
8541
+ }
8542
+
8543
+ if (this.min === this.max) {
8544
+ this.min--;
8545
+ this.max++;
8546
+ }
8547
+ },
8548
+ buildTicks: function() {
8549
+
8550
+
8551
+ this.ticks = [];
8552
+
8553
+ // Figure out what the max number of ticks we can support it is based on the size of
8554
+ // the axis area. For now, we say that the minimum tick spacing in pixels must be 50
8555
+ // We also limit the maximum number of ticks to 11 which gives a nice 10 squares on
8556
+ // the graph
8557
+ var tickFontSize = helpers.getValueOrDefault(this.options.ticks.fontSize, Chart.defaults.global.defaultFontSize);
8558
+ var maxTicks = Math.min(this.options.ticks.maxTicksLimit ? this.options.ticks.maxTicksLimit : 11, Math.ceil(this.drawingArea / (1.5 * tickFontSize)));
8559
+ maxTicks = Math.max(2, maxTicks); // Make sure we always have at least 2 ticks
8560
+
8561
+ // To get a "nice" value for the tick spacing, we will use the appropriately named
8562
+ // "nice number" algorithm. See http://stackoverflow.com/questions/8506881/nice-label-algorithm-for-charts-with-minimum-ticks
8563
+ // for details.
8564
+
8565
+ var niceRange = helpers.niceNum(this.max - this.min, false);
8566
+ var spacing = helpers.niceNum(niceRange / (maxTicks - 1), true);
8567
+ var niceMin = Math.floor(this.min / spacing) * spacing;
8568
+ var niceMax = Math.ceil(this.max / spacing) * spacing;
8569
+
8570
+ var numSpaces = Math.ceil((niceMax - niceMin) / spacing);
8571
+
8572
+ // Put the values into the ticks array
8573
+ this.ticks.push(this.options.ticks.min !== undefined ? this.options.ticks.min : niceMin);
8574
+ for (var j = 1; j < numSpaces; ++j) {
8575
+ this.ticks.push(niceMin + (j * spacing));
8576
+ }
8577
+ this.ticks.push(this.options.ticks.max !== undefined ? this.options.ticks.max : niceMax);
8578
+
8579
+ // At this point, we need to update our max and min given the tick values since we have expanded the
8580
+ // range of the scale
8581
+ this.max = helpers.max(this.ticks);
8582
+ this.min = helpers.min(this.ticks);
8583
+
8584
+ if (this.options.ticks.reverse) {
8585
+ this.ticks.reverse();
8586
+
8587
+ this.start = this.max;
8588
+ this.end = this.min;
8589
+ } else {
8590
+ this.start = this.min;
8591
+ this.end = this.max;
8592
+ }
8593
+
8594
+ this.zeroLineIndex = this.ticks.indexOf(0);
8595
+ },
8596
+ convertTicksToLabels: function() {
8597
+ Chart.Scale.prototype.convertTicksToLabels.call(this);
8598
+
8599
+ // Point labels
8600
+ this.pointLabels = this.chart.data.labels.map(this.options.pointLabels.callback, this);
8601
+ },
8602
+ getLabelForIndex: function(index, datasetIndex) {
8603
+ return +this.getRightValue(this.chart.data.datasets[datasetIndex].data[index]);
8604
+ },
8605
+ fit: function() {
8606
+ /*
8607
+ * Right, this is really confusing and there is a lot of maths going on here
8608
+ * The gist of the problem is here: https://gist.github.com/nnnick/696cc9c55f4b0beb8fe9
8609
+ *
8610
+ * Reaction: https://dl.dropboxusercontent.com/u/34601363/toomuchscience.gif
8611
+ *
8612
+ * Solution:
8613
+ *
8614
+ * We assume the radius of the polygon is half the size of the canvas at first
8615
+ * at each index we check if the text overlaps.
8616
+ *
8617
+ * Where it does, we store that angle and that index.
8618
+ *
8619
+ * After finding the largest index and angle we calculate how much we need to remove
8620
+ * from the shape radius to move the point inwards by that x.
8621
+ *
8622
+ * We average the left and right distances to get the maximum shape radius that can fit in the box
8623
+ * along with labels.
8624
+ *
8625
+ * Once we have that, we can find the centre point for the chart, by taking the x text protrusion
8626
+ * on each side, removing that from the size, halving it and adding the left x protrusion width.
8627
+ *
8628
+ * This will mean we have a shape fitted to the canvas, as large as it can be with the labels
8629
+ * and position it in the most space efficient manner
8630
+ *
8631
+ * https://dl.dropboxusercontent.com/u/34601363/yeahscience.gif
8632
+ */
8633
+
8634
+ var pointLabelFontSize = helpers.getValueOrDefault(this.options.pointLabels.fontSize, Chart.defaults.global.defaultFontSize);
8635
+ var pointLabeFontStyle = helpers.getValueOrDefault(this.options.pointLabels.fontStyle, Chart.defaults.global.defaultFontStyle);
8636
+ var pointLabeFontFamily = helpers.getValueOrDefault(this.options.pointLabels.fontFamily, Chart.defaults.global.defaultFontFamily);
8637
+ var pointLabeFont = helpers.fontString(pointLabelFontSize, pointLabeFontStyle, pointLabeFontFamily);
8638
+
8639
+ // Get maximum radius of the polygon. Either half the height (minus the text width) or half the width.
8640
+ // Use this to calculate the offset + change. - Make sure L/R protrusion is at least 0 to stop issues with centre points
8641
+ var largestPossibleRadius = helpers.min([(this.height / 2 - pointLabelFontSize - 5), this.width / 2]),
8642
+ pointPosition,
8643
+ i,
8644
+ textWidth,
8645
+ halfTextWidth,
8646
+ furthestRight = this.width,
8647
+ furthestRightIndex,
8648
+ furthestRightAngle,
8649
+ furthestLeft = 0,
8650
+ furthestLeftIndex,
8651
+ furthestLeftAngle,
8652
+ xProtrusionLeft,
8653
+ xProtrusionRight,
8654
+ radiusReductionRight,
8655
+ radiusReductionLeft,
8656
+ maxWidthRadius;
8657
+ this.ctx.font = pointLabeFont;
8658
+
8659
+ for (i = 0; i < this.getValueCount(); i++) {
8660
+ // 5px to space the text slightly out - similar to what we do in the draw function.
8661
+ pointPosition = this.getPointPosition(i, largestPossibleRadius);
8662
+ textWidth = this.ctx.measureText(this.pointLabels[i] ? this.pointLabels[i] : '').width + 5;
8663
+ if (i === 0 || i === this.getValueCount() / 2) {
8664
+ // If we're at index zero, or exactly the middle, we're at exactly the top/bottom
8665
+ // of the radar chart, so text will be aligned centrally, so we'll half it and compare
8666
+ // w/left and right text sizes
8667
+ halfTextWidth = textWidth / 2;
8668
+ if (pointPosition.x + halfTextWidth > furthestRight) {
8669
+ furthestRight = pointPosition.x + halfTextWidth;
8670
+ furthestRightIndex = i;
8671
+ }
8672
+ if (pointPosition.x - halfTextWidth < furthestLeft) {
8673
+ furthestLeft = pointPosition.x - halfTextWidth;
8674
+ furthestLeftIndex = i;
8675
+ }
8676
+ } else if (i < this.getValueCount() / 2) {
8677
+ // Less than half the values means we'll left align the text
8678
+ if (pointPosition.x + textWidth > furthestRight) {
8679
+ furthestRight = pointPosition.x + textWidth;
8680
+ furthestRightIndex = i;
8681
+ }
8682
+ } else if (i > this.getValueCount() / 2) {
8683
+ // More than half the values means we'll right align the text
8684
+ if (pointPosition.x - textWidth < furthestLeft) {
8685
+ furthestLeft = pointPosition.x - textWidth;
8686
+ furthestLeftIndex = i;
8687
+ }
8688
+ }
8689
+ }
8690
+
8691
+ xProtrusionLeft = furthestLeft;
8692
+ xProtrusionRight = Math.ceil(furthestRight - this.width);
8693
+
8694
+ furthestRightAngle = this.getIndexAngle(furthestRightIndex);
8695
+ furthestLeftAngle = this.getIndexAngle(furthestLeftIndex);
8696
+
8697
+ radiusReductionRight = xProtrusionRight / Math.sin(furthestRightAngle + Math.PI / 2);
8698
+ radiusReductionLeft = xProtrusionLeft / Math.sin(furthestLeftAngle + Math.PI / 2);
8699
+
8700
+ // Ensure we actually need to reduce the size of the chart
8701
+ radiusReductionRight = (helpers.isNumber(radiusReductionRight)) ? radiusReductionRight : 0;
8702
+ radiusReductionLeft = (helpers.isNumber(radiusReductionLeft)) ? radiusReductionLeft : 0;
8703
+
8704
+ this.drawingArea = Math.round(largestPossibleRadius - (radiusReductionLeft + radiusReductionRight) / 2);
8705
+ this.setCenterPoint(radiusReductionLeft, radiusReductionRight);
8706
+ },
8707
+ setCenterPoint: function(leftMovement, rightMovement) {
8708
+
8709
+ var maxRight = this.width - rightMovement - this.drawingArea,
8710
+ maxLeft = leftMovement + this.drawingArea;
8711
+
8712
+ this.xCenter = Math.round(((maxLeft + maxRight) / 2) + this.left);
8713
+ // Always vertically in the centre as the text height doesn't change
8714
+ this.yCenter = Math.round((this.height / 2) + this.top);
8715
+ },
8716
+
8717
+ getIndexAngle: function(index) {
8718
+ var angleMultiplier = (Math.PI * 2) / this.getValueCount();
8719
+ // Start from the top instead of right, so remove a quarter of the circle
8720
+
8721
+ return index * angleMultiplier - (Math.PI / 2);
8722
+ },
8723
+ getDistanceFromCenterForValue: function(value) {
8724
+ if (value === null) {
8725
+ return 0; // null always in center
8726
+ }
8727
+
8728
+ // Take into account half font size + the yPadding of the top value
8729
+ var scalingFactor = this.drawingArea / (this.max - this.min);
8730
+ if (this.options.reverse) {
8731
+ return (this.max - value) * scalingFactor;
8732
+ } else {
8733
+ return (value - this.min) * scalingFactor;
8734
+ }
8735
+ },
8736
+ getPointPosition: function(index, distanceFromCenter) {
8737
+ var thisAngle = this.getIndexAngle(index);
8738
+ return {
8739
+ x: Math.round(Math.cos(thisAngle) * distanceFromCenter) + this.xCenter,
8740
+ y: Math.round(Math.sin(thisAngle) * distanceFromCenter) + this.yCenter
8741
+ };
8742
+ },
8743
+ getPointPositionForValue: function(index, value) {
8744
+ return this.getPointPosition(index, this.getDistanceFromCenterForValue(value));
8745
+ },
8746
+ draw: function() {
8747
+ if (this.options.display) {
8748
+ var ctx = this.ctx;
8749
+ helpers.each(this.ticks, function(label, index) {
8750
+ // Don't draw a centre value (if it is minimum)
8751
+ if (index > 0 || this.options.reverse) {
8752
+ var yCenterOffset = this.getDistanceFromCenterForValue(this.ticks[index]);
8753
+ var yHeight = this.yCenter - yCenterOffset;
8754
+
8755
+ // Draw circular lines around the scale
8756
+ if (this.options.gridLines.display) {
8757
+ ctx.strokeStyle = this.options.gridLines.color;
8758
+ ctx.lineWidth = this.options.gridLines.lineWidth;
8759
+
8760
+ if (this.options.lineArc) {
8761
+ // Draw circular arcs between the points
8762
+ ctx.beginPath();
8763
+ ctx.arc(this.xCenter, this.yCenter, yCenterOffset, 0, Math.PI * 2);
8764
+ ctx.closePath();
8765
+ ctx.stroke();
8766
+ } else {
8767
+ // Draw straight lines connecting each index
8768
+ ctx.beginPath();
8769
+ for (var i = 0; i < this.getValueCount(); i++) {
8770
+ var pointPosition = this.getPointPosition(i, this.getDistanceFromCenterForValue(this.ticks[index]));
8771
+ if (i === 0) {
8772
+ ctx.moveTo(pointPosition.x, pointPosition.y);
8773
+ } else {
8774
+ ctx.lineTo(pointPosition.x, pointPosition.y);
8775
+ }
8776
+ }
8777
+ ctx.closePath();
8778
+ ctx.stroke();
8779
+ }
8780
+ }
8781
+
8782
+ if (this.options.ticks.display) {
8783
+ var tickFontColor = helpers.getValueOrDefault(this.options.ticks.fontColor, Chart.defaults.global.defaultFontColor);
8784
+ var tickFontSize = helpers.getValueOrDefault(this.options.ticks.fontSize, Chart.defaults.global.defaultFontSize);
8785
+ var tickFontStyle = helpers.getValueOrDefault(this.options.ticks.fontStyle, Chart.defaults.global.defaultFontStyle);
8786
+ var tickFontFamily = helpers.getValueOrDefault(this.options.ticks.fontFamily, Chart.defaults.global.defaultFontFamily);
8787
+ var tickLabelFont = helpers.fontString(tickFontSize, tickFontStyle, tickFontFamily);
8788
+ ctx.font = tickLabelFont;
8789
+
8790
+ if (this.options.ticks.showLabelBackdrop) {
8791
+ var labelWidth = ctx.measureText(label).width;
8792
+ ctx.fillStyle = this.options.ticks.backdropColor;
8793
+ ctx.fillRect(
8794
+ this.xCenter - labelWidth / 2 - this.options.ticks.backdropPaddingX,
8795
+ yHeight - tickFontSize / 2 - this.options.ticks.backdropPaddingY,
8796
+ labelWidth + this.options.ticks.backdropPaddingX * 2,
8797
+ tickFontSize + this.options.ticks.backdropPaddingY * 2
8798
+ );
8799
+ }
8800
+
8801
+ ctx.textAlign = 'center';
8802
+ ctx.textBaseline = "middle";
8803
+ ctx.fillStyle = tickFontColor;
8804
+ ctx.fillText(label, this.xCenter, yHeight);
8805
+ }
8806
+ }
8807
+ }, this);
8808
+
8809
+ if (!this.options.lineArc) {
8810
+ ctx.lineWidth = this.options.angleLines.lineWidth;
8811
+ ctx.strokeStyle = this.options.angleLines.color;
8812
+
8813
+ for (var i = this.getValueCount() - 1; i >= 0; i--) {
8814
+ if (this.options.angleLines.display) {
8815
+ var outerPosition = this.getPointPosition(i, this.getDistanceFromCenterForValue(this.options.reverse ? this.min : this.max));
8816
+ ctx.beginPath();
8817
+ ctx.moveTo(this.xCenter, this.yCenter);
8818
+ ctx.lineTo(outerPosition.x, outerPosition.y);
8819
+ ctx.stroke();
8820
+ ctx.closePath();
8821
+ }
8822
+ // Extra 3px out for some label spacing
8823
+ var pointLabelPosition = this.getPointPosition(i, this.getDistanceFromCenterForValue(this.options.reverse ? this.min : this.max) + 5);
8824
+
8825
+ var pointLabelFontColor = helpers.getValueOrDefault(this.options.pointLabels.fontColor, Chart.defaults.global.defaultFontColor);
8826
+ var pointLabelFontSize = helpers.getValueOrDefault(this.options.pointLabels.fontSize, Chart.defaults.global.defaultFontSize);
8827
+ var pointLabeFontStyle = helpers.getValueOrDefault(this.options.pointLabels.fontStyle, Chart.defaults.global.defaultFontStyle);
8828
+ var pointLabeFontFamily = helpers.getValueOrDefault(this.options.pointLabels.fontFamily, Chart.defaults.global.defaultFontFamily);
8829
+ var pointLabeFont = helpers.fontString(pointLabelFontSize, pointLabeFontStyle, pointLabeFontFamily);
8830
+
8831
+ ctx.font = pointLabeFont;
8832
+ ctx.fillStyle = pointLabelFontColor;
8833
+
8834
+ var labelsCount = this.pointLabels.length,
8835
+ halfLabelsCount = this.pointLabels.length / 2,
8836
+ quarterLabelsCount = halfLabelsCount / 2,
8837
+ upperHalf = (i < quarterLabelsCount || i > labelsCount - quarterLabelsCount),
8838
+ exactQuarter = (i === quarterLabelsCount || i === labelsCount - quarterLabelsCount);
8839
+ if (i === 0) {
8840
+ ctx.textAlign = 'center';
8841
+ } else if (i === halfLabelsCount) {
8842
+ ctx.textAlign = 'center';
8843
+ } else if (i < halfLabelsCount) {
8844
+ ctx.textAlign = 'left';
8845
+ } else {
8846
+ ctx.textAlign = 'right';
8847
+ }
8848
+
8849
+ // Set the correct text baseline based on outer positioning
8850
+ if (exactQuarter) {
8851
+ ctx.textBaseline = 'middle';
8852
+ } else if (upperHalf) {
8853
+ ctx.textBaseline = 'bottom';
8854
+ } else {
8855
+ ctx.textBaseline = 'top';
8856
+ }
8857
+
8858
+ ctx.fillText(this.pointLabels[i] ? this.pointLabels[i] : '', pointLabelPosition.x, pointLabelPosition.y);
8859
+ }
8860
+ }
8861
+ }
8862
+ }
8863
+ });
8864
+ Chart.scaleService.registerScaleType("radialLinear", LinearRadialScale, defaultConfig);
8865
+
8866
+ };
8867
+ },{}],41:[function(require,module,exports){
8868
+ /*global window: false */
8869
+ "use strict";
8870
+
8871
+ var moment = require('moment');
8872
+ moment = typeof(moment) === 'function' ? moment : window.moment;
8873
+
8874
+ module.exports = function(Chart) {
8875
+
8876
+ var helpers = Chart.helpers;
8877
+ var time = {
8878
+ units: [{
8879
+ name: 'millisecond',
8880
+ steps: [1, 2, 5, 10, 20, 50, 100, 250, 500]
8881
+ }, {
8882
+ name: 'second',
8883
+ steps: [1, 2, 5, 10, 30]
8884
+ }, {
8885
+ name: 'minute',
8886
+ steps: [1, 2, 5, 10, 30]
8887
+ }, {
8888
+ name: 'hour',
8889
+ steps: [1, 2, 3, 6, 12]
8890
+ }, {
8891
+ name: 'day',
8892
+ steps: [1, 2, 5]
8893
+ }, {
8894
+ name: 'week',
8895
+ maxStep: 4
8896
+ }, {
8897
+ name: 'month',
8898
+ maxStep: 3
8899
+ }, {
8900
+ name: 'quarter',
8901
+ maxStep: 4
8902
+ }, {
8903
+ name: 'year',
8904
+ maxStep: false
8905
+ }]
8906
+ };
8907
+
8908
+ var defaultConfig = {
8909
+ position: "bottom",
8910
+
8911
+ time: {
8912
+ 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
8913
+ format: false, // DEPRECATED false == date objects, moment object, callback or a pattern string from http://momentjs.com/docs/#/parsing/string-format/
8914
+ unit: false, // false == automatic or override with week, month, year, etc.
8915
+ round: false, // none, or override with week, month, year, etc.
8916
+ displayFormat: false, // DEPRECATED
8917
+
8918
+ // defaults to unit's corresponding unitFormat below or override using pattern string from http://momentjs.com/docs/#/displaying/format/
8919
+ displayFormats: {
8920
+ 'millisecond': 'h:mm:ss.SSS a', // 11:20:01.123 AM,
8921
+ 'second': 'h:mm:ss a', // 11:20:01 AM
8922
+ 'minute': 'h:mm:ss a', // 11:20:01 AM
8923
+ 'hour': 'MMM D, hA', // Sept 4, 5PM
8924
+ 'day': 'll', // Sep 4 2015
8925
+ 'week': 'll', // Week 46, or maybe "[W]WW - YYYY" ?
8926
+ 'month': 'MMM YYYY', // Sept 2015
8927
+ 'quarter': '[Q]Q - YYYY', // Q3
8928
+ 'year': 'YYYY' // 2015
8929
+ }
8930
+ },
8931
+ ticks: {
8932
+ autoSkip: false
8933
+ }
8934
+ };
8935
+
8936
+ var TimeScale = Chart.Scale.extend({
8937
+ initialize: function() {
8938
+ if (!moment) {
8939
+ 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');
8940
+ }
8941
+
8942
+ Chart.Scale.prototype.initialize.call(this);
8943
+ },
8944
+ getLabelMoment: function(datasetIndex, index) {
8945
+ return this.labelMoments[datasetIndex][index];
8946
+ },
8947
+ determineDataLimits: function() {
8948
+ this.labelMoments = [];
8949
+
8950
+ // Only parse these once. If the dataset does not have data as x,y pairs, we will use
8951
+ // these
8952
+ var scaleLabelMoments = [];
8953
+ if (this.chart.data.labels && this.chart.data.labels.length > 0) {
8954
+ helpers.each(this.chart.data.labels, function(label, index) {
8955
+ var labelMoment = this.parseTime(label);
8956
+ if (this.options.time.round) {
8957
+ labelMoment.startOf(this.options.time.round);
8958
+ }
8959
+ scaleLabelMoments.push(labelMoment);
8960
+ }, this);
8961
+
8962
+ this.firstTick = moment.min.call(this, scaleLabelMoments);
8963
+ this.lastTick = moment.max.call(this, scaleLabelMoments);
8964
+ } else {
8965
+ this.firstTick = null;
8966
+ this.lastTick = null;
8967
+ }
8968
+
8969
+ helpers.each(this.chart.data.datasets, function(dataset, datasetIndex) {
8970
+ var momentsForDataset = [];
8971
+
8972
+ if (typeof dataset.data[0] === 'object') {
8973
+ helpers.each(dataset.data, function(value, index) {
8974
+ var labelMoment = this.parseTime(this.getRightValue(value));
8975
+ if (this.options.time.round) {
8976
+ labelMoment.startOf(this.options.time.round);
8977
+ }
8978
+ momentsForDataset.push(labelMoment);
8979
+
8980
+ // May have gone outside the scale ranges, make sure we keep the first and last ticks updated
8981
+ this.firstTick = this.firstTick !== null ? moment.min(this.firstTick, labelMoment) : labelMoment;
8982
+ this.lastTick = this.lastTick !== null ? moment.max(this.lastTick, labelMoment) : labelMoment;
8983
+ }, this);
8984
+ } else {
8985
+ // We have no labels. Use the ones from the scale
8986
+ momentsForDataset = scaleLabelMoments;
8987
+ }
8988
+
8989
+ this.labelMoments.push(momentsForDataset);
8990
+ }, this);
8991
+
8992
+ // Set these after we've done all the data
8993
+ if (this.options.time.min) {
8994
+ this.firstTick = this.parseTime(this.options.time.min);
8995
+ }
8996
+
8997
+ if (this.options.time.max) {
8998
+ this.lastTick = this.parseTime(this.options.time.max);
8999
+ }
9000
+
9001
+ // We will modify these, so clone for later
9002
+ this.firstTick = (this.firstTick || moment()).clone();
9003
+ this.lastTick = (this.lastTick || moment()).clone();
9004
+ },
9005
+ buildTicks: function(index) {
9006
+
9007
+ this.ctx.save();
9008
+ var tickFontSize = helpers.getValueOrDefault(this.options.ticks.fontSize, Chart.defaults.global.defaultFontSize);
9009
+ var tickFontStyle = helpers.getValueOrDefault(this.options.ticks.fontStyle, Chart.defaults.global.defaultFontStyle);
9010
+ var tickFontFamily = helpers.getValueOrDefault(this.options.ticks.fontFamily, Chart.defaults.global.defaultFontFamily);
9011
+ var tickLabelFont = helpers.fontString(tickFontSize, tickFontStyle, tickFontFamily);
9012
+ this.ctx.font = tickLabelFont;
9013
+
9014
+ this.ticks = [];
9015
+ this.unitScale = 1; // How much we scale the unit by, ie 2 means 2x unit per step
9016
+ this.scaleSizeInUnits = 0; // How large the scale is in the base unit (seconds, minutes, etc)
9017
+
9018
+ // Set unit override if applicable
9019
+ if (this.options.time.unit) {
9020
+ this.tickUnit = this.options.time.unit || 'day';
9021
+ this.displayFormat = this.options.time.displayFormats[this.tickUnit];
9022
+ this.scaleSizeInUnits = this.lastTick.diff(this.firstTick, this.tickUnit, true);
9023
+ this.unitScale = helpers.getValueOrDefault(this.options.time.unitStepSize, 1);
9024
+ } else {
9025
+ // Determine the smallest needed unit of the time
9026
+ var innerWidth = this.isHorizontal() ? this.width - (this.paddingLeft + this.paddingRight) : this.height - (this.paddingTop + this.paddingBottom);
9027
+
9028
+ // Crude approximation of what the label length might be
9029
+ var tempFirstLabel = this.tickFormatFunction(this.firstTick, 0, []);
9030
+ var tickLabelWidth = this.ctx.measureText(tempFirstLabel).width;
9031
+ var cosRotation = Math.cos(helpers.toRadians(this.options.ticks.maxRotation));
9032
+ var sinRotation = Math.sin(helpers.toRadians(this.options.ticks.maxRotation));
9033
+ tickLabelWidth = (tickLabelWidth * cosRotation) + (tickFontSize * sinRotation);
9034
+ var labelCapacity = innerWidth / (tickLabelWidth);
9035
+
9036
+ // Start as small as possible
9037
+ this.tickUnit = 'millisecond';
9038
+ this.scaleSizeInUnits = this.lastTick.diff(this.firstTick, this.tickUnit, true);
9039
+ this.displayFormat = this.options.time.displayFormats[this.tickUnit];
9040
+
9041
+ var unitDefinitionIndex = 0;
9042
+ var unitDefinition = time.units[unitDefinitionIndex];
9043
+
9044
+ // While we aren't ideal and we don't have units left
9045
+ while (unitDefinitionIndex < time.units.length) {
9046
+ // Can we scale this unit. If `false` we can scale infinitely
9047
+ this.unitScale = 1;
9048
+
9049
+ if (helpers.isArray(unitDefinition.steps) && Math.ceil(this.scaleSizeInUnits / labelCapacity) < helpers.max(unitDefinition.steps)) {
9050
+ // Use one of the prefedined steps
9051
+ for (var idx = 0; idx < unitDefinition.steps.length; ++idx) {
9052
+ if (unitDefinition.steps[idx] >= Math.ceil(this.scaleSizeInUnits / labelCapacity)) {
9053
+ this.unitScale = helpers.getValueOrDefault(this.options.time.unitStepSize, unitDefinition.steps[idx]);
9054
+ break;
9055
+ }
9056
+ }
9057
+
9058
+ break;
9059
+ } else if ((unitDefinition.maxStep === false) || (Math.ceil(this.scaleSizeInUnits / labelCapacity) < unitDefinition.maxStep)) {
9060
+ // We have a max step. Scale this unit
9061
+ this.unitScale = helpers.getValueOrDefault(this.options.time.unitStepSize, Math.ceil(this.scaleSizeInUnits / labelCapacity));
9062
+ break;
9063
+ } else {
9064
+ // Move to the next unit up
9065
+ ++unitDefinitionIndex;
9066
+ unitDefinition = time.units[unitDefinitionIndex];
9067
+
9068
+ this.tickUnit = unitDefinition.name;
9069
+ this.scaleSizeInUnits = this.lastTick.diff(this.firstTick, this.tickUnit, true);
9070
+ this.displayFormat = this.options.time.displayFormats[unitDefinition.name];
9071
+ }
9072
+ }
9073
+ }
9074
+
9075
+ var roundedStart;
9076
+
9077
+ // Only round the first tick if we have no hard minimum
9078
+ if (!this.options.time.min) {
9079
+ this.firstTick.startOf(this.tickUnit);
9080
+ roundedStart = this.firstTick;
9081
+ } else {
9082
+ roundedStart = this.firstTick.clone().startOf(this.tickUnit);
9083
+ }
9084
+
9085
+ // Only round the last tick if we have no hard maximum
9086
+ if (!this.options.time.max) {
9087
+ this.lastTick.endOf(this.tickUnit);
9088
+ }
9089
+
9090
+ this.smallestLabelSeparation = this.width;
9091
+
9092
+ helpers.each(this.chart.data.datasets, function(dataset, datasetIndex) {
9093
+ for (var i = 1; i < this.labelMoments[datasetIndex].length; i++) {
9094
+ this.smallestLabelSeparation = Math.min(this.smallestLabelSeparation, this.labelMoments[datasetIndex][i].diff(this.labelMoments[datasetIndex][i - 1], this.tickUnit, true));
9095
+ }
9096
+ }, this);
9097
+
9098
+ // Tick displayFormat override
9099
+ if (this.options.time.displayFormat) {
9100
+ this.displayFormat = this.options.time.displayFormat;
9101
+ }
9102
+
9103
+ // first tick. will have been rounded correctly if options.time.min is not specified
9104
+ this.ticks.push(this.firstTick.clone());
9105
+
9106
+ // For every unit in between the first and last moment, create a moment and add it to the ticks tick
9107
+ for (var i = 1; i < this.scaleSizeInUnits; ++i) {
9108
+ var newTick = roundedStart.clone().add(i, this.tickUnit);
9109
+
9110
+ // Are we greater than the max time
9111
+ if (this.options.time.max && newTick.diff(this.lastTick, this.tickUnit, true) >= 0) {
9112
+ break;
9113
+ }
9114
+
9115
+ if (i % this.unitScale === 0) {
9116
+ this.ticks.push(newTick);
9117
+ }
9118
+ }
9119
+
9120
+ // Always show the right tick
9121
+ if (this.ticks[this.ticks.length - 1].diff(this.lastTick, this.tickUnit) !== 0 || this.scaleSizeInUnits === 0) {
9122
+ // this is a weird case. If the <max> option is the same as the end option, we can't just diff the times because the tick was created from the roundedStart
9123
+ // but the last tick was not rounded.
9124
+ if (this.options.time.max) {
9125
+ this.ticks.push(this.lastTick.clone());
9126
+ this.scaleSizeInUnits = this.lastTick.diff(this.ticks[0], this.tickUnit, true);
9127
+ } else {
9128
+ this.scaleSizeInUnits = Math.ceil(this.scaleSizeInUnits / this.unitScale) * this.unitScale;
9129
+ this.ticks.push(this.firstTick.clone().add(this.scaleSizeInUnits, this.tickUnit));
9130
+ this.lastTick = this.ticks[this.ticks.length - 1].clone();
9131
+ }
9132
+ }
9133
+ this.ctx.restore();
9134
+ },
9135
+ // Get tooltip label
9136
+ getLabelForIndex: function(index, datasetIndex) {
9137
+ var label = this.chart.data.labels && index < this.chart.data.labels.length ? this.chart.data.labels[index] : '';
9138
+
9139
+ if (typeof this.chart.data.datasets[datasetIndex].data[0] === 'object') {
9140
+ label = this.getRightValue(this.chart.data.datasets[datasetIndex].data[index]);
9141
+ }
9142
+
9143
+ // Format nicely
9144
+ if (this.options.time.tooltipFormat) {
9145
+ label = this.parseTime(label).format(this.options.time.tooltipFormat);
9146
+ }
9147
+
9148
+ return label;
9149
+ },
9150
+ // Function to format an individual tick mark
9151
+ tickFormatFunction: function tickFormatFunction(tick, index, ticks) {
9152
+ var formattedTick = tick.format(this.displayFormat);
9153
+
9154
+ if (this.options.ticks.userCallback) {
9155
+ return this.options.ticks.userCallback(formattedTick, index, ticks);
9156
+ } else {
9157
+ return formattedTick;
9158
+ }
9159
+ },
9160
+ convertTicksToLabels: function() {
9161
+ this.ticks = this.ticks.map(this.tickFormatFunction, this);
9162
+ },
9163
+ getPixelForValue: function(value, index, datasetIndex, includeOffset) {
9164
+ var labelMoment = this.getLabelMoment(datasetIndex, index);
9165
+
9166
+ if (labelMoment) {
9167
+ var offset = labelMoment.diff(this.firstTick, this.tickUnit, true);
9168
+
9169
+ var decimal = offset / this.scaleSizeInUnits;
9170
+
9171
+ if (this.isHorizontal()) {
9172
+ var innerWidth = this.width - (this.paddingLeft + this.paddingRight);
9173
+ var valueWidth = innerWidth / Math.max(this.ticks.length - 1, 1);
9174
+ var valueOffset = (innerWidth * decimal) + this.paddingLeft;
9175
+
9176
+ return this.left + Math.round(valueOffset);
9177
+ } else {
9178
+ var innerHeight = this.height - (this.paddingTop + this.paddingBottom);
9179
+ var valueHeight = innerHeight / Math.max(this.ticks.length - 1, 1);
9180
+ var heightOffset = (innerHeight * decimal) + this.paddingTop;
9181
+
9182
+ return this.top + Math.round(heightOffset);
9183
+ }
9184
+ }
9185
+ },
9186
+ parseTime: function(label) {
9187
+ if (typeof this.options.time.parser === 'string') {
9188
+ return moment(label, this.options.time.parser);
9189
+ }
9190
+ if (typeof this.options.time.parser === 'function') {
9191
+ return this.options.time.parser(label);
9192
+ }
9193
+ // Date objects
9194
+ if (typeof label.getMonth === 'function' || typeof label === 'number') {
9195
+ return moment(label);
9196
+ }
9197
+ // Moment support
9198
+ if (label.isValid && label.isValid()) {
9199
+ return label;
9200
+ }
9201
+ // Custom parsing (return an instance of moment)
9202
+ if (typeof this.options.time.format !== 'string' && this.options.time.format.call) {
9203
+ console.warn("options.time.format is deprecated and replaced by options.time.parser. See http://nnnick.github.io/Chart.js/docs-v2/#scales-time-scale");
9204
+ return this.options.time.format(label);
9205
+ }
9206
+ // Moment format parsing
9207
+ return moment(label, this.options.time.format);
9208
+ }
9209
+ });
9210
+ Chart.scaleService.registerScaleType("time", TimeScale, defaultConfig);
9211
+
9212
+ };
9213
+
9214
+ },{"moment":1}]},{},[7]);
readme.txt CHANGED
@@ -2,9 +2,9 @@
2
  Contributors: eskapism
3
  Donate link: http://eskapism.se/sida/donate/
4
  Tags: history, log, changes, changelog, audit, trail, pages, attachments, users, cms, dashboard, admin, syslog, feed, activity, stream, audit trail, brute-force
5
- Requires at least: 3.6.0
6
- Tested up to: 4.4
7
- Stable tag: 2.5.5
8
 
9
  View changes made by users within WordPress. See who created a page, uploaded an attachment or approved an comment, and more.
10
 
@@ -108,6 +108,7 @@ So far Simple History is translated to:
108
  * Dutch
109
  * Finnish
110
  * French
 
111
 
112
  I'm looking for translations of Simple History in more languages! If you want to translate Simple History
113
  to your language then read about how this is done over at the [Polyglots handbook](https://make.wordpress.org/polyglots/handbook/rosetta/theme-plugin-directories/#translating-themes-plugins).
@@ -144,7 +145,17 @@ initiated by a specific user.
144
 
145
  ## Changelog
146
 
147
- = 2.5.5 (March 2016) =
 
 
 
 
 
 
 
 
 
 
148
 
149
  - Changed: The logger for Enable Media Replace required the capability `edit_files` to view the logged events, but since this also made it impossible to view events if the constant `DISALLOW_FILE_EDIT` was true. Now Enable Media Replace requires the capability `upload_files` instead. Makes more sense. Fixes https://wordpress.org/support/topic/simple-history-and-disallow_file_edit.
150
  - Changed: No longer log spam trackbacks or comments. Before this version these where logged, but not shown.
2
  Contributors: eskapism
3
  Donate link: http://eskapism.se/sida/donate/
4
  Tags: history, log, changes, changelog, audit, trail, pages, attachments, users, cms, dashboard, admin, syslog, feed, activity, stream, audit trail, brute-force
5
+ Requires at least: 4.5.1
6
+ Tested up to: 4.5.2
7
+ Stable tag: 2.6
8
 
9
  View changes made by users within WordPress. See who created a page, uploaded an attachment or approved an comment, and more.
10
 
108
  * Dutch
109
  * Finnish
110
  * French
111
+ * Russian
112
 
113
  I'm looking for translations of Simple History in more languages! If you want to translate Simple History
114
  to your language then read about how this is done over at the [Polyglots handbook](https://make.wordpress.org/polyglots/handbook/rosetta/theme-plugin-directories/#translating-themes-plugins).
145
 
146
  ## Changelog
147
 
148
+ = 2.6 (May 2016) =
149
+
150
+ - Added: A nice little graph in the sidebar that displays the number of logged events per day the last 28 days. Graph is powered by [Graph.js](http://www.chartjs.org/).
151
+ - Added: Function `get_num_events_last_n_days()`
152
+ - Added: Function `get_num_events_per_day_last_n_days()`
153
+ - Changed: Switched to transients from cache at some places, because more people will benefit from transients instead of cache (that requires object cache to be installed).
154
+ - Changed: New constant `SETTINGS_GENERAL_OPTION_GROUP`. Fixes https://wordpress.org/support/topic/constant-for-settings-option-group-name-option_group.
155
+ - Fixed: Long log messages with no spaces would get cut of. Now all the message is shown, but with one or several line breaks. Fixes https://github.com/bonny/WordPress-Simple-History/pull/112.
156
+ - Fixed: Some small CSS modification to make the page less "jumpy" while loading (for example setting a default height to the select2 input box).
157
+
158
+ = 2.5.5 (April 2016) =
159
 
160
  - Changed: The logger for Enable Media Replace required the capability `edit_files` to view the logged events, but since this also made it impossible to view events if the constant `DISALLOW_FILE_EDIT` was true. Now Enable Media Replace requires the capability `upload_files` instead. Makes more sense. Fixes https://wordpress.org/support/topic/simple-history-and-disallow_file_edit.
161
  - Changed: No longer log spam trackbacks or comments. Before this version these where logged, but not shown.