chartkick 2.2.5 → 2.3.0

Sign up to get free protection for your applications and to get access to all the features.

Potentially problematic release.


This version of chartkick might be problematic. Click here for more details.

checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 1576b841c5b83307b5ca0560bc85d8251f302d02
4
- data.tar.gz: c054b5fc5d5f103cf0b99b628e43a104373c4fe9
3
+ metadata.gz: 4c58fc2c0725fcf5226fd394c02d7e7a08eed251
4
+ data.tar.gz: 4809227124c7b7fa8bb3e1907859e65d7476746c
5
5
  SHA512:
6
- metadata.gz: 0dca756bfabddcfdb9fa97a032aec3debd92fad504e4ef1ae7f7b22a8f1baaefd26fdf3b430912a7b8d16f9604f1450ee4663f4994bd34c04705d0999a528ed9
7
- data.tar.gz: b5f2e783660f36509f34299a62550a222f6abcf57bd4c79a2ecd2a31298e896bf69c3b1c841d7a17b98c58c6c395191c97b24c7625c73dfb473e224c1ab48c0d
6
+ metadata.gz: a5cfd768e87a01679cb843a18365f226a9e7014ee942606a8cdab144c51b05e315d9be2fab068668aad1a55022ce9d67bad30254dd9e7445da7e0f634a38fb0b
7
+ data.tar.gz: d7430b45f2d189d6e711cb9eefbf5e4edc4939200bcc0ba3a813aed1da0e24f47436a38c8c8fc64c8c86dc958be4a71c65e823c2a61dd8dc2d23dba7d58fd7c1
data/CHANGELOG.md CHANGED
@@ -1,3 +1,8 @@
1
+ ## 2.3.0
2
+
3
+ - Fixed deep merge error for non-Rails apps
4
+ - Updated Chartkick.js to 2.3.0
5
+
1
6
  ## 2.2.5
2
7
 
3
8
  - Updated Chart.js to 2.7.1
