sidekiq-benchmark 0.5.0 → 0.7.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
- SHA1:
3
- metadata.gz: 2b2583957b88fb1d556a08a53abe16bc823914ec
4
- data.tar.gz: d084d3be5a35c882326c9d3f8df418c0a9c2bca6
2
+ SHA256:
3
+ metadata.gz: e90aa6b3895541db3454bfac34902d919c5f9c5e8f6640085690af9530fe9995
4
+ data.tar.gz: 7a88366e826fc84954c47296294d4685cf5887bfa2bbea0fe4f966e917d28f95
5
5
  SHA512:
6
- metadata.gz: b0662eec56c195a7077a263c10926e6b7484fde4fb54b7f0d45db8b70e61e74847bf2f35b9c1c9d1a60671362f6ee6970306358af0381e79073013facc47db05
7
- data.tar.gz: 1e7042f4b933e3776cbeaea2cf682a86a69ffc1416f4da4784110036439e0ad9d85f98dc24d5b043e0f1eec7c3f67a5587a20dae443d2e019b576fbb46000ca0
6
+ metadata.gz: a7f05db5120b717567fc6f2c110ed5d1c4873ff35989f2c5a61c9c6317068c3e5a195ece0c504581c8d33a90f0aad5fc3e0e685265d10cffb9c61e037428e592
7
+ data.tar.gz: 690b2c9f0786eb23b0a6288d4f68008085e4917a205cccf016218cdb5a080cfbc94808403bb37d34f1d46c34107e1ec67c1103c190690272dc7f45b0eae4036d
data/.gitignore CHANGED
@@ -1,20 +1,4 @@
1
- *.swp
2
- *.swo
3
- *.gem
4
- *.rbc
5
- .ruby-version
6
- .bundle
7
- .config
8
- .yardoc
1
+ vendor/
9
2
  Gemfile.lock
10
- InstalledFiles
11
- _yardoc
12
- coverage
13
- doc/
14
- lib/bundler/man
15
- pkg
16
- rdoc
17
- spec/reports
18
- test/tmp
19
- test/version_tmp
20
- tmp
3
+ coverage/
4
+ *.gem
data/README.md CHANGED
@@ -19,7 +19,7 @@ And then execute:
19
19
 
20
20
  ## Requirements
21
21
 
22
- Redis 2.6.0 or newer required
22
+ From version 0.5.0 works with Sidekiq 4.2 or newer
23
23
 
24
24
  ## Usage
25
25
 
@@ -73,7 +73,7 @@ end
73
73
 
74
74
  ### Sample Apps
75
75
 