data/CONTRIBUTING.md ADDED
@@ -0,0 +1,42 @@
1
+ # Contributing
2
+
3
+ First, thanks for wanting to contribute. You’re awesome! :heart:
4
+
5
+ ## Questions
6
+
7
+ Use [Stack Overflow](https://stackoverflow.com/) with the tag `chartkick`.
8
+
9
+ ## Feature Requests
10
+
11
+ Create an issue. Start the title with `[Idea]`.
12
+
13
+ ## Issues
14
+
15
+ Think you’ve discovered an issue?
16
+
17
+ 1. Search existing issues to see if it’s been reported.
18
+ 2. Try the `master` branch to make sure it hasn’t been fixed.
19
+
20
+ ```rb
21
+ gem "chartkick", github: "ankane/chartkick"
22
+ ```
23
+
24
+ If the above steps don’t help, create an issue.
25
+
26
+ - Include the JavaScript rendered by Chartkick.
27
+ - For exceptions, include the complete backtrace.
28
+
29
+ ## Pull Requests
30
+
31
+ Fork the project and create a pull request. A few tips:
32
+
33
+ - Keep changes to a minimum. If you have multiple features or fixes, submit multiple pull requests.
34
+ - Follow the existing style. The code should read like it’s written by a single person.
35
+
36
+ Feel free to open an issue to get feedback on your idea before spending too much time on it.
37
+
38
+ Also, note that we aren’t currently accepting new chart types.
39
+
40
+ ---
41
+
42
+ This contributing guide is released under [CCO](https://creativecommons.org/publicdomain/zero/1.0/) (public domain). Use it for your own project without attribution.
data/ISSUE_TEMPLATE.md ADDED
@@ -0,0 +1,7 @@
1
+ Hi,
2
+
3
+ Before creating an issue, please check out the Contributing Guide:
4
+
5
+ https://github.com/ankane/chartkick/blob/master/CONTRIBUTING.md
6
+
7
+ Thanks!
data/README.md CHANGED
@@ -170,12 +170,48 @@ Specify legend position
170
170
  <%= line_chart data, legend: "bottom" %>
171
171
  ```
172
172
 
173
+ Defer chart creation until after the page loads
174
+
175
+ ```erb
176
+ <%= line_chart data, defer: true %>
177
+ ```
178
+
173
179
  Donut chart
174
180
 
175
181
  ```erb
176
182
  <%= pie_chart data, donut: true %>
177
183
  ```
178
184
 
185
+ Prefix, useful for currency - *Chart.js, Highcharts*
186
+
187
+ ```erb
188
+ <%= line_chart data, prefix: "$" %>
189
+ ```
190
+
191
+ Suffix, useful for percentages - *Chart.js, Highcharts*
192
+
193
+ ```erb
194
+ <%= line_chart data, suffix: "%" %>
195
+ ```
196
+
197
+ Set a thousands separator - *Chart.js, Highcharts*
198
+
199
+ ```erb
200
+ <%= line_chart data, thousands: "," %>
201
+ ```
202
+
203
+ Set a decimal separator - *Chart.js, Highcharts*
204
+
205
+ ```erb
206
+ <%= line_chart data, decimal: "," %>
207
+ ```
208
+
209
+ Show a message when data is empty
210
+
211
+ ```erb
212
+ <%= line_chart data, messages: {empty: "No data"} %>
213
+ ```
214
+
179
215
  Refresh data from a remote source every `n` seconds
180
216
 
181
217
  ```erb
@@ -314,7 +350,7 @@ Works with Highcharts 2.1+
314
350
 
315
351
  ### Sinatra and Padrino
316
352
 
317
- You must include `chartkick.js` manually. [Download it here](https://raw.github.com/ankane/chartkick/master/app/assets/javascripts/chartkick.js)
353
+ You must include `chartkick.js` manually. [Download it here](https://raw.githubusercontent.com/ankane/chartkick/master/vendor/assets/javascripts/chartkick.js)
318
354
 
319
355
  ```html
320
356
  <script src="chartkick.js"></script>
@@ -335,7 +371,7 @@ after the JavaScript files and before your charts.
335
371
  If more than one charting library is loaded, choose between them with:
336
372
 
337
373
  ```erb
338
- <%= line_chart data, adapter: "google" %> <!-- or highcharts -->
374
+ <%= line_chart data, adapter: "google" %> <!-- or highcharts or chartjs -->
339
375
  ```
340
376
 
341
377
  ## JavaScript API
data/chartkick.gemspec CHANGED
@@ -8,9 +8,8 @@ Gem::Specification.new do |spec|
8
8
  spec.version = Chartkick::VERSION
9
9
  spec.authors = ["Andrew Kane"]
10
10
  spec.email = ["andrew@chartkick.com"]
11
- spec.description = "Create beautiful JavaScript charts with one line of Ruby"
12
11
  spec.summary = "Create beautiful JavaScript charts with one line of Ruby"
13
- spec.homepage = "http://chartkick.com"
12
+ spec.homepage = "https://www.chartkick.com"
14
13
  spec.license = "MIT"
15
14
 
16
15
  spec.files = `git ls-files`.split($INPUT_RECORD_SEPARATOR)
@@ -18,7 +17,7 @@ Gem::Specification.new do |spec|
18
17
  spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
19
18
  spec.require_paths = ["lib"]
20
19
 
21
- spec.add_development_dependency "bundler", "~> 1.3"
20
+ spec.add_development_dependency "bundler"
22
21
  spec.add_development_dependency "rake"
23
22
  spec.add_development_dependency "minitest"
24
23
  end
@@ -87,7 +87,7 @@ JS
87
87
  hash_a = hash_a.dup
88
88
  hash_b.each_pair do |k, v|
89
89
  tv = hash_a[k]
90
- hash_a[k] = tv.is_a?(Hash) && v.is_a?(Hash) ? tv.deep_merge(v) : v
90
+ hash_a[k] = tv.is_a?(Hash) && v.is_a?(Hash) ? chartkick_deep_merge(tv, v) : v
91
91
  end
92
92
  hash_a
93
93
  end
@@ -1,3 +1,3 @@
1
1
  module Chartkick
2
- VERSION = "2.2.5"
2
+ VERSION = "2.3.0"
3
3
  end
@@ -26,4 +26,18 @@ class TestChartkick < Minitest::Test
26
26
  line_chart @data, options
27
27
  assert_equal "boom", options[:id]
28
28
  end
29
+
30
+ def test_chartkick_deep_merge_different_inner_key
31
+ global_option = {library: {backgroundColor: "#eee"}}
32
+ local_option = {library: {title: "test"}}
33
+ correct_merge = {library: {backgroundColor: "#eee", title: "test"}}
34
+ assert_equal chartkick_deep_merge(global_option, local_option), correct_merge
35
+ end
36
+
37
+ def test_chartkick_deep_merge_same_inner_key
38
+ global_option = {library: {backgroundColor: "#eee"}}
39
+ local_option = {library: {backgroundColor: "#fff"}}
40
+ correct_merge = {library: {backgroundColor: "#fff"}}
41
+ assert_equal chartkick_deep_merge(global_option, local_option), correct_merge
42
+ end
29
43
  end
@@ -2,7 +2,7 @@
2
2
  * Chartkick.js
3
3
  * Create beautiful charts with one line of JavaScript
4
4
  * https://github.com/ankane/chartkick.js
5
- * v2.2.4
5
+ * v2.3.0
6
6
  * MIT License
7
7
  */
8
8
 
@@ -174,7 +174,7 @@
174
174
 
175
175
  function runNext() {
176
176
  if (runningRequests < maxRequests) {
177
- var request = pendingRequests.shift()
177
+ var request = pendingRequests.shift();
178
178
  if (request) {
179
179
  runningRequests++;
180
180
  getJSON(request[0], request[1], request[2]);
@@ -319,16 +319,19 @@
319
319
  if (typeof n !== "object") {
320
320
  if (typeof n === "number") {
321
321
  n = new Date(n * 1000); // ms
322
- } else if ((matches = n.match(DATE_PATTERN))) {
322
+ } else {
323
+ n = toStr(n);
324
+ if ((matches = n.match(DATE_PATTERN))) {
323
325
  year = parseInt(matches[1], 10);
324
326
  month = parseInt(matches[3], 10) - 1;
325
327
  day = parseInt(matches[5], 10);
326
328
  return new Date(year, month, day);
327
- } else { // str
328
- // try our best to get the str into iso8601
329
- // TODO be smarter about this
330
- var str = n.replace(/ /, "T").replace(" ", "").replace("UTC", "Z");
331
- n = parseISO8601(str) || new Date(n);
329
+ } else { // str
330
+ // try our best to get the str into iso8601
331
+ // TODO be smarter about this
332
+ var str = n.replace(/ /, "T").replace(" ", "").replace("UTC", "Z");
333
+ n = parseISO8601(str) || new Date(n);
334
+ }
332
335
  }
333
336
  }
334
337
  return n;
@@ -360,1113 +363,1238 @@
360
363
  }
361
364
 
362
365
  function loadAdapters() {
363
- if (!HighchartsAdapter && "Highcharts" in window) {
364
- HighchartsAdapter = new function () {
365
- var Highcharts = window.Highcharts;
366
+ if (!ChartjsAdapter && "Chart" in window) {
367
+ ChartjsAdapter = (function () {
368
+ var Chart = window.Chart;
366
369
 
367
- this.name = "highcharts";
370
+ var baseOptions = {
371
+ maintainAspectRatio: false,
372
+ animation: false,
373
+ tooltips: {
374
+ displayColors: false,
375
+ callbacks: {}
376
+ },
377
+ legend: {},
378
+ title: {fontSize: 20, fontColor: "#333"}
379
+ };
368
380
 
369
381
  var defaultOptions = {
370
- chart: {},
371
- xAxis: {
372
- title: {
373
- text: null
374
- },
375
- labels: {
376
- style: {
377
- fontSize: "12px"
382
+ scales: {
383
+ yAxes: [
384
+ {
385
+ ticks: {
386
+ maxTicksLimit: 4
387
+ },
388
+ scaleLabel: {
389
+ fontSize: 16,
390
+ // fontStyle: "bold",
391
+ fontColor: "#333"
392
+ }
378
393
  }
379
- }
380
- },
381
- yAxis: {
382
- title: {
383
- text: null
384
- },
385
- labels: {
386
- style: {
387
- fontSize: "12px"
394
+ ],
395
+ xAxes: [
396
+ {
397
+ gridLines: {
398
+ drawOnChartArea: false
399
+ },
400
+ scaleLabel: {
401
+ fontSize: 16,
402
+ // fontStyle: "bold",
403
+ fontColor: "#333"
404
+ },
405
+ time: {},
406
+ ticks: {}
388
407
  }
389
- }
390
- },
391
- title: {
392
- text: null
393
- },
394
- credits: {
395
- enabled: false
396
- },
397
- legend: {
398
- borderWidth: 0
399
- },
400
- tooltip: {
401
- style: {
402
- fontSize: "12px"
403
- }
404
- },
405
- plotOptions: {
406
- areaspline: {},
407
- series: {
408
- marker: {}
409
- }
408
+ ]
410
409
  }
411
410
  };
412
411
 
412
+ // http://there4.io/2012/05/02/google-chart-color-list/
413
+ var defaultColors = [
414
+ "#3366CC", "#DC3912", "#FF9900", "#109618", "#990099", "#3B3EAC", "#0099C6",
415
+ "#DD4477", "#66AA00", "#B82E2E", "#316395", "#994499", "#22AA99", "#AAAA11",
416
+ "#6633CC", "#E67300", "#8B0707", "#329262", "#5574A6", "#651067"
417
+ ];
418
+
413
419
  var hideLegend = function (options, legend, hideLegend) {
414
420
  if (legend !== undefined) {
415
- options.legend.enabled = !!legend;
421
+ options.legend.display = !!legend;
416
422
  if (legend && legend !== true) {
417
- if (legend === "top" || legend === "bottom") {
418
- options.legend.verticalAlign = legend;
419
- } else {
420
- options.legend.layout = "vertical";
421
- options.legend.verticalAlign = "middle";
422
- options.legend.align = legend;
423
- }
423
+ options.legend.position = legend;
424
424
  }
425
425
  } else if (hideLegend) {
426
- options.legend.enabled = false;
426
+ options.legend.display = false;
427
427
  }
428
428
  };
429
429
 
430
430
  var setTitle = function (options, title) {
431
+ options.title.display = true;
431
432
  options.title.text = title;
432
433
  };
433
434
 
434
435
  var setMin = function (options, min) {
435
- options.yAxis.min = min;
436
+ if (min !== null) {
437
+ options.scales.yAxes[0].ticks.min = toFloat(min);
438
+ }
436
439
  };
437
440
 
438
441
  var setMax = function (options, max) {
439
- options.yAxis.max = max;
442
+ options.scales.yAxes[0].ticks.max = toFloat(max);
443
+ };
444
+
445
+ var setBarMin = function (options, min) {
446
+ if (min !== null) {
447
+ options.scales.xAxes[0].ticks.min = toFloat(min);
448
+ }
449
+ };
450
+
451
+ var setBarMax = function (options, max) {
452
+ options.scales.xAxes[0].ticks.max = toFloat(max);
440
453
  };
441
454
 
442
455
  var setStacked = function (options, stacked) {
443
- options.plotOptions.series.stacking = stacked ? "normal" : null;
456
+ options.scales.xAxes[0].stacked = !!stacked;
457
+ options.scales.yAxes[0].stacked = !!stacked;
444
458
  };
445
459
 
446
460
  var setXtitle = function (options, title) {
447
- options.xAxis.title.text = title;
461
+ options.scales.xAxes[0].scaleLabel.display = true;
462
+ options.scales.xAxes[0].scaleLabel.labelString = title;
448
463
  };
449
464
 
450
465
  var setYtitle = function (options, title) {
451
- options.yAxis.title.text = title;
466
+ options.scales.yAxes[0].scaleLabel.display = true;
467
+ options.scales.yAxes[0].scaleLabel.labelString = title;
452
468
  };
453
469
 
454
- var jsOptions = jsOptionsFunc(defaultOptions, hideLegend, setTitle, setMin, setMax, setStacked, setXtitle, setYtitle);
455
-
456
- this.renderLineChart = function (chart, chartType) {
457
- chartType = chartType || "spline";
458
- var chartOptions = {};
459
- if (chartType === "areaspline") {
460
- chartOptions = {
461
- plotOptions: {
462
- areaspline: {
463
- stacking: "normal"
464
- },
465
- area: {
466
- stacking: "normal"
467
- },
468
- series: {
469
- marker: {
470
- enabled: false
471
- }
472
- }
473
- }
474
- };
470
+ var drawChart = function(chart, type, data, options) {
471
+ if (chart.chart) {
472
+ chart.chart.destroy();
473
+ } else {
474
+ chart.element.innerHTML = "<canvas></canvas>";
475
475
  }
476
476
 
477
- if (chart.options.curve === false) {
478
- if (chartType === "areaspline") {
479
- chartType = "area";
480
- } else if (chartType === "spline") {
481
- chartType = "line";
482
- }
483
- }
477
+ var ctx = chart.element.getElementsByTagName("CANVAS")[0];
478
+ chart.chart = new Chart(ctx, {
479
+ type: type,
480
+ data: data,
481
+ options: options
482
+ });
483
+ };
484
484
 
485
- var options = jsOptions(chart, chart.options, chartOptions), data, i, j;
486
- options.xAxis.type = chart.discrete ? "category" : "datetime";
487
- if (!options.chart.type) {
488
- options.chart.type = chartType;
485
+ // http://stackoverflow.com/questions/5623838/rgb-to-hex-and-hex-to-rgb
486
+ var addOpacity = function(hex, opacity) {
487
+ var result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex);
488
+ return result ? "rgba(" + parseInt(result[1], 16) + ", " + parseInt(result[2], 16) + ", " + parseInt(result[3], 16) + ", " + opacity + ")" : hex;
489
+ };
490
+
491
+ var setLabelSize = function (chart, data, options) {
492
+ var maxLabelSize = Math.ceil(chart.element.offsetWidth / 4.0 / data.labels.length);
493
+ if (maxLabelSize > 25) {
494
+ maxLabelSize = 25;
489
495
  }
490
- options.chart.renderTo = chart.element.id;
496
+ options.scales.xAxes[0].ticks.callback = function (value) {
497
+ value = toStr(value);
498
+ if (value.length > maxLabelSize) {
499
+ return value.substring(0, maxLabelSize - 2) + "...";
500
+ } else {
501
+ return value;
502
+ }
503
+ };
504
+ };
491
505
 
492
- var series = chart.data;
493
- for (i = 0; i < series.length; i++) {
494
- data = series[i].data;
495
- if (!chart.discrete) {
496
- for (j = 0; j < data.length; j++) {
497
- data[j][0] = data[j][0].getTime();
506
+ var setFormatOptions = function(chart, options, chartType) {
507
+ var formatOptions = {
508
+ prefix: chart.options.prefix,
509
+ suffix: chart.options.suffix,
510
+ thousands: chart.options.thousands,
511
+ decimal: chart.options.decimal
512
+ };
513
+
514
+ if (formatOptions.prefix || formatOptions.suffix || formatOptions.thousands || formatOptions.decimal) {
515
+ if (chartType !== "pie") {
516
+ var myAxes = options.scales.yAxes;
517
+ if (chartType === "bar") {
518
+ myAxes = options.scales.xAxes;
519
+ }
520
+
521
+ if (!myAxes[0].ticks.callback) {
522
+ myAxes[0].ticks.callback = function (value, index, values) {
523
+ return formatValue("", value, formatOptions);
524
+ };
498
525
  }
499
526
  }
500
- series[i].marker = {symbol: "circle"};
501
- if (chart.options.points === false) {
502
- series[i].marker.enabled = false;
527
+
528
+ if (!options.tooltips.callbacks.label) {
529
+ if (chartType !== "pie") {
530
+ var valueLabel = chartType === "bar" ? "xLabel" : "yLabel";
531
+ options.tooltips.callbacks.label = function (tooltipItem, data) {
532
+ var label = data.datasets[tooltipItem.datasetIndex].label || '';
533
+ if (label) {
534
+ label += ': ';
535
+ }
536
+ return formatValue(label, tooltipItem[valueLabel], formatOptions);
537
+ };
538
+ } else {
539
+ // need to use separate label for pie charts
540
+ options.tooltips.callbacks.label = function (tooltipItem, data) {
541
+ var dataLabel = data.labels[tooltipItem.index];
542
+ var value = ': ';
543
+
544
+ if (isArray(dataLabel)) {
545
+ // show value on first line of multiline label
546
+ // need to clone because we are changing the value
547
+ dataLabel = dataLabel.slice();
548
+ dataLabel[0] += value;
549
+ } else {
550
+ dataLabel += value;
551
+ }
552
+
553
+ return formatValue(dataLabel, data.datasets[tooltipItem.datasetIndex].data[tooltipItem.index], formatOptions);
554
+ };
555
+ }
503
556
  }
504
557
  }
505
- options.series = series;
506
- chart.chart = new Highcharts.Chart(options);
507
- };
508
-
509
- this.renderScatterChart = function (chart) {
510
- var chartOptions = {};
511
- var options = jsOptions(chart, chart.options, chartOptions);
512
- options.chart.type = "scatter";
513
- options.chart.renderTo = chart.element.id;
514
- options.series = chart.data;
515
- chart.chart = new Highcharts.Chart(options);
516
558
  };
517
559
 
518
- this.renderPieChart = function (chart) {
519
- var chartOptions = merge(defaultOptions, {});
520
-
521
- if (chart.options.colors) {
522
- chartOptions.colors = chart.options.colors;
523
- }
524
- if (chart.options.donut) {
525
- chartOptions.plotOptions = {pie: {innerSize: "50%"}};
526
- }
560
+ var jsOptions = jsOptionsFunc(merge(baseOptions, defaultOptions), hideLegend, setTitle, setMin, setMax, setStacked, setXtitle, setYtitle);
527
561
 
528
- if ("legend" in chart.options) {
529
- hideLegend(chartOptions, chart.options.legend);
530
- }
562
+ var createDataTable = function (chart, options, chartType) {
563
+ var datasets = [];
564
+ var labels = [];
531
565
 
532
- if (chart.options.title) {
533
- setTitle(chartOptions, chart.options.title);
534
- }
566
+ var colors = chart.options.colors || defaultColors;
535
567
 
536
- var options = merge(chartOptions, chart.options.library || {});
537
- options.chart.renderTo = chart.element.id;
538
- options.series = [{
539
- type: "pie",
540
- name: chart.options.label || "Value",
541
- data: chart.data
542
- }];
543
- chart.chart = new Highcharts.Chart(options);
544
- };
568
+ var day = true;
569
+ var week = true;
570
+ var dayOfWeek;
571
+ var month = true;
572
+ var year = true;
573
+ var hour = true;
574
+ var minute = true;
575
+ var detectType = (chartType === "line" || chartType === "area") && !chart.discrete;
545
576
 
546
- this.renderColumnChart = function (chart, chartType) {
547
- chartType = chartType || "column";
548
577
  var series = chart.data;
549
- var options = jsOptions(chart, chart.options), i, j, s, d, rows = [], categories = [];
550
- options.chart.type = chartType;
551
- options.chart.renderTo = chart.element.id;
552
578
 
579
+ var sortedLabels = [];
580
+
581
+ var i, j, s, d, key, rows = [];
553
582
  for (i = 0; i < series.length; i++) {
554
583
  s = series[i];
555
584
 
556
585
  for (j = 0; j < s.data.length; j++) {
557
586
  d = s.data[j];
558
- if (!rows[d[0]]) {
559
- rows[d[0]] = new Array(series.length);
560
- categories.push(d[0]);
587
+ key = detectType ? d[0].getTime() : d[0];
588
+ if (!rows[key]) {
589
+ rows[key] = new Array(series.length);
590
+ }
591
+ rows[key][i] = toFloat(d[1]);
592
+ if (sortedLabels.indexOf(key) === -1) {
593
+ sortedLabels.push(key);
561
594
  }
562
- rows[d[0]][i] = d[1];
563
595
  }
564
596
  }
565
597
 
566
- if (chart.options.xtype === "number") {
567
- categories.sort(sortByNumber);
598
+ if (detectType || chart.options.xtype === "number") {
599
+ sortedLabels.sort(sortByNumber);
568
600
  }
569
601
 
570
- options.xAxis.categories = categories;
571
-
572
- var newSeries = [], d2;
573
- for (i = 0; i < series.length; i++) {
574
- d = [];
575
- for (j = 0; j < categories.length; j++) {
576
- d.push(rows[categories[j]][i] || 0);
577
- }
602
+ var rows2 = [];
603
+ for (j = 0; j < series.length; j++) {
604
+ rows2.push([]);
605
+ }
578
606
 
579
- d2 = {
580
- name: series[i].name,
581
- data: d
607
+ var value;
608
+ var k;
609
+ for (k = 0; k < sortedLabels.length; k++) {
610
+ i = sortedLabels[k];
611
+ if (detectType) {
612
+ value = new Date(toFloat(i));
613
+ // TODO make this efficient
614
+ day = day && isDay(value);
615
+ if (!dayOfWeek) {
616
+ dayOfWeek = value.getDay();
617
+ }
618
+ week = week && isWeek(value, dayOfWeek);
619
+ month = month && isMonth(value);
620
+ year = year && isYear(value);
621
+ hour = hour && isHour(value);
622
+ minute = minute && isMinute(value);
623
+ } else {
624
+ value = i;
582
625
  }
583
- if (series[i].stack) {
584
- d2.stack = series[i].stack;
626
+ labels.push(value);
627
+ for (j = 0; j < series.length; j++) {
628
+ // Chart.js doesn't like undefined
629
+ rows2[j].push(rows[i][j] === undefined ? null : rows[i][j]);
585
630
  }
586
-
587
- newSeries.push(d2);
588
631
  }
589
- options.series = newSeries;
590
-
591
- chart.chart = new Highcharts.Chart(options);
592
- };
593
632
 
594
- var self = this;
633
+ for (i = 0; i < series.length; i++) {
634
+ s = series[i];
595
635
 
596
- this.renderBarChart = function (chart) {
597
- self.renderColumnChart(chart, "bar");
598
- };
636
+ var color = s.color || colors[i];
637
+ var backgroundColor = chartType !== "line" ? addOpacity(color, 0.5) : color;
599
638
 
600
- this.renderAreaChart = function (chart) {
601
- self.renderLineChart(chart, "areaspline");
602
- };
603
- };
604
- adapters.push(HighchartsAdapter);
605
- }
606
- if (!GoogleChartsAdapter && window.google && (window.google.setOnLoadCallback || window.google.charts)) {
607
- GoogleChartsAdapter = new function () {
608
- var google = window.google;
639
+ var dataset = {
640
+ label: s.name,
641
+ data: rows2[i],
642
+ fill: chartType === "area",
643
+ borderColor: color,
644
+ backgroundColor: backgroundColor,
645
+ pointBackgroundColor: color,
646
+ borderWidth: 2
647
+ };
609
648
 
610
- this.name = "google";
649
+ if (s.stack) {
650
+ dataset.stack = s.stack;
651
+ }
611
652
 
612
- var loaded = {};
613
- var callbacks = [];
653
+ if (chart.options.curve === false) {
654
+ dataset.lineTension = 0;
655
+ }
614
656
 
615
- var runCallbacks = function () {
616
- var cb, call;
617
- for (var i = 0; i < callbacks.length; i++) {
618
- cb = callbacks[i];
619
- call = google.visualization && ((cb.pack === "corechart" && google.visualization.LineChart) || (cb.pack === "timeline" && google.visualization.Timeline));
620
- if (call) {
621
- cb.callback();
622
- callbacks.splice(i, 1);
623
- i--;
657
+ if (chart.options.points === false) {
658
+ dataset.pointRadius = 0;
659
+ dataset.pointHitRadius = 5;
624
660
  }
625
- }
626
- };
627
661
 
628
- var waitForLoaded = function (pack, callback) {
629
- if (!callback) {
630
- callback = pack;
631
- pack = "corechart";
662
+ datasets.push(merge(dataset, s.library || {}));
632
663
  }
633
664
 
634
- callbacks.push({pack: pack, callback: callback});
665
+ if (detectType && labels.length > 0) {
666
+ var minTime = labels[0].getTime();
667
+ var maxTime = labels[0].getTime();
668
+ for (i = 1; i < labels.length; i++) {
669
+ value = labels[i].getTime();
670
+ if (value < minTime) {
671
+ minTime = value;
672
+ }
673
+ if (value > maxTime) {
674
+ maxTime = value;
675
+ }
676
+ }
635
677
 
636
- if (loaded[pack]) {
637
- runCallbacks();
638
- } else {
639
- loaded[pack] = true;
678
+ var timeDiff = (maxTime - minTime) / (86400 * 1000.0);
640
679
 
641
- // https://groups.google.com/forum/#!topic/google-visualization-api/fMKJcyA2yyI
642
- var loadOptions = {
643
- packages: [pack],
644
- callback: runCallbacks
645
- };
646
- if (config.language) {
647
- loadOptions.language = config.language;
648
- }
649
- if (pack === "corechart" && config.mapsApiKey) {
650
- loadOptions.mapsApiKey = config.mapsApiKey;
680
+ if (!options.scales.xAxes[0].time.unit) {
681
+ var step;
682
+ if (year || timeDiff > 365 * 10) {
683
+ options.scales.xAxes[0].time.unit = "year";
684
+ step = 365;
685
+ } else if (month || timeDiff > 30 * 10) {
686
+ options.scales.xAxes[0].time.unit = "month";
687
+ step = 30;
688
+ } else if (day || timeDiff > 10) {
689
+ options.scales.xAxes[0].time.unit = "day";
690
+ step = 1;
691
+ } else if (hour || timeDiff > 0.5) {
692
+ options.scales.xAxes[0].time.displayFormats = {hour: "MMM D, h a"};
693
+ options.scales.xAxes[0].time.unit = "hour";
694
+ step = 1 / 24.0;
695
+ } else if (minute) {
696
+ options.scales.xAxes[0].time.displayFormats = {minute: "h:mm a"};
697
+ options.scales.xAxes[0].time.unit = "minute";
698
+ step = 1 / 24.0 / 60.0;
699
+ }
700
+
701
+ if (step && timeDiff > 0) {
702
+ var unitStepSize = Math.ceil(timeDiff / step / (chart.element.offsetWidth / 100.0));
703
+ if (week && step === 1) {
704
+ unitStepSize = Math.ceil(unitStepSize / 7.0) * 7;
705
+ }
706
+ options.scales.xAxes[0].time.unitStepSize = unitStepSize;
707
+ }
651
708
  }
652
709
 
653
- if (window.google.setOnLoadCallback) {
654
- google.load("visualization", "1", loadOptions);
655
- } else {
656
- google.charts.load("current", loadOptions);
710
+ if (!options.scales.xAxes[0].time.tooltipFormat) {
711
+ if (day) {
712
+ options.scales.xAxes[0].time.tooltipFormat = "ll";
713
+ } else if (hour) {
714
+ options.scales.xAxes[0].time.tooltipFormat = "MMM D, h a";
715
+ } else if (minute) {
716
+ options.scales.xAxes[0].time.tooltipFormat = "h:mm a";
717
+ }
657
718
  }
658
719
  }
720
+
721
+ var data = {
722
+ labels: labels,
723
+ datasets: datasets
724
+ };
725
+
726
+ return data;
659
727
  };
660
728
 
661
- // Set chart options
662
- var defaultOptions = {
663
- chartArea: {},
664
- fontName: "'Lucida Grande', 'Lucida Sans Unicode', Verdana, Arial, Helvetica, sans-serif",
665
- pointSize: 6,
666
- legend: {
667
- textStyle: {
668
- fontSize: 12,
669
- color: "#444"
670
- },
671
- alignment: "center",
672
- position: "right"
673
- },
674
- curveType: "function",
675
- hAxis: {
676
- textStyle: {
677
- color: "#666",
678
- fontSize: 12
679
- },
680
- titleTextStyle: {},
681
- gridlines: {
682
- color: "transparent"
683
- },
684
- baselineColor: "#ccc",
685
- viewWindow: {}
686
- },
687
- vAxis: {
688
- textStyle: {
689
- color: "#666",
690
- fontSize: 12
691
- },
692
- titleTextStyle: {},
693
- baselineColor: "#ccc",
694
- viewWindow: {}
695
- },
696
- tooltip: {
697
- textStyle: {
698
- color: "#666",
699
- fontSize: 12
700
- }
729
+ var renderLineChart = function (chart, chartType) {
730
+ if (chart.options.xtype === "number") {
731
+ return renderScatterChart(chart, chartType, true);
701
732
  }
702
- };
703
733
 
704
- var hideLegend = function (options, legend, hideLegend) {
705
- if (legend !== undefined) {
706
- var position;
707
- if (!legend) {
708
- position = "none";
709
- } else if (legend === true) {
710
- position = "right";
711
- } else {
712
- position = legend;
713
- }
714
- options.legend.position = position;
715
- } else if (hideLegend) {
716
- options.legend.position = "none";
734
+ var chartOptions = {};
735
+ if (chartType === "area") {
736
+ // TODO fix area stacked
737
+ // chartOptions.stacked = true;
738
+ }
739
+ // fix for https://github.com/chartjs/Chart.js/issues/2441
740
+ if (!chart.options.max && allZeros(chart.data)) {
741
+ chartOptions.max = 1;
717
742
  }
718
- };
719
743
 
720
- var setTitle = function (options, title) {
721
- options.title = title;
722
- options.titleTextStyle = {color: "#333", fontSize: "20px"};
723
- };
744
+ var options = jsOptions(chart, merge(chartOptions, chart.options));
745
+ setFormatOptions(chart, options, chartType);
724
746
 
725
- var setMin = function (options, min) {
726
- options.vAxis.viewWindow.min = min;
727
- };
747
+ var data = createDataTable(chart, options, chartType || "line");
728
748
 
729
- var setMax = function (options, max) {
730
- options.vAxis.viewWindow.max = max;
749
+ options.scales.xAxes[0].type = chart.discrete ? "category" : "time";
750
+
751
+ drawChart(chart, "line", data, options);
731
752
  };
732
753
 
733
- var setBarMin = function (options, min) {
734
- options.hAxis.viewWindow.min = min;
735
- };
754
+ var renderPieChart = function (chart) {
755
+ var options = merge({}, baseOptions);
756
+ if (chart.options.donut) {
757
+ options.cutoutPercentage = 50;
758
+ }
736
759
 
737
- var setBarMax = function (options, max) {
738
- options.hAxis.viewWindow.max = max;
739
- };
760
+ if ("legend" in chart.options) {
761
+ hideLegend(options, chart.options.legend);
762
+ }
740
763
 
741
- var setStacked = function (options, stacked) {
742
- options.isStacked = !!stacked;
764
+ if (chart.options.title) {
765
+ setTitle(options, chart.options.title);
766
+ }
767
+
768
+ options = merge(options, chart.options.library || {});
769
+ setFormatOptions(chart, options, "pie");
770
+
771
+ var labels = [];
772
+ var values = [];
773
+ for (var i = 0; i < chart.data.length; i++) {
774
+ var point = chart.data[i];
775
+ labels.push(point[0]);
776
+ values.push(point[1]);
777
+ }
778
+
779
+ var data = {
780
+ labels: labels,
781
+ datasets: [
782
+ {
783
+ data: values,
784
+ backgroundColor: chart.options.colors || defaultColors
785
+ }
786
+ ]
787
+ };
788
+
789
+ drawChart(chart, "pie", data, options);
743
790
  };
744
791
 
745
- var setXtitle = function (options, title) {
746
- options.hAxis.title = title;
747
- options.hAxis.titleTextStyle.italic = false;
792
+ var renderColumnChart = function (chart, chartType) {
793
+ var options;
794
+ if (chartType === "bar") {
795
+ options = jsOptionsFunc(merge(baseOptions, defaultOptions), hideLegend, setTitle, setBarMin, setBarMax, setStacked, setXtitle, setYtitle)(chart, chart.options);
796
+ } else {
797
+ options = jsOptions(chart, chart.options);
798
+ }
799
+ setFormatOptions(chart, options, chartType);
800
+ var data = createDataTable(chart, options, "column");
801
+ if (chartType !== "bar") {
802
+ setLabelSize(chart, data, options);
803
+ }
804
+ drawChart(chart, (chartType === "bar" ? "horizontalBar" : "bar"), data, options);
748
805
  };
749
806
 
750
- var setYtitle = function (options, title) {
751
- options.vAxis.title = title;
752
- options.vAxis.titleTextStyle.italic = false;
807
+ var renderAreaChart = function (chart) {
808
+ renderLineChart(chart, "area");
753
809
  };
754
810
 
755
- var jsOptions = jsOptionsFunc(defaultOptions, hideLegend, setTitle, setMin, setMax, setStacked, setXtitle, setYtitle);
811
+ var renderBarChart = function (chart) {
812
+ renderColumnChart(chart, "bar");
813
+ };
756
814
 
757
- // cant use object as key
758
- var createDataTable = function (series, columnType, xtype) {
759
- var i, j, s, d, key, rows = [], sortedLabels = [];
760
- for (i = 0; i < series.length; i++) {
761
- s = series[i];
815
+ var renderScatterChart = function (chart, chartType, lineChart) {
816
+ chartType = chartType || "line";
762
817
 
763
- for (j = 0; j < s.data.length; j++) {
764
- d = s.data[j];
765
- key = (columnType === "datetime") ? d[0].getTime() : d[0];
766
- if (!rows[key]) {
767
- rows[key] = new Array(series.length);
768
- sortedLabels.push(key);
769
- }
770
- rows[key][i] = toFloat(d[1]);
771
- }
818
+ var options = jsOptions(chart, chart.options);
819
+ if (!lineChart) {
820
+ setFormatOptions(chart, options, chartType);
772
821
  }
773
822
 
774
- var rows2 = [];
775
- var day = true;
776
- var value;
777
- for (var j = 0; j < sortedLabels.length; j++) {
778
- var i = sortedLabels[j];
779
- if (columnType === "datetime") {
780
- value = new Date(toFloat(i));
781
- day = day && isDay(value);
782
- } else if (columnType === "number") {
783
- value = toFloat(i);
784
- } else {
785
- value = i;
823
+ var colors = chart.options.colors || defaultColors;
824
+
825
+ var datasets = [];
826
+ var series = chart.data;
827
+ for (var i = 0; i < series.length; i++) {
828
+ var s = series[i];
829
+ var d = [];
830
+ for (var j = 0; j < s.data.length; j++) {
831
+ var point = {
832
+ x: toFloat(s.data[j][0]),
833
+ y: toFloat(s.data[j][1])
834
+ };
835
+ if (chartType === "bubble") {
836
+ point.r = toFloat(s.data[j][2]);
837
+ }
838
+ d.push(point);
786
839
  }
787
- rows2.push([value].concat(rows[i]));
788
- }
789
- if (columnType === "datetime") {
790
- rows2.sort(sortByTime);
791
- } else if (columnType === "number") {
792
- rows2.sort(sortByNumberSeries);
793
- }
794
840
 
795
- if (xtype === "number") {
796
- rows2.sort(sortByNumberSeries);
841
+ var color = s.color || colors[i];
842
+ var backgroundColor = chartType === "area" ? addOpacity(color, 0.5) : color;
797
843
 
798
- for (var i = 0; i < rows2.length; i++) {
799
- rows2[i][0] = toStr(rows2[i][0]);
800
- }
844
+ datasets.push({
845
+ label: s.name,
846
+ showLine: lineChart || false,
847
+ data: d,
848
+ borderColor: color,
849
+ backgroundColor: backgroundColor,
850
+ pointBackgroundColor: color,
851
+ fill: chartType === "area"
852
+ });
801
853
  }
802
854
 
803
- // create datatable
804
- var data = new google.visualization.DataTable();
805
- columnType = columnType === "datetime" && day ? "date" : columnType;
806
- data.addColumn(columnType, "");
807
- for (i = 0; i < series.length; i++) {
808
- data.addColumn("number", series[i].name);
855
+ if (chartType === "area") {
856
+ chartType = "line";
809
857
  }
810
- data.addRows(rows2);
811
858
 
812
- return data;
859
+ var data = {datasets: datasets};
860
+
861
+ options.scales.xAxes[0].type = "linear";
862
+ options.scales.xAxes[0].position = "bottom";
863
+
864
+ drawChart(chart, chartType, data, options);
813
865
  };
814
866
 
815
- var resize = function (callback) {
816
- if (window.attachEvent) {
817
- window.attachEvent("onresize", callback);
818
- } else if (window.addEventListener) {
819
- window.addEventListener("resize", callback, true);
820
- }
821
- callback();
867
+ var renderBubbleChart = function (chart) {
868
+ renderScatterChart(chart, "bubble");
822
869
  };
823
870
 
824
- this.renderLineChart = function (chart) {
825
- waitForLoaded(function () {
826
- var chartOptions = {};
871
+ return {
872
+ name: "chartjs",
873
+ renderLineChart: renderLineChart,
874
+ renderPieChart: renderPieChart,
875
+ renderColumnChart: renderColumnChart,
876
+ renderBarChart: renderBarChart,
877
+ renderAreaChart: renderAreaChart,
878
+ renderScatterChart: renderScatterChart,
879
+ renderBubbleChart: renderBubbleChart
880
+ };
881
+ })();
827
882
 
828
- if (chart.options.curve === false) {
829
- chartOptions.curveType = "none";
830
- }
883
+ adapters.push(ChartjsAdapter);
884
+ }
831
885
 
832
- if (chart.options.points === false) {
833
- chartOptions.pointSize = 0;
834
- }
886
+ if (!HighchartsAdapter && "Highcharts" in window) {
887
+ HighchartsAdapter = (function () {
888
+ var Highcharts = window.Highcharts;
835
889
 
836
- var options = jsOptions(chart, chart.options, chartOptions);
837
- var columnType = chart.discrete ? "string" : "datetime";
838
- if (chart.options.xtype === "number") {
839
- columnType = "number";
890
+ var defaultOptions = {
891
+ chart: {},
892
+ xAxis: {
893
+ title: {
894
+ text: null
895
+ },
896
+ labels: {
897
+ style: {
898
+ fontSize: "12px"
899
+ }
840
900
  }
841
- var data = createDataTable(chart.data, columnType);
842
- chart.chart = new google.visualization.LineChart(chart.element);
843
- resize(function () {
844
- chart.chart.draw(data, options);
845
- });
846
- });
847
- };
848
-
849
- this.renderPieChart = function (chart) {
850
- waitForLoaded(function () {
851
- var chartOptions = {
852
- chartArea: {
853
- top: "10%",
854
- height: "80%"
855
- },
856
- legend: {}
857
- };
858
- if (chart.options.colors) {
859
- chartOptions.colors = chart.options.colors;
901
+ },
902
+ yAxis: {
903
+ title: {
904
+ text: null
905
+ },
906
+ labels: {
907
+ style: {
908
+ fontSize: "12px"
909
+ }
860
910
  }
861
- if (chart.options.donut) {
862
- chartOptions.pieHole = 0.5;
911
+ },
912
+ title: {
913
+ text: null
914
+ },
915
+ credits: {
916
+ enabled: false
917
+ },
918
+ legend: {
919
+ borderWidth: 0
920
+ },
921
+ tooltip: {
922
+ style: {
923
+ fontSize: "12px"
863
924
  }
864
- if ("legend" in chart.options) {
865
- hideLegend(chartOptions, chart.options.legend);
925
+ },
926
+ plotOptions: {
927
+ areaspline: {},
928
+ series: {
929
+ marker: {}
866
930
  }
867
- if (chart.options.title) {
868
- setTitle(chartOptions, chart.options.title);
931
+ }
932
+ };
933
+
934
+ var hideLegend = function (options, legend, hideLegend) {
935
+ if (legend !== undefined) {
936
+ options.legend.enabled = !!legend;
937
+ if (legend && legend !== true) {
938
+ if (legend === "top" || legend === "bottom") {
939
+ options.legend.verticalAlign = legend;
940
+ } else {
941
+ options.legend.layout = "vertical";
942
+ options.legend.verticalAlign = "middle";
943
+ options.legend.align = legend;
944
+ }
869
945
  }
870
- var options = merge(merge(defaultOptions, chartOptions), chart.options.library || {});
946
+ } else if (hideLegend) {
947
+ options.legend.enabled = false;
948
+ }
949
+ };
871
950
 
872
- var data = new google.visualization.DataTable();
873
- data.addColumn("string", "");
874
- data.addColumn("number", "Value");
875
- data.addRows(chart.data);
951
+ var setTitle = function (options, title) {
952
+ options.title.text = title;
953
+ };
876
954
 
877
- chart.chart = new google.visualization.PieChart(chart.element);
878
- resize(function () {
879
- chart.chart.draw(data, options);
880
- });
881
- });
955
+ var setMin = function (options, min) {
956
+ options.yAxis.min = min;
882
957
  };
883
958
 
884
- this.renderColumnChart = function (chart) {
885
- waitForLoaded(function () {
886
- var options = jsOptions(chart, chart.options);
887
- var data = createDataTable(chart.data, "string", chart.options.xtype);
888
- chart.chart = new google.visualization.ColumnChart(chart.element);
889
- resize(function () {
890
- chart.chart.draw(data, options);
891
- });
892
- });
959
+ var setMax = function (options, max) {
960
+ options.yAxis.max = max;
893
961
  };
894
962
 
895
- this.renderBarChart = function (chart) {
896
- waitForLoaded(function () {
897
- var chartOptions = {
898
- hAxis: {
899
- gridlines: {
900
- color: "#ccc"
901
- }
902
- }
903
- };
904
- var options = jsOptionsFunc(defaultOptions, hideLegend, setTitle, setBarMin, setBarMax, setStacked, setXtitle, setYtitle)(chart, chart.options, chartOptions);
905
- var data = createDataTable(chart.data, "string", chart.options.xtype);
906
- chart.chart = new google.visualization.BarChart(chart.element);
907
- resize(function () {
908
- chart.chart.draw(data, options);
909
- });
910
- });
963
+ var setStacked = function (options, stacked) {
964
+ options.plotOptions.series.stacking = stacked ? "normal" : null;
911
965
  };
912
966
 
913
- this.renderAreaChart = function (chart) {
914
- waitForLoaded(function () {
915
- var chartOptions = {
916
- isStacked: true,
917
- pointSize: 0,
918
- areaOpacity: 0.5
919
- };
967
+ var setXtitle = function (options, title) {
968
+ options.xAxis.title.text = title;
969
+ };
920
970
 
921
- var options = jsOptions(chart, chart.options, chartOptions);
922
- var columnType = chart.discrete ? "string" : "datetime";
923
- if (chart.options.xtype === "number") {
924
- columnType = "number";
971
+ var setYtitle = function (options, title) {
972
+ options.yAxis.title.text = title;
973
+ };
974
+
975
+ var jsOptions = jsOptionsFunc(defaultOptions, hideLegend, setTitle, setMin, setMax, setStacked, setXtitle, setYtitle);
976
+
977
+ var drawChart = function(chart, data, options) {
978
+ if (chart.chart) {
979
+ chart.chart.destroy();
980
+ }
981
+
982
+ options.chart.renderTo = chart.element.id;
983
+ options.series = data;
984
+ chart.chart = new Highcharts.Chart(options);
985
+ };
986
+
987
+ var setFormatOptions = function(chart, options, chartType) {
988
+ var formatOptions = {
989
+ prefix: chart.options.prefix,
990
+ suffix: chart.options.suffix,
991
+ thousands: chart.options.thousands,
992
+ decimal: chart.options.decimal
993
+ };
994
+
995
+ if (formatOptions.prefix || formatOptions.suffix || formatOptions.thousands || formatOptions.decimal) {
996
+ if (chartType !== "pie" && !options.yAxis.labels.formatter) {
997
+ options.yAxis.labels.formatter = function () {
998
+ return formatValue("", this.value, formatOptions);
999
+ };
925
1000
  }
926
- var data = createDataTable(chart.data, columnType);
927
- chart.chart = new google.visualization.AreaChart(chart.element);
928
- resize(function () {
929
- chart.chart.draw(data, options);
930
- });
931
- });
1001
+
1002
+ if (!options.tooltip.pointFormatter) {
1003
+ options.tooltip.pointFormatter = function () {
1004
+ return '<span style="color:' + this.color + '>\u25CF</span> ' + formatValue(this.series.name + ': <b>', this.y, formatOptions) + '</b><br/>';
1005
+ };
1006
+ }
1007
+ }
932
1008
  };
933
1009
 
934
- this.renderGeoChart = function (chart) {
935
- waitForLoaded(function () {
936
- var chartOptions = {
937
- legend: "none",
938
- colorAxis: {
939
- colors: chart.options.colors || ["#f6c7b6", "#ce502d"]
1010
+ var renderLineChart = function (chart, chartType) {
1011
+ chartType = chartType || "spline";
1012
+ var chartOptions = {};
1013
+ if (chartType === "areaspline") {
1014
+ chartOptions = {
1015
+ plotOptions: {
1016
+ areaspline: {
1017
+ stacking: "normal"
1018
+ },
1019
+ area: {
1020
+ stacking: "normal"
1021
+ },
1022
+ series: {
1023
+ marker: {
1024
+ enabled: false
1025
+ }
1026
+ }
940
1027
  }
941
1028
  };
942
- var options = merge(merge(defaultOptions, chartOptions), chart.options.library || {});
1029
+ }
943
1030
 
944
- var data = new google.visualization.DataTable();
945
- data.addColumn("string", "");
946
- data.addColumn("number", chart.options.label || "Value");
947
- data.addRows(chart.data);
1031
+ if (chart.options.curve === false) {
1032
+ if (chartType === "areaspline") {
1033
+ chartType = "area";
1034
+ } else if (chartType === "spline") {
1035
+ chartType = "line";
1036
+ }
1037
+ }
948
1038
 
949
- chart.chart = new google.visualization.GeoChart(chart.element);
950
- resize(function () {
951
- chart.chart.draw(data, options);
952
- });
953
- });
1039
+ var options = jsOptions(chart, chart.options, chartOptions), data, i, j;
1040
+ options.xAxis.type = chart.discrete ? "category" : "datetime";
1041
+ if (!options.chart.type) {
1042
+ options.chart.type = chartType;
1043
+ }
1044
+ setFormatOptions(chart, options, chartType);
1045
+
1046
+ var series = chart.data;
1047
+ for (i = 0; i < series.length; i++) {
1048
+ data = series[i].data;
1049
+ if (!chart.discrete) {
1050
+ for (j = 0; j < data.length; j++) {
1051
+ data[j][0] = data[j][0].getTime();
1052
+ }
1053
+ }
1054
+ series[i].marker = {symbol: "circle"};
1055
+ if (chart.options.points === false) {
1056
+ series[i].marker.enabled = false;
1057
+ }
1058
+ }
1059
+
1060
+ drawChart(chart, series, options);
954
1061
  };
955
1062
 
956
- this.renderScatterChart = function (chart) {
957
- waitForLoaded(function () {
958
- var chartOptions = {};
959
- var options = jsOptions(chart, chart.options, chartOptions);
1063
+ var renderScatterChart = function (chart) {
1064
+ var options = jsOptions(chart, chart.options, {});
1065
+ options.chart.type = "scatter";
1066
+ drawChart(chart, chart.data, options);
1067
+ };
960
1068
 
961
- var series = chart.data, rows2 = [], i, j, data, d;
962
- for (i = 0; i < series.length; i++) {
963
- d = series[i].data;
964
- for (j = 0; j < d.length; j++) {
965
- var row = new Array(series.length + 1);
966
- row[0] = d[j][0];
967
- row[i + 1] = d[j][1];
968
- rows2.push(row);
1069
+ var renderPieChart = function (chart) {
1070
+ var chartOptions = merge(defaultOptions, {});
1071
+
1072
+ if (chart.options.colors) {
1073
+ chartOptions.colors = chart.options.colors;
1074
+ }
1075
+ if (chart.options.donut) {
1076
+ chartOptions.plotOptions = {pie: {innerSize: "50%"}};
1077
+ }
1078
+
1079
+ if ("legend" in chart.options) {
1080
+ hideLegend(chartOptions, chart.options.legend);
1081
+ }
1082
+
1083
+ if (chart.options.title) {
1084
+ setTitle(chartOptions, chart.options.title);
1085
+ }
1086
+
1087
+ var options = merge(chartOptions, chart.options.library || {});
1088
+ setFormatOptions(chart, options, "pie");
1089
+ var series = [{
1090
+ type: "pie",
1091
+ name: chart.options.label || "Value",
1092
+ data: chart.data
1093
+ }];
1094
+
1095
+ drawChart(chart, series, options);
1096
+ };
1097
+
1098
+ var renderColumnChart = function (chart, chartType) {
1099
+ chartType = chartType || "column";
1100
+ var series = chart.data;
1101
+ var options = jsOptions(chart, chart.options), i, j, s, d, rows = [], categories = [];
1102
+ options.chart.type = chartType;
1103
+ setFormatOptions(chart, options, chartType);
1104
+
1105
+ for (i = 0; i < series.length; i++) {
1106
+ s = series[i];
1107
+
1108
+ for (j = 0; j < s.data.length; j++) {
1109
+ d = s.data[j];
1110
+ if (!rows[d[0]]) {
1111
+ rows[d[0]] = new Array(series.length);
1112
+ categories.push(d[0]);
969
1113
  }
1114
+ rows[d[0]][i] = d[1];
970
1115
  }
1116
+ }
971
1117
 
972
- var data = new google.visualization.DataTable();
973
- data.addColumn("number", "");
974
- for (i = 0; i < series.length; i++) {
975
- data.addColumn("number", series[i].name);
976
- }
977
- data.addRows(rows2);
1118
+ if (chart.options.xtype === "number") {
1119
+ categories.sort(sortByNumber);
1120
+ }
978
1121
 
979
- chart.chart = new google.visualization.ScatterChart(chart.element);
980
- resize(function () {
981
- chart.chart.draw(data, options);
982
- });
983
- });
984
- };
1122
+ options.xAxis.categories = categories;
985
1123
 
986
- this.renderTimeline = function (chart) {
987
- waitForLoaded("timeline", function () {
988
- var chartOptions = {
989
- legend: "none"
990
- };
1124
+ var newSeries = [], d2;
1125
+ for (i = 0; i < series.length; i++) {
1126
+ d = [];
1127
+ for (j = 0; j < categories.length; j++) {
1128
+ d.push(rows[categories[j]][i] || 0);
1129
+ }
991
1130
 
992
- if (chart.options.colors) {
993
- chartOptions.colors = chart.options.colors;
1131
+ d2 = {
1132
+ name: series[i].name,
1133
+ data: d
1134
+ };
1135
+ if (series[i].stack) {
1136
+ d2.stack = series[i].stack;
994
1137
  }
995
- var options = merge(merge(defaultOptions, chartOptions), chart.options.library || {});
996
1138
 
997
- var data = new google.visualization.DataTable();
998
- data.addColumn({type: "string", id: "Name"});
999
- data.addColumn({type: "date", id: "Start"});
1000
- data.addColumn({type: "date", id: "End"});
1001
- data.addRows(chart.data);
1139
+ newSeries.push(d2);
1140
+ }
1002
1141
 
1003
- chart.element.style.lineHeight = "normal";
1004
- chart.chart = new google.visualization.Timeline(chart.element);
1142
+ drawChart(chart, newSeries, options);
1143
+ };
1005
1144
 
1006
- resize(function () {
1007
- chart.chart.draw(data, options);
1008
- });
1009
- });
1145
+ var renderBarChart = function (chart) {
1146
+ renderColumnChart(chart, "bar");
1010
1147
  };
1011
- };
1012
1148
 
1013
- adapters.push(GoogleChartsAdapter);
1149
+ var renderAreaChart = function (chart) {
1150
+ renderLineChart(chart, "areaspline");
1151
+ };
1152
+
1153
+ return {
1154
+ name: "highcharts",
1155
+ renderLineChart: renderLineChart,
1156
+ renderPieChart: renderPieChart,
1157
+ renderColumnChart: renderColumnChart,
1158
+ renderBarChart: renderBarChart,
1159
+ renderAreaChart: renderAreaChart,
1160
+ renderScatterChart: renderScatterChart
1161
+ };
1162
+ })();
1163
+ adapters.push(HighchartsAdapter);
1014
1164
  }
1015
- if (!ChartjsAdapter && "Chart" in window) {
1016
- ChartjsAdapter = new function () {
1017
- var Chart = window.Chart;
1165
+ if (!GoogleChartsAdapter && window.google && (window.google.setOnLoadCallback || window.google.charts)) {
1166
+ GoogleChartsAdapter = (function () {
1167
+ var google = window.google;
1018
1168
 
1019
- this.name = "chartjs";
1169
+ var loaded = {};
1170
+ var callbacks = [];
1020
1171
 
1021
- var baseOptions = {
1022
- maintainAspectRatio: false,
1023
- animation: false,
1024
- tooltips: {
1025
- displayColors: false
1026
- },
1027
- legend: {},
1028
- title: {fontSize: 20, fontColor: "#333"}
1172
+ var runCallbacks = function () {
1173
+ var cb, call;
1174
+ for (var i = 0; i < callbacks.length; i++) {
1175
+ cb = callbacks[i];
1176
+ call = google.visualization && ((cb.pack === "corechart" && google.visualization.LineChart) || (cb.pack === "timeline" && google.visualization.Timeline));
1177
+ if (call) {
1178
+ cb.callback();
1179
+ callbacks.splice(i, 1);
1180
+ i--;
1181
+ }
1182
+ }
1029
1183
  };
1030
1184
 
1185
+ var waitForLoaded = function (pack, callback) {
1186
+ if (!callback) {
1187
+ callback = pack;
1188
+ pack = "corechart";
1189
+ }
1190
+
1191
+ callbacks.push({pack: pack, callback: callback});
1192
+
1193
+ if (loaded[pack]) {
1194
+ runCallbacks();
1195
+ } else {
1196
+ loaded[pack] = true;
1197
+
1198
+ // https://groups.google.com/forum/#!topic/google-visualization-api/fMKJcyA2yyI
1199
+ var loadOptions = {
1200
+ packages: [pack],
1201
+ callback: runCallbacks
1202
+ };
1203
+ if (config.language) {
1204
+ loadOptions.language = config.language;
1205
+ }
1206
+ if (pack === "corechart" && config.mapsApiKey) {
1207
+ loadOptions.mapsApiKey = config.mapsApiKey;
1208
+ }
1209
+
1210
+ if (window.google.setOnLoadCallback) {
1211
+ google.load("visualization", "1", loadOptions);
1212
+ } else {
1213
+ google.charts.load("current", loadOptions);
1214
+ }
1215
+ }
1216
+ };
1217
+
1218
+ // Set chart options
1031
1219
  var defaultOptions = {
1032
- scales: {
1033
- yAxes: [
1034
- {
1035
- ticks: {
1036
- maxTicksLimit: 4
1037
- },
1038
- scaleLabel: {
1039
- fontSize: 16,
1040
- // fontStyle: "bold",
1041
- fontColor: "#333"
1042
- }
1043
- }
1044
- ],
1045
- xAxes: [
1046
- {
1047
- gridLines: {
1048
- drawOnChartArea: false
1049
- },
1050
- scaleLabel: {
1051
- fontSize: 16,
1052
- // fontStyle: "bold",
1053
- fontColor: "#333"
1054
- },
1055
- time: {},
1056
- ticks: {}
1057
- }
1058
- ]
1220
+ chartArea: {},
1221
+ fontName: "'Lucida Grande', 'Lucida Sans Unicode', Verdana, Arial, Helvetica, sans-serif",
1222
+ pointSize: 6,
1223
+ legend: {
1224
+ textStyle: {
1225
+ fontSize: 12,
1226
+ color: "#444"
1227
+ },
1228
+ alignment: "center",
1229
+ position: "right"
1230
+ },
1231
+ curveType: "function",
1232
+ hAxis: {
1233
+ textStyle: {
1234
+ color: "#666",
1235
+ fontSize: 12
1236
+ },
1237
+ titleTextStyle: {},
1238
+ gridlines: {
1239
+ color: "transparent"
1240
+ },
1241
+ baselineColor: "#ccc",
1242
+ viewWindow: {}
1243
+ },
1244
+ vAxis: {
1245
+ textStyle: {
1246
+ color: "#666",
1247
+ fontSize: 12
1248
+ },
1249
+ titleTextStyle: {},
1250
+ baselineColor: "#ccc",
1251
+ viewWindow: {}
1252
+ },
1253
+ tooltip: {
1254
+ textStyle: {
1255
+ color: "#666",
1256
+ fontSize: 12
1257
+ }
1059
1258
  }
1060
1259
  };
1061
1260
 
1062
- // http://there4.io/2012/05/02/google-chart-color-list/
1063
- var defaultColors = [
1064
- "#3366CC", "#DC3912", "#FF9900", "#109618", "#990099", "#3B3EAC", "#0099C6",
1065
- "#DD4477", "#66AA00", "#B82E2E", "#316395", "#994499", "#22AA99", "#AAAA11",
1066
- "#6633CC", "#E67300", "#8B0707", "#329262", "#5574A6", "#651067"
1067
- ];
1068
-
1069
1261
  var hideLegend = function (options, legend, hideLegend) {
1070
1262
  if (legend !== undefined) {
1071
- options.legend.display = !!legend;
1072
- if (legend && legend !== true) {
1073
- options.legend.position = legend;
1263
+ var position;
1264
+ if (!legend) {
1265
+ position = "none";
1266
+ } else if (legend === true) {
1267
+ position = "right";
1268
+ } else {
1269
+ position = legend;
1074
1270
  }
1271
+ options.legend.position = position;
1075
1272
  } else if (hideLegend) {
1076
- options.legend.display = false;
1273
+ options.legend.position = "none";
1077
1274
  }
1078
1275
  };
1079
1276
 
1080
1277
  var setTitle = function (options, title) {
1081
- options.title.display = true;
1082
- options.title.text = title;
1278
+ options.title = title;
1279
+ options.titleTextStyle = {color: "#333", fontSize: "20px"};
1083
1280
  };
1084
1281
 
1085
1282
  var setMin = function (options, min) {
1086
- if (min !== null) {
1087
- options.scales.yAxes[0].ticks.min = toFloat(min);
1088
- }
1283
+ options.vAxis.viewWindow.min = min;
1089
1284
  };
1090
1285
 
1091
1286
  var setMax = function (options, max) {
1092
- options.scales.yAxes[0].ticks.max = toFloat(max);
1287
+ options.vAxis.viewWindow.max = max;
1093
1288
  };
1094
1289
 
1095
1290
  var setBarMin = function (options, min) {
1096
- if (min !== null) {
1097
- options.scales.xAxes[0].ticks.min = toFloat(min);
1098
- }
1291
+ options.hAxis.viewWindow.min = min;
1099
1292
  };
1100
1293
 
1101
1294
  var setBarMax = function (options, max) {
1102
- options.scales.xAxes[0].ticks.max = toFloat(max);
1295
+ options.hAxis.viewWindow.max = max;
1103
1296
  };
1104
1297
 
1105
1298
  var setStacked = function (options, stacked) {
1106
- options.scales.xAxes[0].stacked = !!stacked;
1107
- options.scales.yAxes[0].stacked = !!stacked;
1299
+ options.isStacked = !!stacked;
1108
1300
  };
1109
1301
 
1110
1302
  var setXtitle = function (options, title) {
1111
- options.scales.xAxes[0].scaleLabel.display = true;
1112
- options.scales.xAxes[0].scaleLabel.labelString = title;
1303
+ options.hAxis.title = title;
1304
+ options.hAxis.titleTextStyle.italic = false;
1113
1305
  };
1114
1306
 
1115
1307
  var setYtitle = function (options, title) {
1116
- options.scales.yAxes[0].scaleLabel.display = true;
1117
- options.scales.yAxes[0].scaleLabel.labelString = title;
1118
- };
1119
-
1120
- var drawChart = function(chart, type, data, options) {
1121
- if (chart.chart) {
1122
- chart.chart.destroy();
1123
- } else {
1124
- chart.element.innerHTML = "<canvas></canvas>";
1125
- }
1126
-
1127
- var ctx = chart.element.getElementsByTagName("CANVAS")[0];
1128
- chart.chart = new Chart(ctx, {
1129
- type: type,
1130
- data: data,
1131
- options: options
1132
- });
1133
- };
1134
-
1135
- // http://stackoverflow.com/questions/5623838/rgb-to-hex-and-hex-to-rgb
1136
- var addOpacity = function(hex, opacity) {
1137
- var result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex);
1138
- return result ? "rgba(" + parseInt(result[1], 16) + ", " + parseInt(result[2], 16) + ", " + parseInt(result[3], 16) + ", " + opacity + ")" : hex;
1139
- };
1140
-
1141
- var setLabelSize = function (chart, data, options) {
1142
- var maxLabelSize = Math.ceil(chart.element.offsetWidth / 4.0 / data.labels.length);
1143
- if (maxLabelSize > 25) {
1144
- maxLabelSize = 25;
1145
- }
1146
- options.scales.xAxes[0].ticks.callback = function (value) {
1147
- value = toStr(value);
1148
- if (value.length > maxLabelSize) {
1149
- return value.substring(0, maxLabelSize - 2) + "...";
1150
- } else {
1151
- return value;
1152
- }
1153
- };
1308
+ options.vAxis.title = title;
1309
+ options.vAxis.titleTextStyle.italic = false;
1154
1310
  };
1155
1311
 
1156
- var jsOptions = jsOptionsFunc(merge(baseOptions, defaultOptions), hideLegend, setTitle, setMin, setMax, setStacked, setXtitle, setYtitle);
1157
-
1158
- var createDataTable = function (chart, options, chartType) {
1159
- var datasets = [];
1160
- var labels = [];
1161
-
1162
- var colors = chart.options.colors || defaultColors;
1163
-
1164
- var day = true;
1165
- var week = true;
1166
- var dayOfWeek;
1167
- var month = true;
1168
- var year = true;
1169
- var hour = true;
1170
- var minute = true;
1171
- var detectType = (chartType === "line" || chartType === "area") && !chart.discrete;
1172
-
1173
- var series = chart.data;
1174
-
1175
- var sortedLabels = [];
1312
+ var jsOptions = jsOptionsFunc(defaultOptions, hideLegend, setTitle, setMin, setMax, setStacked, setXtitle, setYtitle);
1176
1313
 
1177
- var i, j, s, d, key, rows = [];
1314
+ // cant use object as key
1315
+ var createDataTable = function (series, columnType, xtype) {
1316
+ var i, j, s, d, key, rows = [], sortedLabels = [];
1178
1317
  for (i = 0; i < series.length; i++) {
1179
1318
  s = series[i];
1180
1319
 
1181
1320
  for (j = 0; j < s.data.length; j++) {
1182
1321
  d = s.data[j];
1183
- key = detectType ? d[0].getTime() : d[0];
1322
+ key = (columnType === "datetime") ? d[0].getTime() : d[0];
1184
1323
  if (!rows[key]) {
1185
1324
  rows[key] = new Array(series.length);
1186
- }
1187
- rows[key][i] = toFloat(d[1]);
1188
- if (sortedLabels.indexOf(key) === -1) {
1189
1325
  sortedLabels.push(key);
1190
1326
  }
1327
+ rows[key][i] = toFloat(d[1]);
1191
1328
  }
1192
1329
  }
1193
1330
 
1194
- if (detectType || chart.options.xtype === "number") {
1195
- sortedLabels.sort(sortByNumber);
1196
- }
1197
-
1198
1331
  var rows2 = [];
1199
- for (j = 0; j < series.length; j++) {
1200
- rows2.push([]);
1201
- }
1202
-
1332
+ var day = true;
1203
1333
  var value;
1204
- var k;
1205
- for (k = 0; k < sortedLabels.length; k++) {
1206
- i = sortedLabels[k];
1207
- if (detectType) {
1334
+ for (j = 0; j < sortedLabels.length; j++) {
1335
+ i = sortedLabels[j];
1336
+ if (columnType === "datetime") {
1208
1337
  value = new Date(toFloat(i));
1209
- // TODO make this efficient
1210
1338
  day = day && isDay(value);
1211
- if (!dayOfWeek) {
1212
- dayOfWeek = value.getDay();
1213
- }
1214
- week = week && isWeek(value, dayOfWeek);
1215
- month = month && isMonth(value);
1216
- year = year && isYear(value);
1217
- hour = hour && isHour(value);
1218
- minute = minute && isMinute(value);
1339
+ } else if (columnType === "number") {
1340
+ value = toFloat(i);
1219
1341
  } else {
1220
1342
  value = i;
1221
1343
  }
1222
- labels.push(value);
1223
- for (j = 0; j < series.length; j++) {
1224
- // Chart.js doesn't like undefined
1225
- rows2[j].push(rows[i][j] === undefined ? null : rows[i][j]);
1226
- }
1344
+ rows2.push([value].concat(rows[i]));
1227
1345
  }
1228
-
1229
- for (i = 0; i < series.length; i++) {
1230
- s = series[i];
1231
-
1232
- var color = s.color || colors[i];
1233
- var backgroundColor = chartType !== "line" ? addOpacity(color, 0.5) : color;
1234
-
1235
- var dataset = {
1236
- label: s.name,
1237
- data: rows2[i],
1238
- fill: chartType === "area",
1239
- borderColor: color,
1240
- backgroundColor: backgroundColor,
1241
- pointBackgroundColor: color,
1242
- borderWidth: 2
1243
- };
1244
-
1245
- if (s.stack) {
1246
- dataset.stack = s.stack;
1247
- }
1248
-
1249
- if (chart.options.curve === false) {
1250
- dataset.lineTension = 0;
1251
- }
1252
-
1253
- if (chart.options.points === false) {
1254
- dataset.pointRadius = 0;
1255
- dataset.pointHitRadius = 5;
1256
- }
1257
-
1258
- datasets.push(merge(dataset, s.library || {}));
1346
+ if (columnType === "datetime") {
1347
+ rows2.sort(sortByTime);
1348
+ } else if (columnType === "number") {
1349
+ rows2.sort(sortByNumberSeries);
1259
1350
  }
1260
-
1261
- if (detectType && labels.length > 0) {
1262
- var minTime = labels[0].getTime();
1263
- var maxTime = labels[0].getTime();
1264
- for (i = 1; i < labels.length; i++) {
1265
- value = labels[i].getTime();
1266
- if (value < minTime) {
1267
- minTime = value;
1268
- }
1269
- if (value > maxTime) {
1270
- maxTime = value;
1271
- }
1272
- }
1273
-
1274
- var timeDiff = (maxTime - minTime) / (86400 * 1000.0);
1275
-
1276
- if (!options.scales.xAxes[0].time.unit) {
1277
- var step;
1278
- if (year || timeDiff > 365 * 10) {
1279
- options.scales.xAxes[0].time.unit = "year";
1280
- step = 365;
1281
- } else if (month || timeDiff > 30 * 10) {
1282
- options.scales.xAxes[0].time.unit = "month";
1283
- step = 30;
1284
- } else if (day || timeDiff > 10) {
1285
- options.scales.xAxes[0].time.unit = "day";
1286
- step = 1;
1287
- } else if (hour || timeDiff > 0.5) {
1288
- options.scales.xAxes[0].time.displayFormats = {hour: "MMM D, h a"};
1289
- options.scales.xAxes[0].time.unit = "hour";
1290
- step = 1 / 24.0;
1291
- } else if (minute) {
1292
- options.scales.xAxes[0].time.displayFormats = {minute: "h:mm a"};
1293
- options.scales.xAxes[0].time.unit = "minute";
1294
- step = 1 / 24.0 / 60.0;
1295
- }
1296
-
1297
- if (step && timeDiff > 0) {
1298
- var unitStepSize = Math.ceil(timeDiff / step / (chart.element.offsetWidth / 100.0));
1299
- if (week && step === 1) {
1300
- unitStepSize = Math.ceil(unitStepSize / 7.0) * 7;
1301
- }
1302
- options.scales.xAxes[0].time.unitStepSize = unitStepSize;
1303
- }
1304
- }
1305
-
1306
- if (!options.scales.xAxes[0].time.tooltipFormat) {
1307
- if (day) {
1308
- options.scales.xAxes[0].time.tooltipFormat = "ll";
1309
- } else if (hour) {
1310
- options.scales.xAxes[0].time.tooltipFormat = "MMM D, h a";
1311
- } else if (minute) {
1312
- options.scales.xAxes[0].time.tooltipFormat = "h:mm a";
1313
- }
1351
+
1352
+ if (xtype === "number") {
1353
+ rows2.sort(sortByNumberSeries);
1354
+
1355
+ for (i = 0; i < rows2.length; i++) {
1356
+ rows2[i][0] = toStr(rows2[i][0]);
1314
1357
  }
1315
1358
  }
1316
1359
 
1317
- var data = {
1318
- labels: labels,
1319
- datasets: datasets
1320
- };
1360
+ // create datatable
1361
+ var data = new google.visualization.DataTable();
1362
+ columnType = columnType === "datetime" && day ? "date" : columnType;
1363
+ data.addColumn(columnType, "");
1364
+ for (i = 0; i < series.length; i++) {
1365
+ data.addColumn("number", series[i].name);
1366
+ }
1367
+ data.addRows(rows2);
1321
1368
 
1322
1369
  return data;
1323
1370
  };
1324
1371
 
1325
- this.renderLineChart = function (chart, chartType) {
1326
- if (chart.options.xtype === "number") {
1327
- return self.renderScatterChart(chart, chartType, true);
1372
+ var resize = function (callback) {
1373
+ if (window.attachEvent) {
1374
+ window.attachEvent("onresize", callback);
1375
+ } else if (window.addEventListener) {
1376
+ window.addEventListener("resize", callback, true);
1328
1377
  }
1378
+ callback();
1379
+ };
1329
1380
 
1330
- var chartOptions = {};
1331
- if (chartType === "area") {
1332
- // TODO fix area stacked
1333
- // chartOptions.stacked = true;
1334
- }
1335
- // fix for https://github.com/chartjs/Chart.js/issues/2441
1336
- if (!chart.options.max && allZeros(chart.data)) {
1337
- chartOptions.max = 1;
1381
+ var drawChart = function(chart, type, data, options) {
1382
+ if (chart.chart) {
1383
+ chart.chart.clearChart();
1338
1384
  }
1339
1385
 
1340
- var options = jsOptions(chart, merge(chartOptions, chart.options));
1386
+ chart.chart = new type(chart.element);
1387
+ resize(function () {
1388
+ chart.chart.draw(data, options);
1389
+ });
1390
+ };
1341
1391
 
1342
- var data = createDataTable(chart, options, chartType || "line");
1392
+ var renderLineChart = function (chart) {
1393
+ waitForLoaded(function () {
1394
+ var chartOptions = {};
1343
1395
 
1344
- options.scales.xAxes[0].type = chart.discrete ? "category" : "time";
1396
+ if (chart.options.curve === false) {
1397
+ chartOptions.curveType = "none";
1398
+ }
1345
1399
 
1346
- drawChart(chart, "line", data, options);
1400
+ if (chart.options.points === false) {
1401
+ chartOptions.pointSize = 0;
1402
+ }
1403
+
1404
+ var options = jsOptions(chart, chart.options, chartOptions);
1405
+ var columnType = chart.discrete ? "string" : "datetime";
1406
+ if (chart.options.xtype === "number") {
1407
+ columnType = "number";
1408
+ }
1409
+ var data = createDataTable(chart.data, columnType);
1410
+
1411
+ drawChart(chart, google.visualization.LineChart, data, options);
1412
+ });
1347
1413
  };
1348
1414
 
1349
- this.renderPieChart = function (chart) {
1350
- var options = merge({}, baseOptions);
1351
- if (chart.options.donut) {
1352
- options.cutoutPercentage = 50;
1353
- }
1415
+ var renderPieChart = function (chart) {
1416
+ waitForLoaded(function () {
1417
+ var chartOptions = {
1418
+ chartArea: {
1419
+ top: "10%",
1420
+ height: "80%"
1421
+ },
1422
+ legend: {}
1423
+ };
1424
+ if (chart.options.colors) {
1425
+ chartOptions.colors = chart.options.colors;
1426
+ }
1427
+ if (chart.options.donut) {
1428
+ chartOptions.pieHole = 0.5;
1429
+ }
1430
+ if ("legend" in chart.options) {
1431
+ hideLegend(chartOptions, chart.options.legend);
1432
+ }
1433
+ if (chart.options.title) {
1434
+ setTitle(chartOptions, chart.options.title);
1435
+ }
1436
+ var options = merge(merge(defaultOptions, chartOptions), chart.options.library || {});
1354
1437
 
1355
- if ("legend" in chart.options) {
1356
- hideLegend(options, chart.options.legend);
1357
- }
1438
+ var data = new google.visualization.DataTable();
1439
+ data.addColumn("string", "");
1440
+ data.addColumn("number", "Value");
1441
+ data.addRows(chart.data);
1358
1442
 
1359
- if (chart.options.title) {
1360
- setTitle(options, chart.options.title);
1361
- }
1443
+ drawChart(chart, google.visualization.PieChart, data, options);
1444
+ });
1445
+ };
1362
1446
 
1363
- options = merge(options, chart.options.library || {});
1447
+ var renderColumnChart = function (chart) {
1448
+ waitForLoaded(function () {
1449
+ var options = jsOptions(chart, chart.options);
1450
+ var data = createDataTable(chart.data, "string", chart.options.xtype);
1364
1451
 
1365
- var labels = [];
1366
- var values = [];
1367
- for (var i = 0; i < chart.data.length; i++) {
1368
- var point = chart.data[i];
1369
- labels.push(point[0]);
1370
- values.push(point[1]);
1371
- }
1452
+ drawChart(chart, google.visualization.ColumnChart, data, options);
1453
+ });
1454
+ };
1372
1455
 
1373
- var data = {
1374
- labels: labels,
1375
- datasets: [
1376
- {
1377
- data: values,
1378
- backgroundColor: chart.options.colors || defaultColors
1456
+ var renderBarChart = function (chart) {
1457
+ waitForLoaded(function () {
1458
+ var chartOptions = {
1459
+ hAxis: {
1460
+ gridlines: {
1461
+ color: "#ccc"
1462
+ }
1379
1463
  }
1380
- ]
1381
- };
1464
+ };
1465
+ var options = jsOptionsFunc(defaultOptions, hideLegend, setTitle, setBarMin, setBarMax, setStacked, setXtitle, setYtitle)(chart, chart.options, chartOptions);
1466
+ var data = createDataTable(chart.data, "string", chart.options.xtype);
1382
1467
 
1383
- drawChart(chart, "pie", data, options);
1468
+ drawChart(chart, google.visualization.BarChart, data, options);
1469
+ });
1384
1470
  };
1385
1471
 
1386
- this.renderColumnChart = function (chart, chartType) {
1387
- var options;
1388
- if (chartType === "bar") {
1389
- options = jsOptionsFunc(merge(baseOptions, defaultOptions), hideLegend, setTitle, setBarMin, setBarMax, setStacked, setXtitle, setYtitle)(chart, chart.options);
1390
- } else {
1391
- options = jsOptions(chart, chart.options);
1392
- }
1393
- var data = createDataTable(chart, options, "column");
1394
- setLabelSize(chart, data, options);
1395
- drawChart(chart, (chartType === "bar" ? "horizontalBar" : "bar"), data, options);
1396
- };
1472
+ var renderAreaChart = function (chart) {
1473
+ waitForLoaded(function () {
1474
+ var chartOptions = {
1475
+ isStacked: true,
1476
+ pointSize: 0,
1477
+ areaOpacity: 0.5
1478
+ };
1397
1479
 
1398
- var self = this;
1480
+ var options = jsOptions(chart, chart.options, chartOptions);
1481
+ var columnType = chart.discrete ? "string" : "datetime";
1482
+ if (chart.options.xtype === "number") {
1483
+ columnType = "number";
1484
+ }
1485
+ var data = createDataTable(chart.data, columnType);
1399
1486
 
1400
- this.renderAreaChart = function (chart) {
1401
- self.renderLineChart(chart, "area");
1487
+ drawChart(chart, google.visualization.AreaChart, data, options);
1488
+ });
1402
1489
  };
1403
1490
 
1404
- this.renderBarChart = function (chart) {
1405
- self.renderColumnChart(chart, "bar");
1406
- };
1491
+ var renderGeoChart = function (chart) {
1492
+ waitForLoaded(function () {
1493
+ var chartOptions = {
1494
+ legend: "none",
1495
+ colorAxis: {
1496
+ colors: chart.options.colors || ["#f6c7b6", "#ce502d"]
1497
+ }
1498
+ };
1499
+ var options = merge(merge(defaultOptions, chartOptions), chart.options.library || {});
1407
1500
 
1408
- this.renderScatterChart = function (chart, chartType, lineChart) {
1409
- chartType = chartType || "line";
1501
+ var data = new google.visualization.DataTable();
1502
+ data.addColumn("string", "");
1503
+ data.addColumn("number", chart.options.label || "Value");
1504
+ data.addRows(chart.data);
1410
1505
 
1411
- var options = jsOptions(chart, chart.options);
1506
+ drawChart(chart, google.visualization.GeoChart, data, options);
1507
+ });
1508
+ };
1412
1509
 
1413
- var colors = chart.options.colors || defaultColors;
1510
+ var renderScatterChart = function (chart) {
1511
+ waitForLoaded(function () {
1512
+ var chartOptions = {};
1513
+ var options = jsOptions(chart, chart.options, chartOptions);
1414
1514
 
1415
- var datasets = [];
1416
- var series = chart.data;
1417
- for (var i = 0; i < series.length; i++) {
1418
- var s = series[i];
1419
- var d = [];
1420
- for (var j = 0; j < s.data.length; j++) {
1421
- var point = {
1422
- x: toFloat(s.data[j][0]),
1423
- y: toFloat(s.data[j][1])
1424
- };
1425
- if (chartType === "bubble") {
1426
- point.r = toFloat(s.data[j][2]);
1515
+ var series = chart.data, rows2 = [], i, j, data, d;
1516
+ for (i = 0; i < series.length; i++) {
1517
+ d = series[i].data;
1518
+ for (j = 0; j < d.length; j++) {
1519
+ var row = new Array(series.length + 1);
1520
+ row[0] = d[j][0];
1521
+ row[i + 1] = d[j][1];
1522
+ rows2.push(row);
1427
1523
  }
1428
- d.push(point);
1429
1524
  }
1430
1525
 
1431
- var color = s.color || colors[i];
1432
- var backgroundColor = chartType === "area" ? addOpacity(color, 0.5) : color;
1526
+ data = new google.visualization.DataTable();
1527
+ data.addColumn("number", "");
1528
+ for (i = 0; i < series.length; i++) {
1529
+ data.addColumn("number", series[i].name);
1530
+ }
1531
+ data.addRows(rows2);
1433
1532
 
1434
- datasets.push({
1435
- label: s.name,
1436
- showLine: lineChart || false,
1437
- data: d,
1438
- borderColor: color,
1439
- backgroundColor: backgroundColor,
1440
- pointBackgroundColor: color,
1441
- fill: chartType === "area"
1442
- })
1443
- }
1533
+ drawChart(chart, google.visualization.ScatterChart, data, options);
1534
+ });
1535
+ };
1444
1536
 
1445
- if (chartType === "area") {
1446
- chartType = "line";
1447
- }
1537
+ var renderTimeline = function (chart) {
1538
+ waitForLoaded("timeline", function () {
1539
+ var chartOptions = {
1540
+ legend: "none"
1541
+ };
1448
1542
 
1449
- var data = {datasets: datasets};
1543
+ if (chart.options.colors) {
1544
+ chartOptions.colors = chart.options.colors;
1545
+ }
1546
+ var options = merge(merge(defaultOptions, chartOptions), chart.options.library || {});
1450
1547
 
1451
- options.scales.xAxes[0].type = "linear";
1452
- options.scales.xAxes[0].position = "bottom";
1548
+ var data = new google.visualization.DataTable();
1549
+ data.addColumn({type: "string", id: "Name"});
1550
+ data.addColumn({type: "date", id: "Start"});
1551
+ data.addColumn({type: "date", id: "End"});
1552
+ data.addRows(chart.data);
1453
1553
 
1454
- drawChart(chart, chartType, data, options);
1554
+ chart.element.style.lineHeight = "normal";
1555
+
1556
+ drawChart(chart, google.visualization.Timeline, data, options);
1557
+ });
1455
1558
  };
1456
1559
 
1457
- this.renderBubbleChart = function (chart) {
1458
- this.renderScatterChart(chart, "bubble");
1560
+ return {
1561
+ name: "google",
1562
+ renderLineChart: renderLineChart,
1563
+ renderPieChart: renderPieChart,
1564
+ renderColumnChart: renderColumnChart,
1565
+ renderBarChart: renderBarChart,
1566
+ renderAreaChart: renderAreaChart,
1567
+ renderScatterChart: renderScatterChart,
1568
+ renderGeoChart: renderGeoChart,
1569
+ renderTimeline: renderTimeline
1459
1570
  };
1460
- };
1571
+ })();
1572
+
1573
+ adapters.push(GoogleChartsAdapter);
1574
+ }
1575
+ }
1461
1576
 
1462
- adapters.unshift(ChartjsAdapter);
1577
+ function dataEmpty(data, chartType) {
1578
+ if (chartType === "PieChart" || chartType === "GeoChart" || chartType === "Timeline") {
1579
+ return data.length === 0;
1580
+ } else {
1581
+ for (var i = 0; i < data.length; i++) {
1582
+ if (data[i].data.length > 0) {
1583
+ return false;
1584
+ }
1585
+ }
1586
+ return true;
1463
1587
  }
1464
1588
  }
1465
1589
 
1466
1590
  function renderChart(chartType, chart) {
1467
- callAdapter(chartType, chart);
1468
- if (chart.options.download && !chart.downloadAttached && chart.adapter === "chartjs") {
1469
- addDownloadButton(chart);
1591
+ if (chart.options.messages && chart.options.messages.empty && dataEmpty(chart.data, chartType)) {
1592
+ setText(chart.element, chart.options.messages.empty);
1593
+ } else {
1594
+ callAdapter(chartType, chart);
1595
+ if (chart.options.download && !chart.downloadAttached && chart.adapter === "chartjs") {
1596
+ addDownloadButton(chart);
1597
+ }
1470
1598
  }
1471
1599
  }
1472
1600
 
@@ -1574,18 +1702,43 @@
1574
1702
  return false;
1575
1703
  }
1576
1704
 
1705
+ function formatValue(pre, value, options) {
1706
+ if (options.thousands || options.decimal) {
1707
+ value = toStr(value);
1708
+ var parts = value.split(".")
1709
+ value = parts[0];
1710
+ if (options.thousands) {
1711
+ value = value.replace(/(\d)(?=(\d{3})+(?!\d))/g, options.thousands);
1712
+ }
1713
+ if (parts.length > 1) {
1714
+ value += (options.decimal || ".") + parts[1];
1715
+ }
1716
+ }
1717
+
1718
+ pre = pre || "";
1719
+ if (options.prefix) {
1720
+ if (value < 0) {
1721
+ value = value * -1;
1722
+ pre += "-";
1723
+ }
1724
+ pre += options.prefix;
1725
+ }
1726
+
1727
+ return pre + value + (options.suffix || "");
1728
+ }
1729
+
1577
1730
  // creates a shallow copy of each element of the array
1578
1731
  // elements are expected to be objects
1579
1732
  function copySeries(series) {
1580
1733
  var newSeries = [], i, j;
1581
1734
  for (i = 0; i < series.length; i++) {
1582
- var copy = {}
1735
+ var copy = {};
1583
1736
  for (j in series[i]) {
1584
1737
  if (series[i].hasOwnProperty(j)) {
1585
1738
  copy[j] = series[i][j];
1586
1739
  }
1587
1740
  }
1588
- newSeries.push(copy)
1741
+ newSeries.push(copy);
1589
1742
  }
1590
1743
  return newSeries;
1591
1744
  }
@@ -1684,7 +1837,7 @@
1684
1837
  if (!processData) {
1685
1838
  processData = function (chart) {
1686
1839
  return chart.rawData;
1687
- }
1840
+ };
1688
1841
  }
1689
1842
 
1690
1843
  // getters
@@ -1746,7 +1899,7 @@
1746
1899
  } else {
1747
1900
  return null;
1748
1901
  }
1749
- }
1902
+ };
1750
1903
 
1751
1904
  Chartkick.charts[element.id] = chart;
1752
1905