76
- [Heroku App](http://sidekiq-benchmark.herokuapp.com/benchmarks)
76
+ [Heroku App](http://sidekiq-benchmark.herokuapp.com/benchmarks/generate)
77
77
 
78
78
  ## Testing sidekiq workers
79
79
 
@@ -92,6 +92,3 @@ require 'sidekiq-benchmark/testing'
92
92
  3. Commit your changes (`git commit -am 'Add some feature'`)
93
93
  4. Push to the branch (`git push origin my-new-feature`)
94
94
  5. Create new Pull Request
95
-
96
-
97
- [![endorse](https://api.coderwall.com/kosmatov/endorsecount.png)](https://coderwall.com/kosmatov)
@@ -8,6 +8,7 @@ module Sidekiq
8
8
  module Benchmark
9
9
  REDIS_NAMESPACE = :benchmark
10
10
  TYPES_KEY = "#{REDIS_NAMESPACE}:types".freeze
11
+ STAT_KEYS = %i[stats total]
11
12
  REDIS_KEYS_TTL = 3600 * 24 * 30
12
13
 
13
14
  autoload :Worker, 'sidekiq-benchmark/worker'
@@ -1,5 +1,5 @@
1
1
  module Sidekiq
2
2
  module Benchmark
3
- VERSION = "0.5.0"
3
+ VERSION = "0.7.1"
4
4
  end
5
5
  end
@@ -23,11 +23,11 @@ module Sidekiq
23
23
  @charts = {}
24
24
 
25
25
  Sidekiq.redis do |conn|
26
- @types = conn.smembers Sidekiq::Benchmark::TYPES_KEY
26
+ @types = conn.smembers TYPES_KEY
27
27
  @types.each do |type|
28
- @charts[type] = { total: [], stats: [] }
28
+ @charts[type] = STAT_KEYS.reduce({}) { |a, e| a[e] = []; a }
29
29
 
30
- total_key = "#{Sidekiq::Benchmark::REDIS_NAMESPACE}:#{type}:total"
30
+ total_key = "#{REDIS_NAMESPACE}:#{type}:total"
31
31
  total_keys = conn.hkeys(total_key) - %w(start_time job_time finish_time)
32
32
 
33
33
  total_time = conn.hget total_key, :job_time
@@ -37,7 +37,7 @@ module Sidekiq
37
37
  @charts[type][:total] << [key, value.to_f.round(2)]
38
38
  end
39
39
 
40
- stats = conn.hgetall "#{Sidekiq::Benchmark::REDIS_NAMESPACE}:#{type}:stats"
40
+ stats = conn.hgetall "#{REDIS_NAMESPACE}:#{type}:stats"
41
41
  stats.each do |key, value|
42
42
  @charts[type][:stats] << [key.to_f, value.to_i]
43
43
  end
@@ -52,7 +52,20 @@ module Sidekiq
52
52
 
53
53
  app.post "/benchmarks/remove" do
54
54
  Sidekiq.redis do |conn|
55
- keys = conn.keys "benchmark:*"
55
+ keys = STAT_KEYS.map { |key| "#{REDIS_NAMESPACE}:#{params[:type]}:#{key}" }
56
+ conn.srem TYPES_KEY, params[:type]
57
+ conn.del keys
58
+ end
59
+
60
+ redirect "#{root_path}benchmarks"
61
+ end
62
+ app.post "/benchmarks/remove_all" do
63
+ Sidekiq.redis do |conn|
64
+ types = conn.smembers TYPES_KEY
65
+ keys = STAT_KEYS.map do |key|
66
+ types.map { |type| "#{REDIS_NAMESPACE}:#{type}:#{key}" }
67
+ end.flatten
68
+ keys << TYPES_KEY
56
69
  conn.del keys
57
70
  end
58
71
 
@@ -50,6 +50,8 @@ module Sidekiq
50
50
  self[name] ||= 0.0
51
51
  self[name] += t1 - t0
52
52
 
53
+ Sidekiq.logger.info "Benchmark #{name}: #{t1 - t0} sec." if @options[:log]
54
+
53
55
  ret
54
56
  end
55
57
  alias_method :bm, :measure
@@ -68,8 +70,8 @@ module Sidekiq
68
70
 
69
71
  def set_redis_key(key)
70
72
  Sidekiq.redis do |conn|
71
- conn.sadd Sidekiq::Benchmark::TYPES_KEY, key
72
- conn.expire Sidekiq::Benchmark::TYPES_KEY, REDIS_KEYS_TTL
73
+ conn.sadd TYPES_KEY, key
74
+ conn.expire TYPES_KEY, REDIS_KEYS_TTL
73
75
  end
74
76
  end
75
77
 
@@ -13,14 +13,14 @@ Gem::Specification.new do |gem|
13
13
  gem.homepage = "https://github.com/kosmatov/sidekiq-benchmark/"
14
14
  gem.license = 'MIT'
15
15
 
16
- gem.files = `git ls-files | grep -Ev '^(examples)'`.split("\n")
16
+ gem.files = `git ls-files | grep -Ev '^(examples|vendor|docker|.travis|Makefile)'`.split("\n")
17
17
  gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
18
18
  gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
19
19
  gem.require_paths = ["lib"]
20
20
 
21
- gem.add_dependency "chartkick", '>= 1.1.1'
21
+ gem.add_dependency "chartkick", ">= 1.1.1"
22
+ gem.add_dependency "sidekiq", "> 5"
22
23
 
23
- gem.add_development_dependency "sidekiq"
24
24
  gem.add_development_dependency "rake"
25
25
  gem.add_development_dependency "rack-test"
26
26
  gem.add_development_dependency "minitest", "~> 5"
@@ -12,12 +12,12 @@ class Sidekiq::Benchmark::TestingTest < Minitest::Spec
12
12
  it "save nothing to redis" do
13
13
  Sidekiq.redis do |conn|
14
14
  total_time = conn.hget(@worker.benchmark.redis_keys[:total], :job_time)
15
- total_time.must_be_nil
15
+ _(total_time).must_be_nil
16
16
  end
17
17
  end
18
18
 
19
19
  it "run code in bm blocks" do
20
- @worker.counter.wont_equal 0
20
+ _(@worker.counter).wont_equal 0
21
21
  end
22
22
  end
23
23
  end
@@ -14,33 +14,38 @@ module Sidekiq
14
14
  Test.flush_db
15
15
  end
16
16
 
17
- it "should display index without stats" do
17
+ it "display index without stats" do
18
18
  get '/benchmarks'
19
- last_response.status.must_equal 200
19
+ _(last_response.status).must_equal 200
20
20
  end
21
21
 
22
- it "should display index with stats" do
22
+ it "display index with stats" do
23
23
  WorkerMock.new
24
24
 
25
25
  get '/benchmarks'
26
- last_response.status.must_equal 200
26
+ _(last_response.status).must_equal 200
27
27
  end
28
28
 
29
- it "should remove benchmarks data" do
29
+ it "remove all benchmarks data" do
30
30
  WorkerMock.new
31
31
 
32
- Sidekiq.redis do |conn|
33
- keys = conn.keys "benchmark:*"
34
- keys.wont_be_empty
35
- end
32
+ Sidekiq.redis { |conn| _(conn.keys("benchmark:*")).wont_be_empty }
36
33
 
37
- post '/benchmarks/remove'
38
- last_response.status.must_equal 302
34
+ post '/benchmarks/remove_all'
35
+ _(last_response.status).must_equal 302
39
36
 
40
- Sidekiq.redis do |conn|
41
- keys = conn.keys "benchmark:*"
42
- keys.must_be_empty
43
- end
37
+ Sidekiq.redis { |conn| _(conn.keys("benchmark:*")).must_be_empty }
38
+ end
39
+
40
+ it "remove benchmark data" do
41
+ WorkerMock.new
42
+
43
+ Sidekiq.redis { |conn| _(conn.keys("benchmark:sidekiq_benchmark_test_workermock:*")).wont_be_empty }
44
+
45
+ post '/benchmarks/remove', type: :sidekiq_benchmark_test_workermock
46
+ _(last_response.status).must_equal 302
47
+
48
+ Sidekiq.redis { |conn| _(conn.keys("benchmark:sidekiq_benchmark_test_workermock:*")).must_be_empty }
44
49
  end
45
50
  end
46
51
  end
@@ -16,12 +16,12 @@ module Sidekiq
16
16
  metrics = @worker.benchmark.metrics
17
17
 
18
18
  @worker.metric_names.each do |metric_name|
19
- metrics[metric_name].wont_be_nil
19
+ _(metrics[metric_name]).wont_be_nil
20
20
  end
21
21
 
22
- @worker.benchmark.start_time.wont_be_nil
23
- @worker.benchmark.finish_time.wont_be_nil
24
- metrics[:assigned_metric].must_equal @worker.assigned_metric
22
+ _(@worker.benchmark.start_time).wont_be_nil
23
+ _(@worker.benchmark.finish_time).wont_be_nil
24
+ _(metrics[:assigned_metric]).must_equal @worker.assigned_metric
25
25
  end
26
26
 
27
27
  it 'should add up metrics' do
@@ -34,10 +34,10 @@ module Sidekiq
34
34
  it "should save metrics to redis" do
35
35
  Sidekiq.redis do |conn|
36
36
  total_time = conn.hget(@worker.benchmark.redis_keys[:total], :job_time)
37
- total_time.wont_be_nil
37
+ _(total_time).wont_be_nil
38
38
 
39
39
  metrics = conn.hkeys(@worker.benchmark.redis_keys[:stats])
40
- metrics.wont_be_empty
40
+ _(metrics).wont_be_empty
41
41
  end
42
42
  end
43
43
 
@@ -47,20 +47,20 @@ module Sidekiq
47
47
 
48
48
  Sidekiq.redis do |conn|
49
49
  metric_set = conn.hkeys(worker.benchmark.redis_keys[:stats])
50
- metric_set.must_be_empty
50
+ _(metric_set).must_be_empty
51
51
  end
52
52
 
53
53
  worker.metric_names.each do |metric_name|
54
- metrics[metric_name].wont_be_nil
54
+ _(metrics[metric_name]).wont_be_nil
55
55
  end
56
56
 
57
- worker.benchmark.finish_time.must_be_nil
57
+ _(worker.benchmark.finish_time).must_be_nil
58
58
  worker.finish
59
- worker.benchmark.finish_time.wont_be_nil
59
+ _(worker.benchmark.finish_time).wont_be_nil
60
60
 
61
61
  Sidekiq.redis do |conn|
62
62
  metric_set = conn.hkeys(worker.benchmark.redis_keys[:stats])
63
- metric_set.wont_be_empty
63
+ _(metric_set).wont_be_empty
64
64
  end
65
65
  end
66
66
 
@@ -68,8 +68,8 @@ module Sidekiq
68
68
  worker = AlterWorkerMock.new
69
69
  value = worker.benchmark.call(:multiply, 4, 4)
70
70
 
71
- value.must_equal 16
72
- worker.benchmark.metrics[:multiply].wont_be_nil
71
+ _(value).must_equal 16
72
+ _(worker.benchmark.metrics[:multiply]).wont_be_nil
73
73
  end
74
74
 
75
75
  end
@@ -18,7 +18,7 @@ require 'sidekiq-benchmark'
18
18
  require 'delorean'
19
19
  require 'pry'
20
20
 
21
- REDIS = Sidekiq::RedisConnection.create url: "redis://localhost/15"
21
+ REDIS = Sidekiq::RedisConnection.create url: "redis://#{ENV['REDIS_HOST'] || 'localhost'}/15"
22
22
  Bundler.require
23
23
 
24
24
  module Sidekiq
@@ -1,31 +1,37 @@
1
1
  /*
2
2
  * Chartkick.js
3
- * Create beautiful Javascript charts with minimal code
3
+ * Create beautiful charts with one line of JavaScript
4
4
  * https://github.com/ankane/chartkick.js
5
- * v1.0.2
5
+ * v3.2.1
6
6
  * MIT License
7
7
  */
8
8
 
9
- /*jslint browser: true, indent: 2, plusplus: true */
10
- /*global google, $*/
11
-
12
- (function() {
13
- 'use strict';
14
-
15
- // helpers
9
+ (function (global, factory) {
10
+ typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() :
11
+ typeof define === 'function' && define.amd ? define(factory) :
12
+ (global = global || self, global.Chartkick = factory());
13
+ }(this, (function () { 'use strict';
16
14
 
17
15
  function isArray(variable) {
18
16
  return Object.prototype.toString.call(variable) === "[object Array]";
19
17
  }
20
18
 
19
+ function isFunction(variable) {
20
+ return variable instanceof Function;
21
+ }
22
+
21
23
  function isPlainObject(variable) {
22
- return variable instanceof Object;
24
+ // protect against prototype pollution, defense 2
25
+ return Object.prototype.toString.call(variable) === "[object Object]" && !isFunction(variable) && variable instanceof Object;
23
26
  }
24
27
 
25
28
  // https://github.com/madrobby/zepto/blob/master/src/zepto.js
26
29
  function extend(target, source) {
27
30
  var key;
28
31
  for (key in source) {
32
+ // protect against prototype pollution, defense 1
33
+ if (key === "__proto__") { continue; }
34
+
29
35
  if (isPlainObject(source[key]) || isArray(source[key])) {
30
36
  if (isPlainObject(source[key]) && !isPlainObject(target[key])) {
31
37
  target[key] = {};
@@ -34,8 +40,7 @@
34
40
  target[key] = [];
35
41
  }
36
42
  extend(target[key], source[key]);
37
- }
38
- else if (source[key] !== undefined) {
43
+ } else if (source[key] !== undefined) {
39
44
  target[key] = source[key];
40
45
  }
41
46
  }
@@ -48,20 +53,23 @@
48
53
  return target;
49
54
  }
50
55
 
56
+ var DATE_PATTERN = /^(\d\d\d\d)(-)?(\d\d)(-)?(\d\d)$/i;
57
+
51
58
  // https://github.com/Do/iso8601.js
52
- var ISO8601_PATTERN = /(\d\d\d\d)(\-)?(\d\d)(\-)?(\d\d)(T)?(\d\d)(:)?(\d\d)?(:)?(\d\d)?([\.,]\d+)?($|Z|([\+\-])(\d\d)(:)?(\d\d)?)/i;
59
+ var ISO8601_PATTERN = /(\d\d\d\d)(-)?(\d\d)(-)?(\d\d)(T)?(\d\d)(:)?(\d\d)?(:)?(\d\d)?([.,]\d+)?($|Z|([+-])(\d\d)(:)?(\d\d)?)/i;
53
60
  var DECIMAL_SEPARATOR = String(1.5).charAt(1);
54
61
 
55
62
  function parseISO8601(input) {
56
63
  var day, hour, matches, milliseconds, minutes, month, offset, result, seconds, type, year;
57
64
  type = Object.prototype.toString.call(input);
58
- if (type === '[object Date]') {
65
+ if (type === "[object Date]") {
59
66
  return input;
60
67
  }
61
- if (type !== '[object String]') {
68
+ if (type !== "[object String]") {
62
69
  return;
63
70
  }
64
- if (matches = input.match(ISO8601_PATTERN)) {
71
+ matches = input.match(ISO8601_PATTERN);
72
+ if (matches) {
65
73
  year = parseInt(matches[1], 10);
66
74
  month = parseInt(matches[3], 10) - 1;
67
75
  day = parseInt(matches[5], 10);
@@ -75,7 +83,7 @@
75
83
  if (matches[17]) {
76
84
  offset += parseInt(matches[17], 10);
77
85
  }
78
- offset *= matches[14] === '-' ? -1 : 1;
86
+ offset *= matches[14] === "-" ? -1 : 1;
79
87
  result -= offset * 60 * 1000;
80
88
  }
81
89
  return new Date(result);
@@ -96,29 +104,92 @@
96
104
  return false;
97
105
  }
98
106
 
99
- function jsOptionsFunc(defaultOptions, hideLegend, setMin, setMax) {
100
- return function(series, opts) {
107
+ function toStr(n) {
108
+ return "" + n;
109
+ }
110
+
111
+ function toFloat(n) {
112
+ return parseFloat(n);
113
+ }
114
+
115
+ function toDate(n) {
116
+ var matches, year, month, day;
117
+ if (typeof n !== "object") {
118
+ if (typeof n === "number") {
119
+ n = new Date(n * 1000); // ms
120
+ } else {
121
+ n = toStr(n);
122
+ if ((matches = n.match(DATE_PATTERN))) {
123
+ year = parseInt(matches[1], 10);
124
+ month = parseInt(matches[3], 10) - 1;
125
+ day = parseInt(matches[5], 10);
126
+ return new Date(year, month, day);
127
+ } else { // str
128
+ // try our best to get the str into iso8601
129
+ // TODO be smarter about this
130
+ var str = n.replace(/ /, "T").replace(" ", "").replace("UTC", "Z");
131
+ n = parseISO8601(str) || new Date(n);
132
+ }
133
+ }
134
+ }
135
+ return n;
136
+ }
137
+
138
+ function toArr(n) {
139
+ if (!isArray(n)) {
140
+ var arr = [], i;
141
+ for (i in n) {
142
+ if (n.hasOwnProperty(i)) {
143
+ arr.push([i, n[i]]);
144
+ }
145
+ }
146
+ n = arr;
147
+ }
148
+ return n;
149
+ }
150
+
151
+ function jsOptionsFunc(defaultOptions, hideLegend, setTitle, setMin, setMax, setStacked, setXtitle, setYtitle) {
152
+ return function (chart, opts, chartOptions) {
153
+ var series = chart.data;
101
154
  var options = merge({}, defaultOptions);
155
+ options = merge(options, chartOptions || {});
156
+
157
+ if (chart.hideLegend || "legend" in opts) {
158
+ hideLegend(options, opts.legend, chart.hideLegend);
159
+ }
102
160
 
103
- // hide legend
104
- // this is *not* an external option!
105
- if (opts.hideLegend) {
106
- hideLegend(options);
161
+ if (opts.title) {
162
+ setTitle(options, opts.title);
107
163
  }
108
164
 
109
165
  // min
110
166
  if ("min" in opts) {
111
167
  setMin(options, opts.min);
112
- }
113
- else if (!negativeValues(series)) {
168
+ } else if (!negativeValues(series)) {
114
169
  setMin(options, 0);
115
170
  }
116
171
 
117
172
  // max
118
- if ("max" in opts) {
173
+ if (opts.max) {
119
174
  setMax(options, opts.max);
120
175
  }
121
176
 
177
+ if ("stacked" in opts) {
178
+ setStacked(options, opts.stacked);
179
+ }
180
+
181
+ if (opts.colors) {
182
+ options.colors = opts.colors;
183
+ }
184
+
185
+ if (opts.xtitle) {
186
+ setXtitle(options, opts.xtitle);
187
+ }
188
+
189
+ if (opts.ytitle) {
190
+ setYtitle(options, opts.ytitle);
191
+ }
192
+
122
193
  // merge library last
123
194
  options = merge(options, opts.library || {});
124
195
 
@@ -126,422 +197,2268 @@
126
197
  };
127
198
  }
128
199
 
129
- // only functions that need defined specific to charting library
130
- var renderLineChart, renderPieChart, renderColumnChart;
200
+ function sortByTime(a, b) {
201
+ return a[0].getTime() - b[0].getTime();
202
+ }
203
+
204
+ function sortByNumberSeries(a, b) {
205
+ return a[0] - b[0];
206
+ }
207
+
208
+ function sortByNumber(a, b) {
209
+ return a - b;
210
+ }
211
+
212
+ function isMinute(d) {
213
+ return d.getMilliseconds() === 0 && d.getSeconds() === 0;
214
+ }
215
+
216
+ function isHour(d) {
217
+ return isMinute(d) && d.getMinutes() === 0;
218
+ }
219
+
220
+ function isDay(d) {
221
+ return isHour(d) && d.getHours() === 0;
222
+ }
223
+
224
+ function isWeek(d, dayOfWeek) {
225
+ return isDay(d) && d.getDay() === dayOfWeek;
226
+ }
227
+
228
+ function isMonth(d) {
229
+ return isDay(d) && d.getDate() === 1;
230
+ }
231
+
232
+ function isYear(d) {
233
+ return isMonth(d) && d.getMonth() === 0;
234
+ }
235
+
236
+ function isDate(obj) {
237
+ return !isNaN(toDate(obj)) && toStr(obj).length >= 6;
238
+ }
239
+
240
+ function isNumber(obj) {
241
+ return typeof obj === "number";
242
+ }
243
+
244
+ var byteSuffixes = ["bytes", "KB", "MB", "GB", "TB", "PB", "EB"];
245
+
246
+ function formatValue(pre, value, options, axis) {
247
+ pre = pre || "";
248
+ if (options.prefix) {
249
+ if (value < 0) {
250
+ value = value * -1;
251
+ pre += "-";
252
+ }
253
+ pre += options.prefix;
254
+ }
131
255
 
132
- if ("Highcharts" in window) {
256
+ var suffix = options.suffix || "";
257
+ var precision = options.precision;
258
+ var round = options.round;
259
+
260
+ if (options.byteScale) {
261
+ var suffixIdx;
262
+ var baseValue = axis ? options.byteScale : value;
263
+
264
+ if (baseValue >= 1152921504606846976) {
265
+ value /= 1152921504606846976;
266
+ suffixIdx = 6;
267
+ } else if (baseValue >= 1125899906842624) {
268
+ value /= 1125899906842624;
269
+ suffixIdx = 5;
270
+ } else if (baseValue >= 1099511627776) {
271
+ value /= 1099511627776;
272
+ suffixIdx = 4;
273
+ } else if (baseValue >= 1073741824) {
274
+ value /= 1073741824;
275
+ suffixIdx = 3;
276
+ } else if (baseValue >= 1048576) {
277
+ value /= 1048576;
278
+ suffixIdx = 2;
279
+ } else if (baseValue >= 1024) {
280
+ value /= 1024;
281
+ suffixIdx = 1;
282
+ } else {
283
+ suffixIdx = 0;
284
+ }
133
285
 
134
- var defaultOptions = {
135
- xAxis: {
136
- labels: {
137
- style: {
138
- fontSize: "12px"
286
+ // TODO handle manual precision case
287
+ if (precision === undefined && round === undefined) {
288
+ if (value >= 1023.5) {
289
+ if (suffixIdx < byteSuffixes.length - 1) {
290
+ value = 1.0;
291
+ suffixIdx += 1;
139
292
  }
140
293
  }
141
- },
142
- yAxis: {
143
- title: {
144
- text: null
145
- },
146
- labels: {
147
- style: {
148
- fontSize: "12px"
149
- }
294
+ precision = value >= 1000 ? 4 : 3;
295
+ }
296
+ suffix = " " + byteSuffixes[suffixIdx];
297
+ }
298
+
299
+ if (precision !== undefined && round !== undefined) {
300
+ throw Error("Use either round or precision, not both");
301
+ }
302
+
303
+ if (!axis) {
304
+ if (precision !== undefined) {
305
+ value = value.toPrecision(precision);
306
+ if (!options.zeros) {
307
+ value = parseFloat(value);
150
308
  }
151
- },
152
- title: {
153
- text: null
154
- },
155
- credits: {
156
- enabled: false
157
- },
158
- legend: {
159
- borderWidth: 0
160
- },
161
- tooltip: {
162
- style: {
163
- fontSize: "12px"
309
+ }
310
+
311
+ if (round !== undefined) {
312
+ if (round < 0) {
313
+ var num = Math.pow(10, -1 * round);
314
+ value = parseInt((1.0 * value / num).toFixed(0)) * num;
315
+ } else {
316
+ value = value.toFixed(round);
317
+ if (!options.zeros) {
318
+ value = parseFloat(value);
319
+ }
164
320
  }
165
321
  }
166
- };
322
+ }
167
323
 
168
- var hideLegend = function(options) {
169
- options.legend.enabled = false;
170
- };
324
+ if (options.thousands || options.decimal) {
325
+ value = toStr(value);
326
+ var parts = value.split(".");
327
+ value = parts[0];
328
+ if (options.thousands) {
329
+ value = value.replace(/\B(?=(\d{3})+(?!\d))/g, options.thousands);
330
+ }
331
+ if (parts.length > 1) {
332
+ value += (options.decimal || ".") + parts[1];
333
+ }
334
+ }
171
335
 
172
- var setMin = function(options, min) {
173
- options.yAxis.min = min;
174
- };
336
+ return pre + value + suffix;
337
+ }
175
338
 
176
- var setMax = function(options, max) {
177
- options.yAxis.max = max;
178
- };
339
+ function seriesOption(chart, series, option) {
340
+ if (option in series) {
341
+ return series[option];
342
+ } else if (option in chart.options) {
343
+ return chart.options[option];
344
+ }
345
+ return null;
346
+ }
179
347
 
180
- var jsOptions = jsOptionsFunc(defaultOptions, hideLegend, setMin, setMax);
348
+ function allZeros(data) {
349
+ var i, j, d;
350
+ for (i = 0; i < data.length; i++) {
351
+ d = data[i].data;
352
+ for (j = 0; j < d.length; j++) {
353
+ if (d[j][1] != 0) {
354
+ return false;
355
+ }
356
+ }
357
+ }
358
+ return true;
359
+ }
181
360
 
182
- renderLineChart = function(element, series, opts) {
183
- var options = jsOptions(series, opts), data, i, j;
184
- options.xAxis.type = "datetime";
185
- options.chart = {type: "spline", renderTo: element.id};
361
+ var baseOptions = {
362
+ maintainAspectRatio: false,
363
+ animation: false,
364
+ tooltips: {
365
+ displayColors: false,
366
+ callbacks: {}
367
+ },
368
+ legend: {},
369
+ title: {fontSize: 20, fontColor: "#333"}
370
+ };
186
371
 
187
- for (i = 0; i < series.length; i++) {
188
- data = series[i].data;
189
- for (j = 0; j < data.length; j++) {
190
- data[j][0] = data[j][0].getTime();
372
+ var defaultOptions = {
373
+ scales: {
374
+ yAxes: [
375
+ {
376
+ ticks: {
377
+ maxTicksLimit: 4
378
+ },
379
+ scaleLabel: {
380
+ fontSize: 16,
381
+ // fontStyle: "bold",
382
+ fontColor: "#333"
383
+ }
384
+ }
385
+ ],
386
+ xAxes: [
387
+ {
388
+ gridLines: {
389
+ drawOnChartArea: false
390
+ },
391
+ scaleLabel: {
392
+ fontSize: 16,
393
+ // fontStyle: "bold",
394
+ fontColor: "#333"
395
+ },
396
+ time: {},
397
+ ticks: {}
191
398
  }
192
- series[i].marker = {symbol: "circle"};
399
+ ]
400
+ }
401
+ };
402
+
403
+ // http://there4.io/2012/05/02/google-chart-color-list/
404
+ var defaultColors = [
405
+ "#3366CC", "#DC3912", "#FF9900", "#109618", "#990099", "#3B3EAC", "#0099C6",
406
+ "#DD4477", "#66AA00", "#B82E2E", "#316395", "#994499", "#22AA99", "#AAAA11",
407
+ "#6633CC", "#E67300", "#8B0707", "#329262", "#5574A6", "#651067"
408
+ ];
409
+
410
+ var hideLegend = function (options, legend, hideLegend) {
411
+ if (legend !== undefined) {
412
+ options.legend.display = !!legend;
413
+ if (legend && legend !== true) {
414
+ options.legend.position = legend;
193
415
  }
194
- options.series = series;
195
- new Highcharts.Chart(options);
196
- };
416
+ } else if (hideLegend) {
417
+ options.legend.display = false;
418
+ }
419
+ };
197
420
 
198
- renderPieChart = function(element, series, opts) {
199
- var options = merge(defaultOptions, opts.library || {});
200
- options.chart = {renderTo: element.id};
201
- options.series = [{
202
- type: "pie",
203
- name: "Value",
204
- data: series
205
- }];
206
- new Highcharts.Chart(options);
207
- };
421
+ var setTitle = function (options, title) {
422
+ options.title.display = true;
423
+ options.title.text = title;
424
+ };
208
425
 
209
- renderColumnChart = function(element, series, opts) {
210
- var options = jsOptions(series, opts), i, j, s, d, rows = [];
211
- options.chart = {type: "column", renderTo: element.id};
426
+ var setMin = function (options, min) {
427
+ if (min !== null) {
428
+ options.scales.yAxes[0].ticks.min = toFloat(min);
429
+ }
430
+ };
212
431
 
213
- for (i = 0; i < series.length; i++) {
214
- s = series[i];
432
+ var setMax = function (options, max) {
433
+ options.scales.yAxes[0].ticks.max = toFloat(max);
434
+ };
215
435
 
216
- for (j = 0; j < s.data.length; j++) {
217
- d = s.data[j];
218
- if (!rows[d[0]]) {
219
- rows[d[0]] = new Array(series.length);
220
- }
221
- rows[d[0]][i] = d[1];
222
- }
223
- }
436
+ var setBarMin = function (options, min) {
437
+ if (min !== null) {
438
+ options.scales.xAxes[0].ticks.min = toFloat(min);
439
+ }
440
+ };
441
+
442
+ var setBarMax = function (options, max) {
443
+ options.scales.xAxes[0].ticks.max = toFloat(max);
444
+ };
445
+
446
+ var setStacked = function (options, stacked) {
447
+ options.scales.xAxes[0].stacked = !!stacked;
448
+ options.scales.yAxes[0].stacked = !!stacked;
449
+ };
224
450
 
225
- var categories = [];
226
- for (i in rows) {
227
- if (rows.hasOwnProperty(i)) {
228
- categories.push(i);
451
+ var setXtitle = function (options, title) {
452
+ options.scales.xAxes[0].scaleLabel.display = true;
453
+ options.scales.xAxes[0].scaleLabel.labelString = title;
454
+ };
455
+
456
+ var setYtitle = function (options, title) {
457
+ options.scales.yAxes[0].scaleLabel.display = true;
458
+ options.scales.yAxes[0].scaleLabel.labelString = title;
459
+ };
460
+
461
+ // https://stackoverflow.com/questions/5623838/rgb-to-hex-and-hex-to-rgb
462
+ var addOpacity = function(hex, opacity) {
463
+ var result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex);
464
+ return result ? "rgba(" + parseInt(result[1], 16) + ", " + parseInt(result[2], 16) + ", " + parseInt(result[3], 16) + ", " + opacity + ")" : hex;
465
+ };
466
+
467
+ // check if not null or undefined
468
+ // https://stackoverflow.com/a/27757708/1177228
469
+ var notnull = function(x) {
470
+ return x != null;
471
+ };
472
+
473
+ var setLabelSize = function (chart, data, options) {
474
+ var maxLabelSize = Math.ceil(chart.element.offsetWidth / 4.0 / data.labels.length);
475
+ if (maxLabelSize > 25) {
476
+ maxLabelSize = 25;
477
+ } else if (maxLabelSize < 10) {
478
+ maxLabelSize = 10;
479
+ }
480
+ if (!options.scales.xAxes[0].ticks.callback) {
481
+ options.scales.xAxes[0].ticks.callback = function (value) {
482
+ value = toStr(value);
483
+ if (value.length > maxLabelSize) {
484
+ return value.substring(0, maxLabelSize - 2) + "...";
485
+ } else {
486
+ return value;
229
487
  }
488
+ };
489
+ }
490
+ };
491
+
492
+ var setFormatOptions = function(chart, options, chartType) {
493
+ var formatOptions = {
494
+ prefix: chart.options.prefix,
495
+ suffix: chart.options.suffix,
496
+ thousands: chart.options.thousands,
497
+ decimal: chart.options.decimal,
498
+ precision: chart.options.precision,
499
+ round: chart.options.round,
500
+ zeros: chart.options.zeros
501
+ };
502
+
503
+ if (chart.options.bytes) {
504
+ var series = chart.data;
505
+ if (chartType === "pie") {
506
+ series = [{data: series}];
230
507
  }
231
- options.xAxis.categories = categories;
232
508
 
233
- var newSeries = [];
234
- for (i = 0; i < series.length; i++) {
235
- d = [];
236
- for (j = 0; j < categories.length; j++) {
237
- d.push(rows[categories[j]][i]);
509
+ // calculate max
510
+ var max = 0;
511
+ for (var i = 0; i < series.length; i++) {
512
+ var s = series[i];
513
+ for (var j = 0; j < s.data.length; j++) {
514
+ if (s.data[j][1] > max) {
515
+ max = s.data[j][1];
516
+ }
238
517
  }
518
+ }
239
519
 
240
- newSeries.push({
241
- name: series[i].name,
242
- data: d
243
- });
520
+ // calculate scale
521
+ var scale = 1;
522
+ while (max >= 1024) {
523
+ scale *= 1024;
524
+ max /= 1024;
244
525
  }
245
- options.series = newSeries;
246
526
 
247
- new Highcharts.Chart(options);
248
- };
249
- } else if ("google" in window) { // Google charts
250
- // load from google
251
- var loaded = false;
252
- google.setOnLoadCallback(function() {
253
- loaded = true;
254
- });
255
- google.load("visualization", "1.0", {"packages": ["corechart"]});
527
+ // set step size
528
+ formatOptions.byteScale = scale;
529
+ }
256
530
 
257
- var waitForLoaded = function(callback) {
258
- google.setOnLoadCallback(callback); // always do this to prevent race conditions (watch out for other issues due to this)
259
- if (loaded) {
260
- callback();
531
+ if (chartType !== "pie") {
532
+ var myAxes = options.scales.yAxes;
533
+ if (chartType === "bar") {
534
+ myAxes = options.scales.xAxes;
261
535
  }
262
- };
263
536
 
264
- // Set chart options
265
- var defaultOptions = {
266
- fontName: "'Lucida Grande', 'Lucida Sans Unicode', Verdana, Arial, Helvetica, sans-serif",
267
- pointSize: 6,
268
- legend: {
269
- textStyle: {
270
- fontSize: 12,
271
- color: "#444"
272
- },
273
- alignment: "center",
274
- position: "right"
275
- },
276
- curveType: "function",
277
- hAxis: {
278
- textStyle: {
279
- color: "#666",
280
- fontSize: 12
281
- },
282
- gridlines: {
283
- color: "transparent"
284
- },
285
- baselineColor: "#ccc"
286
- },
287
- vAxis: {
288
- textStyle: {
289
- color: "#666",
290
- fontSize: 12
291
- },
292
- baselineColor: "#ccc",
293
- viewWindow: {}
294
- },
295
- tooltip: {
296
- textStyle: {
297
- color: "#666",
298
- fontSize: 12
537
+ if (formatOptions.byteScale) {
538
+ if (!myAxes[0].ticks.stepSize) {
539
+ myAxes[0].ticks.stepSize = formatOptions.byteScale / 2;
540
+ }
541
+ if (!myAxes[0].ticks.maxTicksLimit) {
542
+ myAxes[0].ticks.maxTicksLimit = 4;
299
543
  }
300
544
  }
301
- };
302
545
 
303
- var hideLegend = function(options) {
304
- options.legend.position = "none";
305
- };
546
+ if (!myAxes[0].ticks.callback) {
547
+ myAxes[0].ticks.callback = function (value) {
548
+ return formatValue("", value, formatOptions, true);
549
+ };
550
+ }
551
+ }
306
552
 
307
- var setMin = function(options, min) {
308
- options.vAxis.viewWindow.min = min;
309
- };
553
+ if (!options.tooltips.callbacks.label) {
554
+ if (chartType === "scatter") {
555
+ options.tooltips.callbacks.label = function (item, data) {
556
+ var label = data.datasets[item.datasetIndex].label || '';
557
+ if (label) {
558
+ label += ': ';
559
+ }
560
+ return label + '(' + item.xLabel + ', ' + item.yLabel + ')';
561
+ };
562
+ } else if (chartType === "bubble") {
563
+ options.tooltips.callbacks.label = function (item, data) {
564
+ var label = data.datasets[item.datasetIndex].label || '';
565
+ if (label) {
566
+ label += ': ';
567
+ }
568
+ var dataPoint = data.datasets[item.datasetIndex].data[item.index];
569
+ return label + '(' + item.xLabel + ', ' + item.yLabel + ', ' + dataPoint.v + ')';
570
+ };
571
+ } else if (chartType === "pie") {
572
+ // need to use separate label for pie charts
573
+ options.tooltips.callbacks.label = function (tooltipItem, data) {
574
+ var dataLabel = data.labels[tooltipItem.index];
575
+ var value = ': ';
576
+
577
+ if (isArray(dataLabel)) {
578
+ // show value on first line of multiline label
579
+ // need to clone because we are changing the value
580
+ dataLabel = dataLabel.slice();
581
+ dataLabel[0] += value;
582
+ } else {
583
+ dataLabel += value;
584
+ }
310
585
 
311
- var setMax = function(options, max) {
312
- options.vAxis.viewWindow.max = max;
313
- };
586
+ return formatValue(dataLabel, data.datasets[tooltipItem.datasetIndex].data[tooltipItem.index], formatOptions);
587
+ };
588
+ } else {
589
+ var valueLabel = chartType === "bar" ? "xLabel" : "yLabel";
590
+ options.tooltips.callbacks.label = function (tooltipItem, data) {
591
+ var label = data.datasets[tooltipItem.datasetIndex].label || '';
592
+ if (label) {
593
+ label += ': ';
594
+ }
595
+ return formatValue(label, tooltipItem[valueLabel], formatOptions);
596
+ };
597
+ }
598
+ }
599
+ };
600
+
601
+ var jsOptions = jsOptionsFunc(merge(baseOptions, defaultOptions), hideLegend, setTitle, setMin, setMax, setStacked, setXtitle, setYtitle);
602
+
603
+ var createDataTable = function (chart, options, chartType, library) {
604
+ var datasets = [];
605
+ var labels = [];
606
+
607
+ var colors = chart.options.colors || defaultColors;
608
+
609
+ var day = true;
610
+ var week = true;
611
+ var dayOfWeek;
612
+ var month = true;
613
+ var year = true;
614
+ var hour = true;
615
+ var minute = true;
616
+
617
+ var series = chart.data;
618
+
619
+ var max = 0;
620
+ if (chartType === "bubble") {
621
+ for (var i$1 = 0; i$1 < series.length; i$1++) {
622
+ var s$1 = series[i$1];
623
+ for (var j$1 = 0; j$1 < s$1.data.length; j$1++) {
624
+ if (s$1.data[j$1][2] > max) {
625
+ max = s$1.data[j$1][2];
626
+ }
627
+ }
628
+ }
629
+ }
314
630
 
315
- var jsOptions = jsOptionsFunc(defaultOptions, hideLegend, setMin, setMax);
631
+ var i, j, s, d, key, rows = [], rows2 = [];
316
632
 
317
- // cant use object as key
318
- var createDataTable = function(series, columnType) {
319
- var data = new google.visualization.DataTable();
320
- data.addColumn(columnType, "");
633
+ if (chartType === "bar" || chartType === "column" || (chart.xtype !== "number" && chart.xtype !== "bubble")) {
634
+ var sortedLabels = [];
321
635
 
322
- var i, j, s, d, key, rows = [];
323
636
  for (i = 0; i < series.length; i++) {
324
637
  s = series[i];
325
- data.addColumn("number", s.name);
326
638
 
327
639
  for (j = 0; j < s.data.length; j++) {
328
640
  d = s.data[j];
329
- key = (columnType === "datetime") ? d[0].getTime() : d[0];
641
+ key = chart.xtype == "datetime" ? d[0].getTime() : d[0];
330
642
  if (!rows[key]) {
331
643
  rows[key] = new Array(series.length);
332
644
  }
333
645
  rows[key][i] = toFloat(d[1]);
646
+ if (sortedLabels.indexOf(key) === -1) {
647
+ sortedLabels.push(key);
648
+ }
334
649
  }
335
650
  }
336
651
 
337
- var rows2 = [];
338
- for (i in rows) {
339
- if (rows.hasOwnProperty(i)) {
340
- rows2.push([(columnType === "datetime") ? new Date(toFloat(i)) : i].concat(rows[i]));
652
+ if (chart.xtype === "datetime" || chart.xtype === "number") {
653
+ sortedLabels.sort(sortByNumber);
654
+ }
655
+
656
+ for (j = 0; j < series.length; j++) {
657
+ rows2.push([]);
658
+ }
659
+
660
+ var value;
661
+ var k;
662
+ for (k = 0; k < sortedLabels.length; k++) {
663
+ i = sortedLabels[k];
664
+ if (chart.xtype === "datetime") {
665
+ value = new Date(toFloat(i));
666
+ // TODO make this efficient
667
+ day = day && isDay(value);
668
+ if (!dayOfWeek) {
669
+ dayOfWeek = value.getDay();
670
+ }
671
+ week = week && isWeek(value, dayOfWeek);
672
+ month = month && isMonth(value);
673
+ year = year && isYear(value);
674
+ hour = hour && isHour(value);
675
+ minute = minute && isMinute(value);
676
+ } else {
677
+ value = i;
678
+ }
679
+ labels.push(value);
680
+ for (j = 0; j < series.length; j++) {
681
+ // Chart.js doesn't like undefined
682
+ rows2[j].push(rows[i][j] === undefined ? null : rows[i][j]);
341
683
  }
342
684
  }
343
- if (columnType === "datetime") {
344
- rows2.sort(sortByTime);
685
+ } else {
686
+ for (var i$2 = 0; i$2 < series.length; i$2++) {
687
+ var s$2 = series[i$2];
688
+ var d$1 = [];
689
+ for (var j$2 = 0; j$2 < s$2.data.length; j$2++) {
690
+ var point = {
691
+ x: toFloat(s$2.data[j$2][0]),
692
+ y: toFloat(s$2.data[j$2][1])
693
+ };
694
+ if (chartType === "bubble") {
695
+ point.r = toFloat(s$2.data[j$2][2]) * 20 / max;
696
+ // custom attribute, for tooltip
697
+ point.v = s$2.data[j$2][2];
698
+ }
699
+ d$1.push(point);
700
+ }
701
+ rows2.push(d$1);
345
702
  }
703
+ }
346
704
 
347
- data.addRows(rows2);
705
+ for (i = 0; i < series.length; i++) {
706
+ s = series[i];
707
+
708
+ var color = s.color || colors[i];
709
+ var backgroundColor = chartType !== "line" ? addOpacity(color, 0.5) : color;
710
+
711
+ var dataset = {
712
+ label: s.name || "",
713
+ data: rows2[i],
714
+ fill: chartType === "area",
715
+ borderColor: color,
716
+ backgroundColor: backgroundColor,
717
+ pointBackgroundColor: color,
718
+ borderWidth: 2,
719
+ pointHoverBackgroundColor: color
720
+ };
721
+
722
+ if (s.stack) {
723
+ dataset.stack = s.stack;
724
+ }
348
725
 
349
- return data;
350
- };
726
+ var curve = seriesOption(chart, s, "curve");
727
+ if (curve === false) {
728
+ dataset.lineTension = 0;
729
+ }
351
730
 
352
- renderLineChart = function(element, series, opts) {
353
- waitForLoaded(function() {
354
- var options = jsOptions(series, opts);
355
- var data = createDataTable(series, "datetime");
356
- var chart = new google.visualization.LineChart(element);
357
- chart.draw(data, options);
358
- });
359
- };
731
+ var points = seriesOption(chart, s, "points");
732
+ if (points === false) {
733
+ dataset.pointRadius = 0;
734
+ dataset.pointHitRadius = 5;
735
+ }
360
736
 
361
- renderPieChart = function(element, series, opts) {
362
- waitForLoaded(function() {
363
- var options = merge(defaultOptions, opts.library || {});
364
- options.chartArea = {
365
- top: "10%",
366
- height: "80%"
367
- };
737
+ dataset = merge(dataset, chart.options.dataset || {});
738
+ dataset = merge(dataset, s.library || {});
739
+ dataset = merge(dataset, s.dataset || {});
368
740
 
369
- var data = new google.visualization.DataTable();
370
- data.addColumn("string", "");
371
- data.addColumn("number", "Value");
372
- data.addRows(series);
741
+ datasets.push(dataset);
742
+ }
373
743
 
374
- var chart = new google.visualization.PieChart(element);
375
- chart.draw(data, options);
376
- });
377
- };
744
+ var xmin = chart.options.xmin;
745
+ var xmax = chart.options.xmax;
378
746
 
379
- renderColumnChart = function(element, series, opts) {
380
- waitForLoaded(function() {
381
- var options = jsOptions(series, opts);
382
- var data = createDataTable(series, "string");
383
- var chart = new google.visualization.ColumnChart(element);
384
- chart.draw(data, options);
385
- });
386
- };
387
- } else { // no chart library installed
388
- renderLineChart = renderPieChart = renderColumnChart = function() {
389
- throw new Error("Please install Google Charts or Highcharts");
390
- };
391
- }
747
+ if (chart.xtype === "datetime") {
748
+ // hacky check for Chart.js >= 2.9.0
749
+ // https://github.com/chartjs/Chart.js/compare/v2.8.0...v2.9.0
750
+ var gte29 = "math" in library.helpers;
751
+ var ticksKey = gte29 ? "ticks" : "time";
752
+ if (notnull(xmin)) {
753
+ options.scales.xAxes[0][ticksKey].min = toDate(xmin).getTime();
754
+ }
755
+ if (notnull(xmax)) {
756
+ options.scales.xAxes[0][ticksKey].max = toDate(xmax).getTime();
757
+ }
758
+ } else if (chart.xtype === "number") {
759
+ if (notnull(xmin)) {
760
+ options.scales.xAxes[0].ticks.min = xmin;
761
+ }
762
+ if (notnull(xmax)) {
763
+ options.scales.xAxes[0].ticks.max = xmax;
764
+ }
765
+ }
392
766
 
393
- function setText(element, text) {
394
- if (document.body.innerText) {
395
- element.innerText = text;
396
- } else {
397
- element.textContent = text;
767
+ // for empty datetime chart
768
+ if (chart.xtype === "datetime" && labels.length === 0) {
769
+ if (notnull(xmin)) {
770
+ labels.push(toDate(xmin));
771
+ }
772
+ if (notnull(xmax)) {
773
+ labels.push(toDate(xmax));
774
+ }
775
+ day = false;
776
+ week = false;
777
+ month = false;
778
+ year = false;
779
+ hour = false;
780
+ minute = false;
398
781
  }
399
- }
400
782
 
401
- function chartError(element, message) {
402
- setText(element, "Error Loading Chart: " + message);
403
- element.style.color = "#ff0000";
404
- }
783
+ if (chart.xtype === "datetime" && labels.length > 0) {
784
+ var minTime = (notnull(xmin) ? toDate(xmin) : labels[0]).getTime();
785
+ var maxTime = (notnull(xmax) ? toDate(xmax) : labels[0]).getTime();
405
786
 
406
- function getJSON(element, url, success) {
407
- $.ajax({
408
- dataType: "json",
409
- url: url,
410
- success: success,
411
- error: function(jqXHR, textStatus, errorThrown) {
412
- var message = (typeof errorThrown === "string") ? errorThrown : errorThrown.message;
413
- chartError(element, message);
787
+ for (i = 1; i < labels.length; i++) {
788
+ var value$1 = labels[i].getTime();
789
+ if (value$1 < minTime) {
790
+ minTime = value$1;
791
+ }
792
+ if (value$1 > maxTime) {
793
+ maxTime = value$1;
794
+ }
414
795
  }
415
- });
416
- }
417
796
 
418
- function errorCatcher(element, data, opts, callback) {
419
- try {
420
- callback(element, data, opts);
421
- } catch (err) {
422
- chartError(element, err.message);
423
- throw err;
797
+ var timeDiff = (maxTime - minTime) / (86400 * 1000.0);
798
+
799
+ if (!options.scales.xAxes[0].time.unit) {
800
+ var step;
801
+ if (year || timeDiff > 365 * 10) {
802
+ options.scales.xAxes[0].time.unit = "year";
803
+ step = 365;
804
+ } else if (month || timeDiff > 30 * 10) {
805
+ options.scales.xAxes[0].time.unit = "month";
806
+ step = 30;
807
+ } else if (day || timeDiff > 10) {
808
+ options.scales.xAxes[0].time.unit = "day";
809
+ step = 1;
810
+ } else if (hour || timeDiff > 0.5) {
811
+ options.scales.xAxes[0].time.displayFormats = {hour: "MMM D, h a"};
812
+ options.scales.xAxes[0].time.unit = "hour";
813
+ step = 1 / 24.0;
814
+ } else if (minute) {
815
+ options.scales.xAxes[0].time.displayFormats = {minute: "h:mm a"};
816
+ options.scales.xAxes[0].time.unit = "minute";
817
+ step = 1 / 24.0 / 60.0;
818
+ }
819
+
820
+ if (step && timeDiff > 0) {
821
+ var unitStepSize = Math.ceil(timeDiff / step / (chart.element.offsetWidth / 100.0));
822
+ if (week && step === 1) {
823
+ unitStepSize = Math.ceil(unitStepSize / 7.0) * 7;
824
+ }
825
+ options.scales.xAxes[0].time.unitStepSize = unitStepSize;
826
+ }
827
+ }
828
+
829
+ if (!options.scales.xAxes[0].time.tooltipFormat) {
830
+ if (day) {
831
+ options.scales.xAxes[0].time.tooltipFormat = "ll";
832
+ } else if (hour) {
833
+ options.scales.xAxes[0].time.tooltipFormat = "MMM D, h a";
834
+ } else if (minute) {
835
+ options.scales.xAxes[0].time.tooltipFormat = "h:mm a";
836
+ }
837
+ }
838
+ }
839
+
840
+ var data = {
841
+ labels: labels,
842
+ datasets: datasets
843
+ };
844
+
845
+ return data;
846
+ };
847
+
848
+ var defaultExport = function defaultExport(library) {
849
+ this.name = "chartjs";
850
+ this.library = library;
851
+ };
852
+
853
+ defaultExport.prototype.renderLineChart = function renderLineChart (chart, chartType) {
854
+ var chartOptions = {};
855
+ // fix for https://github.com/chartjs/Chart.js/issues/2441
856
+ if (!chart.options.max && allZeros(chart.data)) {
857
+ chartOptions.max = 1;
858
+ }
859
+
860
+ var options = jsOptions(chart, merge(chartOptions, chart.options));
861
+ setFormatOptions(chart, options, chartType);
862
+
863
+ var data = createDataTable(chart, options, chartType || "line", this.library);
864
+
865
+ if (chart.xtype === "number") {
866
+ options.scales.xAxes[0].type = "linear";
867
+ options.scales.xAxes[0].position = "bottom";
868
+ } else {
869
+ options.scales.xAxes[0].type = chart.xtype === "string" ? "category" : "time";
870
+ }
871
+
872
+ this.drawChart(chart, "line", data, options);
873
+ };
874
+
875
+ defaultExport.prototype.renderPieChart = function renderPieChart (chart) {
876
+ var options = merge({}, baseOptions);
877
+ if (chart.options.donut) {
878
+ options.cutoutPercentage = 50;
879
+ }
880
+
881
+ if ("legend" in chart.options) {
882
+ hideLegend(options, chart.options.legend);
883
+ }
884
+
885
+ if (chart.options.title) {
886
+ setTitle(options, chart.options.title);
887
+ }
888
+
889
+ options = merge(options, chart.options.library || {});
890
+ setFormatOptions(chart, options, "pie");
891
+
892
+ var labels = [];
893
+ var values = [];
894
+ for (var i = 0; i < chart.data.length; i++) {
895
+ var point = chart.data[i];
896
+ labels.push(point[0]);
897
+ values.push(point[1]);
898
+ }
899
+
900
+ var dataset = {
901
+ data: values,
902
+ backgroundColor: chart.options.colors || defaultColors
903
+ };
904
+ dataset = merge(dataset, chart.options.dataset || {});
905
+
906
+ var data = {
907
+ labels: labels,
908
+ datasets: [dataset]
909
+ };
910
+
911
+ this.drawChart(chart, "pie", data, options);
912
+ };
913
+
914
+ defaultExport.prototype.renderColumnChart = function renderColumnChart (chart, chartType) {
915
+ var options;
916
+ if (chartType === "bar") {
917
+ var barOptions = merge(baseOptions, defaultOptions);
918
+ delete barOptions.scales.yAxes[0].ticks.maxTicksLimit;
919
+ options = jsOptionsFunc(barOptions, hideLegend, setTitle, setBarMin, setBarMax, setStacked, setXtitle, setYtitle)(chart, chart.options);
920
+ } else {
921
+ options = jsOptions(chart, chart.options);
922
+ }
923
+ setFormatOptions(chart, options, chartType);
924
+ var data = createDataTable(chart, options, "column", this.library);
925
+ if (chartType !== "bar") {
926
+ setLabelSize(chart, data, options);
927
+ }
928
+ this.drawChart(chart, (chartType === "bar" ? "horizontalBar" : "bar"), data, options);
929
+ };
930
+
931
+ defaultExport.prototype.renderAreaChart = function renderAreaChart (chart) {
932
+ this.renderLineChart(chart, "area");
933
+ };
934
+
935
+ defaultExport.prototype.renderBarChart = function renderBarChart (chart) {
936
+ this.renderColumnChart(chart, "bar");
937
+ };
938
+
939
+ defaultExport.prototype.renderScatterChart = function renderScatterChart (chart, chartType) {
940
+ chartType = chartType || "scatter";
941
+
942
+ var options = jsOptions(chart, chart.options);
943
+ setFormatOptions(chart, options, chartType);
944
+
945
+ if (!("showLines" in options)) {
946
+ options.showLines = false;
947
+ }
948
+
949
+ var data = createDataTable(chart, options, chartType, this.library);
950
+
951
+ options.scales.xAxes[0].type = "linear";
952
+ options.scales.xAxes[0].position = "bottom";
953
+
954
+ this.drawChart(chart, chartType, data, options);
955
+ };
956
+
957
+ defaultExport.prototype.renderBubbleChart = function renderBubbleChart (chart) {
958
+ this.renderScatterChart(chart, "bubble");
959
+ };
960
+
961
+ defaultExport.prototype.destroy = function destroy (chart) {
962
+ if (chart.chart) {
963
+ chart.chart.destroy();
964
+ }
965
+ };
966
+
967
+ defaultExport.prototype.drawChart = function drawChart (chart, type, data, options) {
968
+ this.destroy(chart);
969
+
970
+ var chartOptions = {
971
+ type: type,
972
+ data: data,
973
+ options: options
974
+ };
975
+
976
+ if (chart.options.code) {
977
+ window.console.log("new Chart(ctx, " + JSON.stringify(chartOptions) + ");");
978
+ }
979
+
980
+ chart.element.innerHTML = "<canvas></canvas>";
981
+ var ctx = chart.element.getElementsByTagName("CANVAS")[0];
982
+ chart.chart = new this.library(ctx, chartOptions);
983
+ };
984
+
985
+ var defaultOptions$1 = {
986
+ chart: {},
987
+ xAxis: {
988
+ title: {
989
+ text: null
990
+ },
991
+ labels: {
992
+ style: {
993
+ fontSize: "12px"
994
+ }
995
+ }
996
+ },
997
+ yAxis: {
998
+ title: {
999
+ text: null
1000
+ },
1001
+ labels: {
1002
+ style: {
1003
+ fontSize: "12px"
1004
+ }
1005
+ }
1006
+ },
1007
+ title: {
1008
+ text: null
1009
+ },
1010
+ credits: {
1011
+ enabled: false
1012
+ },
1013
+ legend: {
1014
+ borderWidth: 0
1015
+ },
1016
+ tooltip: {
1017
+ style: {
1018
+ fontSize: "12px"
1019
+ }
1020
+ },
1021
+ plotOptions: {
1022
+ areaspline: {},
1023
+ area: {},
1024
+ series: {
1025
+ marker: {}
1026
+ }
1027
+ }
1028
+ };
1029
+
1030
+ var hideLegend$1 = function (options, legend, hideLegend) {
1031
+ if (legend !== undefined) {
1032
+ options.legend.enabled = !!legend;
1033
+ if (legend && legend !== true) {
1034
+ if (legend === "top" || legend === "bottom") {
1035
+ options.legend.verticalAlign = legend;
1036
+ } else {
1037
+ options.legend.layout = "vertical";
1038
+ options.legend.verticalAlign = "middle";
1039
+ options.legend.align = legend;
1040
+ }
1041
+ }
1042
+ } else if (hideLegend) {
1043
+ options.legend.enabled = false;
1044
+ }
1045
+ };
1046
+
1047
+ var setTitle$1 = function (options, title) {
1048
+ options.title.text = title;
1049
+ };
1050
+
1051
+ var setMin$1 = function (options, min) {
1052
+ options.yAxis.min = min;
1053
+ };
1054
+
1055
+ var setMax$1 = function (options, max) {
1056
+ options.yAxis.max = max;
1057
+ };
1058
+
1059
+ var setStacked$1 = function (options, stacked) {
1060
+ var stackedValue = stacked ? (stacked === true ? "normal" : stacked) : null;
1061
+ options.plotOptions.series.stacking = stackedValue;
1062
+ options.plotOptions.area.stacking = stackedValue;
1063
+ options.plotOptions.areaspline.stacking = stackedValue;
1064
+ };
1065
+
1066
+ var setXtitle$1 = function (options, title) {
1067
+ options.xAxis.title.text = title;
1068
+ };
1069
+
1070
+ var setYtitle$1 = function (options, title) {
1071
+ options.yAxis.title.text = title;
1072
+ };
1073
+
1074
+ var jsOptions$1 = jsOptionsFunc(defaultOptions$1, hideLegend$1, setTitle$1, setMin$1, setMax$1, setStacked$1, setXtitle$1, setYtitle$1);
1075
+
1076
+ var setFormatOptions$1 = function(chart, options, chartType) {
1077
+ var formatOptions = {
1078
+ prefix: chart.options.prefix,
1079
+ suffix: chart.options.suffix,
1080
+ thousands: chart.options.thousands,
1081
+ decimal: chart.options.decimal,
1082
+ precision: chart.options.precision,
1083
+ round: chart.options.round,
1084
+ zeros: chart.options.zeros
1085
+ };
1086
+
1087
+ if (chartType !== "pie" && !options.yAxis.labels.formatter) {
1088
+ options.yAxis.labels.formatter = function () {
1089
+ return formatValue("", this.value, formatOptions);
1090
+ };
1091
+ }
1092
+
1093
+ if (!options.tooltip.pointFormatter) {
1094
+ options.tooltip.pointFormatter = function () {
1095
+ return '<span style="color:' + this.color + '">\u25CF</span> ' + formatValue(this.series.name + ': <b>', this.y, formatOptions) + '</b><br/>';
1096
+ };
1097
+ }
1098
+ };
1099
+
1100
+ var defaultExport$1 = function defaultExport(library) {
1101
+ this.name = "highcharts";
1102
+ this.library = library;
1103
+ };
1104
+
1105
+ defaultExport$1.prototype.renderLineChart = function renderLineChart (chart, chartType) {
1106
+ chartType = chartType || "spline";
1107
+ var chartOptions = {};
1108
+ if (chartType === "areaspline") {
1109
+ chartOptions = {
1110
+ plotOptions: {
1111
+ areaspline: {
1112
+ stacking: "normal"
1113
+ },
1114
+ area: {
1115
+ stacking: "normal"
1116
+ },
1117
+ series: {
1118
+ marker: {
1119
+ enabled: false
1120
+ }
1121
+ }
1122
+ }
1123
+ };
1124
+ }
1125
+
1126
+ if (chart.options.curve === false) {
1127
+ if (chartType === "areaspline") {
1128
+ chartType = "area";
1129
+ } else if (chartType === "spline") {
1130
+ chartType = "line";
1131
+ }
1132
+ }
1133
+
1134
+ var options = jsOptions$1(chart, chart.options, chartOptions), data, i, j;
1135
+ options.xAxis.type = chart.xtype === "string" ? "category" : (chart.xtype === "number" ? "linear" : "datetime");
1136
+ if (!options.chart.type) {
1137
+ options.chart.type = chartType;
1138
+ }
1139
+ setFormatOptions$1(chart, options, chartType);
1140
+
1141
+ var series = chart.data;
1142
+ for (i = 0; i < series.length; i++) {
1143
+ series[i].name = series[i].name || "Value";
1144
+ data = series[i].data;
1145
+ if (chart.xtype === "datetime") {
1146
+ for (j = 0; j < data.length; j++) {
1147
+ data[j][0] = data[j][0].getTime();
1148
+ }
1149
+ }
1150
+ series[i].marker = {symbol: "circle"};
1151
+ if (chart.options.points === false) {
1152
+ series[i].marker.enabled = false;
1153
+ }
1154
+ }
1155
+
1156
+ this.drawChart(chart, series, options);
1157
+ };
1158
+
1159
+ defaultExport$1.prototype.renderScatterChart = function renderScatterChart (chart) {
1160
+ var options = jsOptions$1(chart, chart.options, {});
1161
+ options.chart.type = "scatter";
1162
+ this.drawChart(chart, chart.data, options);
1163
+ };
1164
+
1165
+ defaultExport$1.prototype.renderPieChart = function renderPieChart (chart) {
1166
+ var chartOptions = merge(defaultOptions$1, {});
1167
+
1168
+ if (chart.options.colors) {
1169
+ chartOptions.colors = chart.options.colors;
1170
+ }
1171
+ if (chart.options.donut) {
1172
+ chartOptions.plotOptions = {pie: {innerSize: "50%"}};
1173
+ }
1174
+
1175
+ if ("legend" in chart.options) {
1176
+ hideLegend$1(chartOptions, chart.options.legend);
1177
+ }
1178
+
1179
+ if (chart.options.title) {
1180
+ setTitle$1(chartOptions, chart.options.title);
1181
+ }
1182
+
1183
+ var options = merge(chartOptions, chart.options.library || {});
1184
+ setFormatOptions$1(chart, options, "pie");
1185
+ var series = [{
1186
+ type: "pie",
1187
+ name: chart.options.label || "Value",
1188
+ data: chart.data
1189
+ }];
1190
+
1191
+ this.drawChart(chart, series, options);
1192
+ };
1193
+
1194
+ defaultExport$1.prototype.renderColumnChart = function renderColumnChart (chart, chartType) {
1195
+ chartType = chartType || "column";
1196
+ var series = chart.data;
1197
+ var options = jsOptions$1(chart, chart.options), i, j, s, d, rows = [], categories = [];
1198
+ options.chart.type = chartType;
1199
+ setFormatOptions$1(chart, options, chartType);
1200
+
1201
+ for (i = 0; i < series.length; i++) {
1202
+ s = series[i];
1203
+
1204
+ for (j = 0; j < s.data.length; j++) {
1205
+ d = s.data[j];
1206
+ if (!rows[d[0]]) {
1207
+ rows[d[0]] = new Array(series.length);
1208
+ categories.push(d[0]);
1209
+ }
1210
+ rows[d[0]][i] = d[1];
1211
+ }
1212
+ }
1213
+
1214
+ if (chart.xtype === "number") {
1215
+ categories.sort(sortByNumber);
1216
+ }
1217
+
1218
+ options.xAxis.categories = categories;
1219
+
1220
+ var newSeries = [], d2;
1221
+ for (i = 0; i < series.length; i++) {
1222
+ d = [];
1223
+ for (j = 0; j < categories.length; j++) {
1224
+ d.push(rows[categories[j]][i] || 0);
1225
+ }
1226
+
1227
+ d2 = {
1228
+ name: series[i].name || "Value",
1229
+ data: d
1230
+ };
1231
+ if (series[i].stack) {
1232
+ d2.stack = series[i].stack;
1233
+ }
1234
+
1235
+ newSeries.push(d2);
1236
+ }
1237
+
1238
+ this.drawChart(chart, newSeries, options);
1239
+ };
1240
+
1241
+ defaultExport$1.prototype.renderBarChart = function renderBarChart (chart) {
1242
+ this.renderColumnChart(chart, "bar");
1243
+ };
1244
+
1245
+ defaultExport$1.prototype.renderAreaChart = function renderAreaChart (chart) {
1246
+ this.renderLineChart(chart, "areaspline");
1247
+ };
1248
+
1249
+ defaultExport$1.prototype.destroy = function destroy (chart) {
1250
+ if (chart.chart) {
1251
+ chart.chart.destroy();
1252
+ }
1253
+ };
1254
+
1255
+ defaultExport$1.prototype.drawChart = function drawChart (chart, data, options) {
1256
+ this.destroy(chart);
1257
+
1258
+ options.chart.renderTo = chart.element.id;
1259
+ options.series = data;
1260
+
1261
+ if (chart.options.code) {
1262
+ window.console.log("new Highcharts.Chart(" + JSON.stringify(options) + ");");
1263
+ }
1264
+
1265
+ chart.chart = new this.library.Chart(options);
1266
+ };
1267
+
1268
+ var loaded = {};
1269
+ var callbacks = [];
1270
+
1271
+ // Set chart options
1272
+ var defaultOptions$2 = {
1273
+ chartArea: {},
1274
+ fontName: "'Lucida Grande', 'Lucida Sans Unicode', Verdana, Arial, Helvetica, sans-serif",
1275
+ pointSize: 6,
1276
+ legend: {
1277
+ textStyle: {
1278
+ fontSize: 12,
1279
+ color: "#444"
1280
+ },
1281
+ alignment: "center",
1282
+ position: "right"
1283
+ },
1284
+ curveType: "function",
1285
+ hAxis: {
1286
+ textStyle: {
1287
+ color: "#666",
1288
+ fontSize: 12
1289
+ },
1290
+ titleTextStyle: {},
1291
+ gridlines: {
1292
+ color: "transparent"
1293
+ },
1294
+ baselineColor: "#ccc",
1295
+ viewWindow: {}
1296
+ },
1297
+ vAxis: {
1298
+ textStyle: {
1299
+ color: "#666",
1300
+ fontSize: 12
1301
+ },
1302
+ titleTextStyle: {},
1303
+ baselineColor: "#ccc",
1304
+ viewWindow: {}
1305
+ },
1306
+ tooltip: {
1307
+ textStyle: {
1308
+ color: "#666",
1309
+ fontSize: 12
1310
+ }
1311
+ }
1312
+ };
1313
+
1314
+ var hideLegend$2 = function (options, legend, hideLegend) {
1315
+ if (legend !== undefined) {
1316
+ var position;
1317
+ if (!legend) {
1318
+ position = "none";
1319
+ } else if (legend === true) {
1320
+ position = "right";
1321
+ } else {
1322
+ position = legend;
1323
+ }
1324
+ options.legend.position = position;
1325
+ } else if (hideLegend) {
1326
+ options.legend.position = "none";
1327
+ }
1328
+ };
1329
+
1330
+ var setTitle$2 = function (options, title) {
1331
+ options.title = title;
1332
+ options.titleTextStyle = {color: "#333", fontSize: "20px"};
1333
+ };
1334
+
1335
+ var setMin$2 = function (options, min) {
1336
+ options.vAxis.viewWindow.min = min;
1337
+ };
1338
+
1339
+ var setMax$2 = function (options, max) {
1340
+ options.vAxis.viewWindow.max = max;
1341
+ };
1342
+
1343
+ var setBarMin$1 = function (options, min) {
1344
+ options.hAxis.viewWindow.min = min;
1345
+ };
1346
+
1347
+ var setBarMax$1 = function (options, max) {
1348
+ options.hAxis.viewWindow.max = max;
1349
+ };
1350
+
1351
+ var setStacked$2 = function (options, stacked) {
1352
+ options.isStacked = stacked ? stacked : false;
1353
+ };
1354
+
1355
+ var setXtitle$2 = function (options, title) {
1356
+ options.hAxis.title = title;
1357
+ options.hAxis.titleTextStyle.italic = false;
1358
+ };
1359
+
1360
+ var setYtitle$2 = function (options, title) {
1361
+ options.vAxis.title = title;
1362
+ options.vAxis.titleTextStyle.italic = false;
1363
+ };
1364
+
1365
+ var jsOptions$2 = jsOptionsFunc(defaultOptions$2, hideLegend$2, setTitle$2, setMin$2, setMax$2, setStacked$2, setXtitle$2, setYtitle$2);
1366
+
1367
+ var resize = function (callback) {
1368
+ if (window.attachEvent) {
1369
+ window.attachEvent("onresize", callback);
1370
+ } else if (window.addEventListener) {
1371
+ window.addEventListener("resize", callback, true);
1372
+ }
1373
+ callback();
1374
+ };
1375
+
1376
+ var defaultExport$2 = function defaultExport(library) {
1377
+ this.name = "google";
1378
+ this.library = library;
1379
+ };
1380
+
1381
+ defaultExport$2.prototype.renderLineChart = function renderLineChart (chart) {
1382
+ var this$1 = this;
1383
+
1384
+ this.waitForLoaded(chart, function () {
1385
+ var chartOptions = {};
1386
+
1387
+ if (chart.options.curve === false) {
1388
+ chartOptions.curveType = "none";
1389
+ }
1390
+
1391
+ if (chart.options.points === false) {
1392
+ chartOptions.pointSize = 0;
1393
+ }
1394
+
1395
+ var options = jsOptions$2(chart, chart.options, chartOptions);
1396
+ var data = this$1.createDataTable(chart.data, chart.xtype);
1397
+
1398
+ this$1.drawChart(chart, "LineChart", data, options);
1399
+ });
1400
+ };
1401
+
1402
+ defaultExport$2.prototype.renderPieChart = function renderPieChart (chart) {
1403
+ var this$1 = this;
1404
+
1405
+ this.waitForLoaded(chart, function () {
1406
+ var chartOptions = {
1407
+ chartArea: {
1408
+ top: "10%",
1409
+ height: "80%"
1410
+ },
1411
+ legend: {}
1412
+ };
1413
+ if (chart.options.colors) {
1414
+ chartOptions.colors = chart.options.colors;
1415
+ }
1416
+ if (chart.options.donut) {
1417
+ chartOptions.pieHole = 0.5;
1418
+ }
1419
+ if ("legend" in chart.options) {
1420
+ hideLegend$2(chartOptions, chart.options.legend);
1421
+ }
1422
+ if (chart.options.title) {
1423
+ setTitle$2(chartOptions, chart.options.title);
1424
+ }
1425
+ var options = merge(merge(defaultOptions$2, chartOptions), chart.options.library || {});
1426
+
1427
+ var data = new this$1.library.visualization.DataTable();
1428
+ data.addColumn("string", "");
1429
+ data.addColumn("number", "Value");
1430
+ data.addRows(chart.data);
1431
+
1432
+ this$1.drawChart(chart, "PieChart", data, options);
1433
+ });
1434
+ };
1435
+
1436
+ defaultExport$2.prototype.renderColumnChart = function renderColumnChart (chart) {
1437
+ var this$1 = this;
1438
+
1439
+ this.waitForLoaded(chart, function () {
1440
+ var options = jsOptions$2(chart, chart.options);
1441
+ var data = this$1.createDataTable(chart.data, chart.xtype);
1442
+
1443
+ this$1.drawChart(chart, "ColumnChart", data, options);
1444
+ });
1445
+ };
1446
+
1447
+ defaultExport$2.prototype.renderBarChart = function renderBarChart (chart) {
1448
+ var this$1 = this;
1449
+
1450
+ this.waitForLoaded(chart, function () {
1451
+ var chartOptions = {
1452
+ hAxis: {
1453
+ gridlines: {
1454
+ color: "#ccc"
1455
+ }
1456
+ }
1457
+ };
1458
+ var options = jsOptionsFunc(defaultOptions$2, hideLegend$2, setTitle$2, setBarMin$1, setBarMax$1, setStacked$2, setXtitle$2, setYtitle$2)(chart, chart.options, chartOptions);
1459
+ var data = this$1.createDataTable(chart.data, chart.xtype);
1460
+
1461
+ this$1.drawChart(chart, "BarChart", data, options);
1462
+ });
1463
+ };
1464
+
1465
+ defaultExport$2.prototype.renderAreaChart = function renderAreaChart (chart) {
1466
+ var this$1 = this;
1467
+
1468
+ this.waitForLoaded(chart, function () {
1469
+ var chartOptions = {
1470
+ isStacked: true,
1471
+ pointSize: 0,
1472
+ areaOpacity: 0.5
1473
+ };
1474
+
1475
+ var options = jsOptions$2(chart, chart.options, chartOptions);
1476
+ var data = this$1.createDataTable(chart.data, chart.xtype);
1477
+
1478
+ this$1.drawChart(chart, "AreaChart", data, options);
1479
+ });
1480
+ };
1481
+
1482
+ defaultExport$2.prototype.renderGeoChart = function renderGeoChart (chart) {
1483
+ var this$1 = this;
1484
+
1485
+ this.waitForLoaded(chart, "geochart", function () {
1486
+ var chartOptions = {
1487
+ legend: "none",
1488
+ colorAxis: {
1489
+ colors: chart.options.colors || ["#f6c7b6", "#ce502d"]
1490
+ }
1491
+ };
1492
+ var options = merge(merge(defaultOptions$2, chartOptions), chart.options.library || {});
1493
+
1494
+ var data = new this$1.library.visualization.DataTable();
1495
+ data.addColumn("string", "");
1496
+ data.addColumn("number", chart.options.label || "Value");
1497
+ data.addRows(chart.data);
1498
+
1499
+ this$1.drawChart(chart, "GeoChart", data, options);
1500
+ });
1501
+ };
1502
+
1503
+ defaultExport$2.prototype.renderScatterChart = function renderScatterChart (chart) {
1504
+ var this$1 = this;
1505
+
1506
+ this.waitForLoaded(chart, function () {
1507
+ var chartOptions = {};
1508
+ var options = jsOptions$2(chart, chart.options, chartOptions);
1509
+
1510
+ var series = chart.data, rows2 = [], i, j, data, d;
1511
+ for (i = 0; i < series.length; i++) {
1512
+ series[i].name = series[i].name || "Value";
1513
+ d = series[i].data;
1514
+ for (j = 0; j < d.length; j++) {
1515
+ var row = new Array(series.length + 1);
1516
+ row[0] = d[j][0];
1517
+ row[i + 1] = d[j][1];
1518
+ rows2.push(row);
1519
+ }
1520
+ }
1521
+
1522
+ data = new this$1.library.visualization.DataTable();
1523
+ data.addColumn("number", "");
1524
+ for (i = 0; i < series.length; i++) {
1525
+ data.addColumn("number", series[i].name);
1526
+ }
1527
+ data.addRows(rows2);
1528
+
1529
+ this$1.drawChart(chart, "ScatterChart", data, options);
1530
+ });
1531
+ };
1532
+
1533
+ defaultExport$2.prototype.renderTimeline = function renderTimeline (chart) {
1534
+ var this$1 = this;
1535
+
1536
+ this.waitForLoaded(chart, "timeline", function () {
1537
+ var chartOptions = {
1538
+ legend: "none"
1539
+ };
1540
+
1541
+ if (chart.options.colors) {
1542
+ chartOptions.colors = chart.options.colors;
1543
+ }
1544
+ var options = merge(merge(defaultOptions$2, chartOptions), chart.options.library || {});
1545
+
1546
+ var data = new this$1.library.visualization.DataTable();
1547
+ data.addColumn({type: "string", id: "Name"});
1548
+ data.addColumn({type: "date", id: "Start"});
1549
+ data.addColumn({type: "date", id: "End"});
1550
+ data.addRows(chart.data);
1551
+
1552
+ chart.element.style.lineHeight = "normal";
1553
+
1554
+ this$1.drawChart(chart, "Timeline", data, options);
1555
+ });
1556
+ };
1557
+
1558
+ defaultExport$2.prototype.destroy = function destroy (chart) {
1559
+ if (chart.chart) {
1560
+ chart.chart.clearChart();
1561
+ }
1562
+ };
1563
+
1564
+ defaultExport$2.prototype.drawChart = function drawChart (chart, type, data, options) {
1565
+ this.destroy(chart);
1566
+
1567
+ if (chart.options.code) {
1568
+ window.console.log("var data = new google.visualization.DataTable(" + data.toJSON() + ");\nvar chart = new google.visualization." + type + "(element);\nchart.draw(data, " + JSON.stringify(options) + ");");
1569
+ }
1570
+
1571
+ chart.chart = new this.library.visualization[type](chart.element);
1572
+ resize(function () {
1573
+ chart.chart.draw(data, options);
1574
+ });
1575
+ };
1576
+
1577
+ defaultExport$2.prototype.waitForLoaded = function waitForLoaded (chart, pack, callback) {
1578
+ var this$1 = this;
1579
+
1580
+ if (!callback) {
1581
+ callback = pack;
1582
+ pack = "corechart";
1583
+ }
1584
+
1585
+ callbacks.push({pack: pack, callback: callback});
1586
+
1587
+ if (loaded[pack]) {
1588
+ this.runCallbacks();
1589
+ } else {
1590
+ loaded[pack] = true;
1591
+
1592
+ // https://groups.google.com/forum/#!topic/google-visualization-api/fMKJcyA2yyI
1593
+ var loadOptions = {
1594
+ packages: [pack],
1595
+ callback: function () { this$1.runCallbacks(); }
1596
+ };
1597
+ var config = chart.__config();
1598
+ if (config.language) {
1599
+ loadOptions.language = config.language;
1600
+ }
1601
+ if (pack === "geochart" && config.mapsApiKey) {
1602
+ loadOptions.mapsApiKey = config.mapsApiKey;
1603
+ }
1604
+
1605
+ this.library.charts.load("current", loadOptions);
1606
+ }
1607
+ };
1608
+
1609
+ defaultExport$2.prototype.runCallbacks = function runCallbacks () {
1610
+ var cb, call;
1611
+ for (var i = 0; i < callbacks.length; i++) {
1612
+ cb = callbacks[i];
1613
+ call = this.library.visualization && ((cb.pack === "corechart" && this.library.visualization.LineChart) || (cb.pack === "timeline" && this.library.visualization.Timeline) || (cb.pack === "geochart" && this.library.visualization.GeoChart));
1614
+ if (call) {
1615
+ cb.callback();
1616
+ callbacks.splice(i, 1);
1617
+ i--;
1618
+ }
1619
+ }
1620
+ };
1621
+
1622
+ // cant use object as key
1623
+ defaultExport$2.prototype.createDataTable = function createDataTable (series, columnType) {
1624
+ var i, j, s, d, key, rows = [], sortedLabels = [];
1625
+ for (i = 0; i < series.length; i++) {
1626
+ s = series[i];
1627
+ series[i].name = series[i].name || "Value";
1628
+
1629
+ for (j = 0; j < s.data.length; j++) {
1630
+ d = s.data[j];
1631
+ key = (columnType === "datetime") ? d[0].getTime() : d[0];
1632
+ if (!rows[key]) {
1633
+ rows[key] = new Array(series.length);
1634
+ sortedLabels.push(key);
1635
+ }
1636
+ rows[key][i] = toFloat(d[1]);
1637
+ }
1638
+ }
1639
+
1640
+ var rows2 = [];
1641
+ var day = true;
1642
+ var value;
1643
+ for (j = 0; j < sortedLabels.length; j++) {
1644
+ i = sortedLabels[j];
1645
+ if (columnType === "datetime") {
1646
+ value = new Date(toFloat(i));
1647
+ day = day && isDay(value);
1648
+ } else if (columnType === "number") {
1649
+ value = toFloat(i);
1650
+ } else {
1651
+ value = i;
1652
+ }
1653
+ rows2.push([value].concat(rows[i]));
1654
+ }
1655
+ if (columnType === "datetime") {
1656
+ rows2.sort(sortByTime);
1657
+ } else if (columnType === "number") {
1658
+ rows2.sort(sortByNumberSeries);
1659
+
1660
+ for (i = 0; i < rows2.length; i++) {
1661
+ rows2[i][0] = toStr(rows2[i][0]);
1662
+ }
1663
+
1664
+ columnType = "string";
1665
+ }
1666
+
1667
+ // create datatable
1668
+ var data = new this.library.visualization.DataTable();
1669
+ columnType = columnType === "datetime" && day ? "date" : columnType;
1670
+ data.addColumn(columnType, "");
1671
+ for (i = 0; i < series.length; i++) {
1672
+ data.addColumn("number", series[i].name);
1673
+ }
1674
+ data.addRows(rows2);
1675
+
1676
+ return data;
1677
+ };
1678
+
1679
+ var pendingRequests = [], runningRequests = 0, maxRequests = 4;
1680
+
1681
+ function pushRequest(url, success, error) {
1682
+ pendingRequests.push([url, success, error]);
1683
+ runNext();
1684
+ }
1685
+
1686
+ function runNext() {
1687
+ if (runningRequests < maxRequests) {
1688
+ var request = pendingRequests.shift();
1689
+ if (request) {
1690
+ runningRequests++;
1691
+ getJSON(request[0], request[1], request[2]);
1692
+ runNext();
1693
+ }
1694
+ }
1695
+ }
1696
+
1697
+ function requestComplete() {
1698
+ runningRequests--;
1699
+ runNext();
1700
+ }
1701
+
1702
+ function getJSON(url, success, error) {
1703
+ ajaxCall(url, success, function (jqXHR, textStatus, errorThrown) {
1704
+ var message = (typeof errorThrown === "string") ? errorThrown : errorThrown.message;
1705
+ error(message);
1706
+ });
1707
+ }
1708
+
1709
+ function ajaxCall(url, success, error) {
1710
+ var $ = window.jQuery || window.Zepto || window.$;
1711
+
1712
+ if ($ && $.ajax) {
1713
+ $.ajax({
1714
+ dataType: "json",
1715
+ url: url,
1716
+ success: success,
1717
+ error: error,
1718
+ complete: requestComplete
1719
+ });
1720
+ } else {
1721
+ var xhr = new XMLHttpRequest();
1722
+ xhr.open("GET", url, true);
1723
+ xhr.setRequestHeader("Content-Type", "application/json");
1724
+ xhr.onload = function () {
1725
+ requestComplete();
1726
+ if (xhr.status === 200) {
1727
+ success(JSON.parse(xhr.responseText), xhr.statusText, xhr);
1728
+ } else {
1729
+ error(xhr, "error", xhr.statusText);
1730
+ }
1731
+ };
1732
+ xhr.send();
1733
+ }
1734
+ }
1735
+
1736
+ var config = {};
1737
+ var adapters = [];
1738
+
1739
+ // helpers
1740
+
1741
+ function setText(element, text) {
1742
+ if (document.body.innerText) {
1743
+ element.innerText = text;
1744
+ } else {
1745
+ element.textContent = text;
1746
+ }
1747
+ }
1748
+
1749
+ // TODO remove prefix for all messages
1750
+ function chartError(element, message, noPrefix) {
1751
+ if (!noPrefix) {
1752
+ message = "Error Loading Chart: " + message;
1753
+ }
1754
+ setText(element, message);
1755
+ element.style.color = "#ff0000";
1756
+ }
1757
+
1758
+ function errorCatcher(chart) {
1759
+ try {
1760
+ chart.__render();
1761
+ } catch (err) {
1762
+ chartError(chart.element, err.message);
1763
+ throw err;
424
1764
  }
425
1765
  }
426
1766
 
427
- function fetchDataSource(element, dataSource, opts, callback) {
1767
+ function fetchDataSource(chart, dataSource) {
428
1768
  if (typeof dataSource === "string") {
429
- getJSON(element, dataSource, function(data, textStatus, jqXHR) {
430
- errorCatcher(element, data, opts, callback);
1769
+ pushRequest(dataSource, function (data) {
1770
+ chart.rawData = data;
1771
+ errorCatcher(chart);
1772
+ }, function (message) {
1773
+ chartError(chart.element, message);
431
1774
  });
1775
+ } else if (typeof dataSource === "function") {
1776
+ try {
1777
+ dataSource(function (data) {
1778
+ chart.rawData = data;
1779
+ errorCatcher(chart);
1780
+ }, function (message) {
1781
+ chartError(chart.element, message, true);
1782
+ });
1783
+ } catch (err) {
1784
+ chartError(chart.element, err, true);
1785
+ }
432
1786
  } else {
433
- errorCatcher(element, dataSource, opts, callback);
1787
+ chart.rawData = dataSource;
1788
+ errorCatcher(chart);
434
1789
  }
435
1790
  }
436
1791
 
437
- // type conversions
1792
+ function addDownloadButton(chart) {
1793
+ var element = chart.element;
1794
+ var link = document.createElement("a");
438
1795
 
439
- function toStr(n) {
440
- return "" + n;
1796
+ var download = chart.options.download;
1797
+ if (download === true) {
1798
+ download = {};
1799
+ } else if (typeof download === "string") {
1800
+ download = {filename: download};
1801
+ }
1802
+ link.download = download.filename || "chart.png"; // https://caniuse.com/download
1803
+
1804
+ link.style.position = "absolute";
1805
+ link.style.top = "20px";
1806
+ link.style.right = "20px";
1807
+ link.style.zIndex = 1000;
1808
+ link.style.lineHeight = "20px";
1809
+ link.target = "_blank"; // for safari
1810
+ var image = document.createElement("img");
1811
+ image.alt = "Download";
1812
+ image.style.border = "none";
1813
+ // icon from font-awesome
1814
+ // http://fa2png.io/
1815
+ image.src = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAUCAMAAAC6V+0/AAABCFBMVEUAAADMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMywEsqxAAAAV3RSTlMAAQIDBggJCgsMDQ4PERQaHB0eISIjJCouLzE0OTo/QUJHSUpLTU5PUllhYmltcHh5foWLjI+SlaCio6atr7S1t7m6vsHHyM7R2tze5Obo7fHz9ff5+/1hlxK2AAAA30lEQVQYGUXBhVYCQQBA0TdYWAt2d3d3YWAHyur7/z9xgD16Lw0DW+XKx+1GgX+FRzM3HWQWrHl5N/oapW5RPe0PkBu+UYeICvozTWZVK23Ao04B79oJrOsJDOoxkZoQPWgX29pHpCZEk7rEvQYiNSFq1UMqvlCjJkRBS1R8hb00Vb/TajtBL7nTHE1X1vyMQF732dQhyF2o6SAwrzP06iUQzvwsArlnzcOdrgBhJyHa1QOgO9U1GsKuvjUTjavliZYQ8nNPapG6sap/3nrIdJ6bOWzmX/fy0XVpfzZP3S8OJT3g9EEiJwAAAABJRU5ErkJggg==";
1816
+ link.appendChild(image);
1817
+ element.style.position = "relative";
1818
+
1819
+ chart.__downloadAttached = true;
1820
+
1821
+ // mouseenter
1822
+ chart.__enterEvent = addEvent(element, "mouseover", function(e) {
1823
+ var related = e.relatedTarget;
1824
+ // check download option again to ensure it wasn't changed
1825
+ if ((!related || (related !== this && !childOf(this, related))) && chart.options.download) {
1826
+ link.href = chart.toImage(download);
1827
+ element.appendChild(link);
1828
+ }
1829
+ });
1830
+
1831
+ // mouseleave
1832
+ chart.__leaveEvent = addEvent(element, "mouseout", function(e) {
1833
+ var related = e.relatedTarget;
1834
+ if (!related || (related !== this && !childOf(this, related))) {
1835
+ if (link.parentNode) {
1836
+ link.parentNode.removeChild(link);
1837
+ }
1838
+ }
1839
+ });
441
1840
  }
442
1841
 
443
- function toFloat(n) {
444
- return parseFloat(n);
1842
+ // https://stackoverflow.com/questions/10149963/adding-event-listener-cross-browser
1843
+ function addEvent(elem, event, fn) {
1844
+ if (elem.addEventListener) {
1845
+ elem.addEventListener(event, fn, false);
1846
+ return fn;
1847
+ } else {
1848
+ var fn2 = function() {
1849
+ // set the this pointer same as addEventListener when fn is called
1850
+ return(fn.call(elem, window.event));
1851
+ };
1852
+ elem.attachEvent("on" + event, fn2);
1853
+ return fn2;
1854
+ }
445
1855
  }
446
1856
 
447
- function toDate(n) {
448
- if (typeof n !== "object") {
449
- if (typeof n === "number") {
450
- n = new Date(n * 1000); // ms
451
- } else { // str
452
- // try our best to get the str into iso8601
453
- // TODO be smarter about this
454
- var str = n.replace(/ /, "T").replace(" ", "").replace("UTC", "Z");
455
- n = parseISO8601(str) || new Date(n);
1857
+ function removeEvent(elem, event, fn) {
1858
+ if (elem.removeEventListener) {
1859
+ elem.removeEventListener(event, fn, false);
1860
+ } else {
1861
+ elem.detachEvent("on" + event, fn);
1862
+ }
1863
+ }
1864
+
1865
+ // https://gist.github.com/shawnbot/4166283
1866
+ function childOf(p, c) {
1867
+ if (p === c) { return false; }
1868
+ while (c && c !== p) { c = c.parentNode; }
1869
+ return c === p;
1870
+ }
1871
+
1872
+ function getAdapterType(library) {
1873
+ if (library) {
1874
+ if (library.product === "Highcharts") {
1875
+ return defaultExport$1;
1876
+ } else if (library.charts) {
1877
+ return defaultExport$2;
1878
+ } else if (isFunction(library)) {
1879
+ return defaultExport;
456
1880
  }
457
1881
  }
458
- return n;
1882
+ throw new Error("Unknown adapter");
459
1883
  }
460
1884
 
461
- function toArr(n) {
462
- if (!isArray(n)) {
463
- var arr = [], i;
464
- for (i in n) {
465
- if (n.hasOwnProperty(i)) {
466
- arr.push([i, n[i]]);
1885
+ function addAdapter(library) {
1886
+ var adapterType = getAdapterType(library);
1887
+ var adapter = new adapterType(library);
1888
+
1889
+ if (adapters.indexOf(adapter) === -1) {
1890
+ adapters.push(adapter);
1891
+ }
1892
+ }
1893
+
1894
+ function loadAdapters() {
1895
+ if ("Chart" in window) {
1896
+ addAdapter(window.Chart);
1897
+ }
1898
+
1899
+ if ("Highcharts" in window) {
1900
+ addAdapter(window.Highcharts);
1901
+ }
1902
+
1903
+ if (window.google && window.google.charts) {
1904
+ addAdapter(window.google);
1905
+ }
1906
+ }
1907
+
1908
+ function dataEmpty(data, chartType) {
1909
+ if (chartType === "PieChart" || chartType === "GeoChart" || chartType === "Timeline") {
1910
+ return data.length === 0;
1911
+ } else {
1912
+ for (var i = 0; i < data.length; i++) {
1913
+ if (data[i].data.length > 0) {
1914
+ return false;
467
1915
  }
468
1916
  }
469
- n = arr;
1917
+ return true;
470
1918
  }
471
- return n;
472
1919
  }
473
1920
 
474
- // process data
1921
+ function renderChart(chartType, chart) {
1922
+ if (chart.options.messages && chart.options.messages.empty && dataEmpty(chart.data, chartType)) {
1923
+ setText(chart.element, chart.options.messages.empty);
1924
+ } else {
1925
+ callAdapter(chartType, chart);
1926
+ if (chart.options.download && !chart.__downloadAttached && chart.adapter === "chartjs") {
1927
+ addDownloadButton(chart);
1928
+ }
1929
+ }
1930
+ }
475
1931
 
476
- function sortByTime(a, b) {
477
- return a[0].getTime() - b[0].getTime();
1932
+ // TODO remove chartType if cross-browser way
1933
+ // to get the name of the chart class
1934
+ function callAdapter(chartType, chart) {
1935
+ var i, adapter, fnName, adapterName;
1936
+ fnName = "render" + chartType;
1937
+ adapterName = chart.options.adapter;
1938
+
1939
+ loadAdapters();
1940
+
1941
+ for (i = 0; i < adapters.length; i++) {
1942
+ adapter = adapters[i];
1943
+ if ((!adapterName || adapterName === adapter.name) && isFunction(adapter[fnName])) {
1944
+ chart.adapter = adapter.name;
1945
+ chart.__adapterObject = adapter;
1946
+ return adapter[fnName](chart);
1947
+ }
1948
+ }
1949
+
1950
+ if (adapters.length > 0) {
1951
+ throw new Error("No charting library found for " + chartType);
1952
+ } else {
1953
+ throw new Error("No charting libraries found - be sure to include one before your charts");
1954
+ }
478
1955
  }
479
1956
 
480
- function processSeries(series, opts, time) {
481
- var i, j, data, r, key;
1957
+ // process data
482
1958
 
483
- // see if one series or multiple
484
- if (!isArray(series) || typeof series[0] !== "object" || isArray(series[0])) {
485
- series = [{name: "Value", data: series}];
486
- opts.hideLegend = true;
1959
+ var toFormattedKey = function (key, keyType) {
1960
+ if (keyType === "number") {
1961
+ key = toFloat(key);
1962
+ } else if (keyType === "datetime") {
1963
+ key = toDate(key);
487
1964
  } else {
488
- opts.hideLegend = false;
1965
+ key = toStr(key);
489
1966
  }
1967
+ return key;
1968
+ };
490
1969
 
491
- // right format
492
- for (i = 0; i < series.length; i++) {
493
- data = toArr(series[i].data);
494
- r = [];
495
- for (j = 0; j < data.length; j++) {
496
- key = data[j][0];
497
- key = time ? toDate(key) : toStr(key);
1970
+ var formatSeriesData = function (data, keyType) {
1971
+ var r = [], key, j;
1972
+ for (j = 0; j < data.length; j++) {
1973
+ if (keyType === "bubble") {
1974
+ r.push([toFloat(data[j][0]), toFloat(data[j][1]), toFloat(data[j][2])]);
1975
+ } else {
1976
+ key = toFormattedKey(data[j][0], keyType);
498
1977
  r.push([key, toFloat(data[j][1])]);
499
1978
  }
500
- if (time) {
501
- r.sort(sortByTime);
1979
+ }
1980
+ if (keyType === "datetime") {
1981
+ r.sort(sortByTime);
1982
+ } else if (keyType === "number") {
1983
+ r.sort(sortByNumberSeries);
1984
+ }
1985
+ return r;
1986
+ };
1987
+
1988
+ function detectXType(series, noDatetime, options) {
1989
+ if (dataEmpty(series)) {
1990
+ if ((options.xmin || options.xmax) && (!options.xmin || isDate(options.xmin)) && (!options.xmax || isDate(options.xmax))) {
1991
+ return "datetime";
1992
+ } else {
1993
+ return "number";
502
1994
  }
503
- series[i].data = r;
1995
+ } else if (detectXTypeWithFunction(series, isNumber)) {
1996
+ return "number";
1997
+ } else if (!noDatetime && detectXTypeWithFunction(series, isDate)) {
1998
+ return "datetime";
1999
+ } else {
2000
+ return "string";
504
2001
  }
2002
+ }
505
2003
 
506
- return series;
2004
+ function detectXTypeWithFunction(series, func) {
2005
+ var i, j, data;
2006
+ for (i = 0; i < series.length; i++) {
2007
+ data = toArr(series[i].data);
2008
+ for (j = 0; j < data.length; j++) {
2009
+ if (!func(data[j][0])) {
2010
+ return false;
2011
+ }
2012
+ }
2013
+ }
2014
+ return true;
507
2015
  }
508
2016
 
509
- function processLineData(element, data, opts) {
510
- renderLineChart(element, processSeries(data, opts, true), opts);
2017
+ // creates a shallow copy of each element of the array
2018
+ // elements are expected to be objects
2019
+ function copySeries(series) {
2020
+ var newSeries = [], i, j;
2021
+ for (i = 0; i < series.length; i++) {
2022
+ var copy = {};
2023
+ for (j in series[i]) {
2024
+ if (series[i].hasOwnProperty(j)) {
2025
+ copy[j] = series[i][j];
2026
+ }
2027
+ }
2028
+ newSeries.push(copy);
2029
+ }
2030
+ return newSeries;
511
2031
  }
512
2032
 
513
- function processColumnData(element, data, opts) {
514
- renderColumnChart(element, processSeries(data, opts, false), opts);
2033
+ function processSeries(chart, keyType, noDatetime) {
2034
+ var i;
2035
+
2036
+ var opts = chart.options;
2037
+ var series = chart.rawData;
2038
+
2039
+ // see if one series or multiple
2040
+ if (!isArray(series) || typeof series[0] !== "object" || isArray(series[0])) {
2041
+ series = [{name: opts.label, data: series}];
2042
+ chart.hideLegend = true;
2043
+ } else {
2044
+ chart.hideLegend = false;
2045
+ }
2046
+
2047
+ // convert to array
2048
+ // must come before dataEmpty check
2049
+ series = copySeries(series);
2050
+ for (i = 0; i < series.length; i++) {
2051
+ series[i].data = toArr(series[i].data);
2052
+ }
2053
+
2054
+ chart.xtype = keyType ? keyType : (opts.discrete ? "string" : detectXType(series, noDatetime, opts));
2055
+
2056
+ // right format
2057
+ for (i = 0; i < series.length; i++) {
2058
+ series[i].data = formatSeriesData(series[i].data, chart.xtype);
2059
+ }
2060
+
2061
+ return series;
515
2062
  }
516
2063
 
517
- function processPieData(element, data, opts) {
518
- var perfectData = toArr(data), i;
2064
+ function processSimple(chart) {
2065
+ var perfectData = toArr(chart.rawData), i;
519
2066
  for (i = 0; i < perfectData.length; i++) {
520
2067
  perfectData[i] = [toStr(perfectData[i][0]), toFloat(perfectData[i][1])];
521
2068
  }
522
- renderPieChart(element, perfectData, opts);
2069
+ return perfectData;
523
2070
  }
524
2071
 
525
- function setElement(element, data, opts, callback) {
2072
+ // define classes
2073
+
2074
+ var Chart = function Chart(element, dataSource, options) {
2075
+ var elementId;
526
2076
  if (typeof element === "string") {
2077
+ elementId = element;
527
2078
  element = document.getElementById(element);
2079
+ if (!element) {
2080
+ throw new Error("No element with id " + elementId);
2081
+ }
528
2082
  }
529
- fetchDataSource(element, data, opts || {}, callback);
530
- }
2083
+ this.element = element;
2084
+ this.options = merge(Chartkick.options, options || {});
2085
+ this.dataSource = dataSource;
531
2086
 
532
- // define classes
2087
+ Chartkick.charts[element.id] = this;
2088
+
2089
+ fetchDataSource(this, dataSource);
2090
+
2091
+ if (this.options.refresh) {
2092
+ this.startRefresh();
2093
+ }
2094
+ };
2095
+
2096
+ Chart.prototype.getElement = function getElement () {
2097
+ return this.element;
2098
+ };
2099
+
2100
+ Chart.prototype.getDataSource = function getDataSource () {
2101
+ return this.dataSource;
2102
+ };
2103
+
2104
+ Chart.prototype.getData = function getData () {
2105
+ return this.data;
2106
+ };
2107
+
2108
+ Chart.prototype.getOptions = function getOptions () {
2109
+ return this.options;
2110
+ };
2111
+
2112
+ Chart.prototype.getChartObject = function getChartObject () {
2113
+ return this.chart;
2114
+ };
2115
+
2116
+ Chart.prototype.getAdapter = function getAdapter () {
2117
+ return this.adapter;
2118
+ };
2119
+
2120
+ Chart.prototype.updateData = function updateData (dataSource, options) {
2121
+ this.dataSource = dataSource;
2122
+ if (options) {
2123
+ this.__updateOptions(options);
2124
+ }
2125
+ fetchDataSource(this, dataSource);
2126
+ };
2127
+
2128
+ Chart.prototype.setOptions = function setOptions (options) {
2129
+ this.__updateOptions(options);
2130
+ this.redraw();
2131
+ };
2132
+
2133
+ Chart.prototype.redraw = function redraw () {
2134
+ fetchDataSource(this, this.rawData);
2135
+ };
2136
+
2137
+ Chart.prototype.refreshData = function refreshData () {
2138
+ if (typeof this.dataSource === "string") {
2139
+ // prevent browser from caching
2140
+ var sep = this.dataSource.indexOf("?") === -1 ? "?" : "&";
2141
+ var url = this.dataSource + sep + "_=" + (new Date()).getTime();
2142
+ fetchDataSource(this, url);
2143
+ } else if (typeof this.dataSource === "function") {
2144
+ fetchDataSource(this, this.dataSource);
2145
+ }
2146
+ };
2147
+
2148
+ Chart.prototype.startRefresh = function startRefresh () {
2149
+ var this$1 = this;
2150
+
2151
+ var refresh = this.options.refresh;
2152
+
2153
+ if (refresh && typeof this.dataSource !== "string" && typeof this.dataSource !== "function") {
2154
+ throw new Error("Data source must be a URL or callback for refresh");
2155
+ }
2156
+
2157
+ if (!this.intervalId) {
2158
+ if (refresh) {
2159
+ this.intervalId = setInterval( function () {
2160
+ this$1.refreshData();
2161
+ }, refresh * 1000);
2162
+ } else {
2163
+ throw new Error("No refresh interval");
2164
+ }
2165
+ }
2166
+ };
2167
+
2168
+ Chart.prototype.stopRefresh = function stopRefresh () {
2169
+ if (this.intervalId) {
2170
+ clearInterval(this.intervalId);
2171
+ this.intervalId = null;
2172
+ }
2173
+ };
2174
+
2175
+ Chart.prototype.toImage = function toImage (download) {
2176
+ if (this.adapter === "chartjs") {
2177
+ if (download && download.background && download.background !== "transparent") {
2178
+ // https://stackoverflow.com/questions/30464750/chartjs-line-chart-set-background-color
2179
+ var canvas = this.chart.chart.canvas;
2180
+ var ctx = this.chart.chart.ctx;
2181
+ var tmpCanvas = document.createElement("canvas");
2182
+ var tmpCtx = tmpCanvas.getContext("2d");
2183
+ tmpCanvas.width = ctx.canvas.width;
2184
+ tmpCanvas.height = ctx.canvas.height;
2185
+ tmpCtx.fillStyle = download.background;
2186
+ tmpCtx.fillRect(0, 0, tmpCanvas.width, tmpCanvas.height);
2187
+ tmpCtx.drawImage(canvas, 0, 0);
2188
+ return tmpCanvas.toDataURL("image/png");
2189
+ } else {
2190
+ return this.chart.toBase64Image();
2191
+ }
2192
+ } else {
2193
+ // TODO throw error in next major version
2194
+ // throw new Error("Feature only available for Chart.js");
2195
+ return null;
2196
+ }
2197
+ };
2198
+
2199
+ Chart.prototype.destroy = function destroy () {
2200
+ if (this.__adapterObject) {
2201
+ this.__adapterObject.destroy(this);
2202
+ }
2203
+
2204
+ if (this.__enterEvent) {
2205
+ removeEvent(this.element, "mouseover", this.__enterEvent);
2206
+ }
2207
+
2208
+ if (this.__leaveEvent) {
2209
+ removeEvent(this.element, "mouseout", this.__leaveEvent);
2210
+ }
2211
+ };
2212
+
2213
+ Chart.prototype.__updateOptions = function __updateOptions (options) {
2214
+ var updateRefresh = options.refresh && options.refresh !== this.options.refresh;
2215
+ this.options = merge(Chartkick.options, options);
2216
+ if (updateRefresh) {
2217
+ this.stopRefresh();
2218
+ this.startRefresh();
2219
+ }
2220
+ };
2221
+
2222
+ Chart.prototype.__render = function __render () {
2223
+ this.data = this.__processData();
2224
+ renderChart(this.__chartName(), this);
2225
+ };
2226
+
2227
+ Chart.prototype.__config = function __config () {
2228
+ return config;
2229
+ };
2230
+
2231
+ var LineChart = /*@__PURE__*/(function (Chart) {
2232
+ function LineChart () {
2233
+ Chart.apply(this, arguments);
2234
+ }
2235
+
2236
+ if ( Chart ) LineChart.__proto__ = Chart;
2237
+ LineChart.prototype = Object.create( Chart && Chart.prototype );
2238
+ LineChart.prototype.constructor = LineChart;
2239
+
2240
+ LineChart.prototype.__processData = function __processData () {
2241
+ return processSeries(this);
2242
+ };
2243
+
2244
+ LineChart.prototype.__chartName = function __chartName () {
2245
+ return "LineChart";
2246
+ };
2247
+
2248
+ return LineChart;
2249
+ }(Chart));
2250
+
2251
+ var PieChart = /*@__PURE__*/(function (Chart) {
2252
+ function PieChart () {
2253
+ Chart.apply(this, arguments);
2254
+ }
2255
+
2256
+ if ( Chart ) PieChart.__proto__ = Chart;
2257
+ PieChart.prototype = Object.create( Chart && Chart.prototype );
2258
+ PieChart.prototype.constructor = PieChart;
2259
+
2260
+ PieChart.prototype.__processData = function __processData () {
2261
+ return processSimple(this);
2262
+ };
2263
+
2264
+ PieChart.prototype.__chartName = function __chartName () {
2265
+ return "PieChart";
2266
+ };
2267
+
2268
+ return PieChart;
2269
+ }(Chart));
2270
+
2271
+ var ColumnChart = /*@__PURE__*/(function (Chart) {
2272
+ function ColumnChart () {
2273
+ Chart.apply(this, arguments);
2274
+ }
2275
+
2276
+ if ( Chart ) ColumnChart.__proto__ = Chart;
2277
+ ColumnChart.prototype = Object.create( Chart && Chart.prototype );
2278
+ ColumnChart.prototype.constructor = ColumnChart;
2279
+
2280
+ ColumnChart.prototype.__processData = function __processData () {
2281
+ return processSeries(this, null, true);
2282
+ };
2283
+
2284
+ ColumnChart.prototype.__chartName = function __chartName () {
2285
+ return "ColumnChart";
2286
+ };
2287
+
2288
+ return ColumnChart;
2289
+ }(Chart));
2290
+
2291
+ var BarChart = /*@__PURE__*/(function (Chart) {
2292
+ function BarChart () {
2293
+ Chart.apply(this, arguments);
2294
+ }
2295
+
2296
+ if ( Chart ) BarChart.__proto__ = Chart;
2297
+ BarChart.prototype = Object.create( Chart && Chart.prototype );
2298
+ BarChart.prototype.constructor = BarChart;
2299
+
2300
+ BarChart.prototype.__processData = function __processData () {
2301
+ return processSeries(this, null, true);
2302
+ };
2303
+
2304
+ BarChart.prototype.__chartName = function __chartName () {
2305
+ return "BarChart";
2306
+ };
2307
+
2308
+ return BarChart;
2309
+ }(Chart));
2310
+
2311
+ var AreaChart = /*@__PURE__*/(function (Chart) {
2312
+ function AreaChart () {
2313
+ Chart.apply(this, arguments);
2314
+ }
2315
+
2316
+ if ( Chart ) AreaChart.__proto__ = Chart;
2317
+ AreaChart.prototype = Object.create( Chart && Chart.prototype );
2318
+ AreaChart.prototype.constructor = AreaChart;
2319
+
2320
+ AreaChart.prototype.__processData = function __processData () {
2321
+ return processSeries(this);
2322
+ };
2323
+
2324
+ AreaChart.prototype.__chartName = function __chartName () {
2325
+ return "AreaChart";
2326
+ };
2327
+
2328
+ return AreaChart;
2329
+ }(Chart));
2330
+
2331
+ var GeoChart = /*@__PURE__*/(function (Chart) {
2332
+ function GeoChart () {
2333
+ Chart.apply(this, arguments);
2334
+ }
2335
+
2336
+ if ( Chart ) GeoChart.__proto__ = Chart;
2337
+ GeoChart.prototype = Object.create( Chart && Chart.prototype );
2338
+ GeoChart.prototype.constructor = GeoChart;
2339
+
2340
+ GeoChart.prototype.__processData = function __processData () {
2341
+ return processSimple(this);
2342
+ };
2343
+
2344
+ GeoChart.prototype.__chartName = function __chartName () {
2345
+ return "GeoChart";
2346
+ };
2347
+
2348
+ return GeoChart;
2349
+ }(Chart));
2350
+
2351
+ var ScatterChart = /*@__PURE__*/(function (Chart) {
2352
+ function ScatterChart () {
2353
+ Chart.apply(this, arguments);
2354
+ }
2355
+
2356
+ if ( Chart ) ScatterChart.__proto__ = Chart;
2357
+ ScatterChart.prototype = Object.create( Chart && Chart.prototype );
2358
+ ScatterChart.prototype.constructor = ScatterChart;
2359
+
2360
+ ScatterChart.prototype.__processData = function __processData () {
2361
+ return processSeries(this, "number");
2362
+ };
2363
+
2364
+ ScatterChart.prototype.__chartName = function __chartName () {
2365
+ return "ScatterChart";
2366
+ };
2367
+
2368
+ return ScatterChart;
2369
+ }(Chart));
2370
+
2371
+ var BubbleChart = /*@__PURE__*/(function (Chart) {
2372
+ function BubbleChart () {
2373
+ Chart.apply(this, arguments);
2374
+ }
2375
+
2376
+ if ( Chart ) BubbleChart.__proto__ = Chart;
2377
+ BubbleChart.prototype = Object.create( Chart && Chart.prototype );
2378
+ BubbleChart.prototype.constructor = BubbleChart;
2379
+
2380
+ BubbleChart.prototype.__processData = function __processData () {
2381
+ return processSeries(this, "bubble");
2382
+ };
2383
+
2384
+ BubbleChart.prototype.__chartName = function __chartName () {
2385
+ return "BubbleChart";
2386
+ };
2387
+
2388
+ return BubbleChart;
2389
+ }(Chart));
2390
+
2391
+ var Timeline = /*@__PURE__*/(function (Chart) {
2392
+ function Timeline () {
2393
+ Chart.apply(this, arguments);
2394
+ }
2395
+
2396
+ if ( Chart ) Timeline.__proto__ = Chart;
2397
+ Timeline.prototype = Object.create( Chart && Chart.prototype );
2398
+ Timeline.prototype.constructor = Timeline;
2399
+
2400
+ Timeline.prototype.__processData = function __processData () {
2401
+ var i, data = this.rawData;
2402
+ for (i = 0; i < data.length; i++) {
2403
+ data[i][1] = toDate(data[i][1]);
2404
+ data[i][2] = toDate(data[i][2]);
2405
+ }
2406
+ return data;
2407
+ };
2408
+
2409
+ Timeline.prototype.__chartName = function __chartName () {
2410
+ return "Timeline";
2411
+ };
2412
+
2413
+ return Timeline;
2414
+ }(Chart));
533
2415
 
534
2416
  var Chartkick = {
535
- LineChart: function(element, dataSource, opts) {
536
- setElement(element, dataSource, opts, processLineData);
2417
+ LineChart: LineChart,
2418
+ PieChart: PieChart,
2419
+ ColumnChart: ColumnChart,
2420
+ BarChart: BarChart,
2421
+ AreaChart: AreaChart,
2422
+ GeoChart: GeoChart,
2423
+ ScatterChart: ScatterChart,
2424
+ BubbleChart: BubbleChart,
2425
+ Timeline: Timeline,
2426
+ charts: {},
2427
+ configure: function (options) {
2428
+ for (var key in options) {
2429
+ if (options.hasOwnProperty(key)) {
2430
+ config[key] = options[key];
2431
+ }
2432
+ }
2433
+ },
2434
+ setDefaultOptions: function (opts) {
2435
+ Chartkick.options = opts;
537
2436
  },
538
- ColumnChart: function(element, dataSource, opts) {
539
- setElement(element, dataSource, opts, processColumnData);
2437
+ eachChart: function (callback) {
2438
+ for (var chartId in Chartkick.charts) {
2439
+ if (Chartkick.charts.hasOwnProperty(chartId)) {
2440
+ callback(Chartkick.charts[chartId]);
2441
+ }
2442
+ }
540
2443
  },
541
- PieChart: function(element, dataSource, opts) {
542
- setElement(element, dataSource, opts, processPieData);
2444
+ config: config,
2445
+ options: {},
2446
+ adapters: adapters,
2447
+ addAdapter: addAdapter,
2448
+ use: function(adapter) {
2449
+ addAdapter(adapter);
2450
+ return Chartkick;
543
2451
  }
544
2452
  };
545
2453
 
546
- window.Chartkick = Chartkick;
547
- })();
2454
+ // not ideal, but allows for simpler integration
2455
+ if (typeof window !== "undefined" && !window.Chartkick) {
2456
+ window.Chartkick = Chartkick;
2457
+ }
2458
+
2459
+ // backwards compatibility for esm require
2460
+ Chartkick.default = Chartkick;
2461
+
2462
+ return Chartkick;
2463
+
2464
+ })));