pghero 1.6.2 → 1.6.3

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

Potentially problematic release.


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

checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 88bdee267422f250773f1764fb7b7afea03344b1
4
- data.tar.gz: 378e02b5b0c4e3f59646bada6899a203b22ff111
3
+ metadata.gz: 1584095de5f97c95ab55a592deb44aa7eea06e35
4
+ data.tar.gz: 356b2efd8d8996b4e45378d9a840772413cb688e
5
5
  SHA512:
6
- metadata.gz: 3cac1afe8db164aa87aaa614a2210b7f38b5e6746ab58e3b9931510b1481e579ff03438f1471ac5037379e8123342fc47e526eeb308f94dba9ea6901e7fa53f6
7
- data.tar.gz: 1675887b2ea3d5ca45dfa5c5964c35c3e30de771171387fd379efc4cf92d1379e7013281274477d5c8717a0549f560857b27e22284d4558d2449083e08bd1217
6
+ metadata.gz: 2756d1a94611279615cd3362311cd7cf3aee45a610323137b902eca674a6ed7176ebcdfd8a96c7a92392fb48fceb0464f37c5cc21583343eb1aee75156300843
7
+ data.tar.gz: ea0b4eb73f5cb3ca170808e75021c808bd61d0ae5be36e99e89c86af8fd3f913236eaf87aabc8fc8afe61f10e7407599356ed9163b0e50d7b16d2d172d8561aa
data/CHANGELOG.md CHANGED
@@ -1,3 +1,12 @@
1
+ ## 1.6.3
2
+
3
+ - Added 10 second timeout for explain
4
+ - No longer show autovacuum in long running queries
5
+ - Added charts for connections
6
+ - Added new config format
7
+ - Removed Chartkick gem dependency for charts
8
+ - Fixed error when primary database is not PostgreSQL
9
+
1
10
  ## 1.6.2
2
11
 
3
12
  - Suggest GiST over GIN for `LIKE` queries again (seeing better performance)
data/README.md CHANGED
@@ -10,19 +10,13 @@ A performance dashboard for Postgres - health checks, suggested indexes, and mor
10
10
 
11
11
  ## Installation
12
12
 
13
- PgHero can be installed as a standalone app or a Rails engine.
13
+ PgHero is available as a Rails engine, Linux package, and Docker image.
14
14
 
15
- - [Linux](guides/Linux.md) - Ubuntu, Debian, and more
16
- - [Docker](guides/Docker.md)
17
- - [Heroku](guides/Heroku.md)
18
- - [Rails](guides/Rails.md)
19
-
20
- ## Related Projects
15
+ Select your preferred method of installation to get started.
21
16
 
22
- Also check out:
23
-
24
- - [pgsync](https://github.com/ankane/pgsync) - Sync Postgres data to your local machine
25
- - [pgslice](https://github.com/ankane/pgslice) - Postgres partitioning as easy as pie
17
+ - [Rails](guides/Rails.md)
18
+ - [Linux](guides/Linux.md)
19
+ - [Docker](guides/Docker.md)
26
20
 
27
21
  ## Credits
28
22
 
@@ -1,8 +1,8 @@
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
- * v2.1.0
5
+ * v2.2.2
6
6
  * MIT License
7
7
  */
8
8
 
@@ -15,6 +15,7 @@
15
15
  var Chartkick, ISO8601_PATTERN, DECIMAL_SEPARATOR, adapters = [];
16
16
  var DATE_PATTERN = /^(\d\d\d\d)(\-)?(\d\d)(\-)?(\d\d)$/i;
17
17
  var GoogleChartsAdapter, HighchartsAdapter, ChartjsAdapter;
18
+ var pendingRequests = [], runningRequests = 0, maxRequests = 4;
18
19
 
19
20
  // helpers
20
21
 
@@ -104,15 +105,18 @@
104
105
  return false;
105
106
  }
106
107
 
107
- function jsOptionsFunc(defaultOptions, hideLegend, setMin, setMax, setStacked, setXtitle, setYtitle) {
108
- return function (series, opts, chartOptions) {
108
+ function jsOptionsFunc(defaultOptions, hideLegend, setTitle, setMin, setMax, setStacked, setXtitle, setYtitle) {
109
+ return function (chart, opts, chartOptions) {
110
+ var series = chart.data;
109
111
  var options = merge({}, defaultOptions);
110
112
  options = merge(options, chartOptions || {});
111
113
 
112
- // hide legend
113
- // this is *not* an external option!
114
- if (opts.hideLegend) {
115
- hideLegend(options);
114
+ if (chart.hideLegend || "legend" in opts) {
115
+ hideLegend(options, opts.legend, chart.hideLegend);
116
+ }
117
+
118
+ if (opts.title) {
119
+ setTitle(options, opts.title);
116
120
  }
117
121
 
118
122
  // min
@@ -163,6 +167,27 @@
163
167
  element.style.color = "#ff0000";
164
168
  }
165
169
 
170
+ function pushRequest(element, url, success) {
171
+ pendingRequests.push([element, url, success]);
172
+ runNext();
173
+ }
174
+
175
+ function runNext() {
176
+ if (runningRequests < maxRequests) {
177
+ var request = pendingRequests.shift()
178
+ if (request) {
179
+ runningRequests++;
180
+ getJSON(request[0], request[1], request[2]);
181
+ runNext();
182
+ }
183
+ }
184
+ }
185
+
186
+ function requestComplete() {
187
+ runningRequests--;
188
+ runNext();
189
+ }
190
+
166
191
  function getJSON(element, url, success) {
167
192
  ajaxCall(url, success, function (jqXHR, textStatus, errorThrown) {
168
193
  var message = (typeof errorThrown === "string") ? errorThrown : errorThrown.message;
@@ -178,13 +203,15 @@
178
203
  dataType: "json",
179
204
  url: url,
180
205
  success: success,
181
- error: error
206
+ error: error,
207
+ complete: requestComplete
182
208
  });
183
209
  } else {
184
210
  var xhr = new XMLHttpRequest();
185
211
  xhr.open("GET", url, true);
186
212
  xhr.setRequestHeader("Content-Type", "application/json");
187
213
  xhr.onload = function () {
214
+ requestComplete();
188
215
  if (xhr.status === 200) {
189
216
  success(JSON.parse(xhr.responseText), xhr.statusText, xhr);
190
217
  } else {
@@ -204,18 +231,79 @@
204
231
  }
205
232
  }
206
233
 
207
- function fetchDataSource(chart, callback) {
208
- if (typeof chart.dataSource === "string") {
209
- getJSON(chart.element, chart.dataSource, function (data, textStatus, jqXHR) {
210
- chart.data = data;
234
+ function fetchDataSource(chart, callback, dataSource) {
235
+ if (typeof dataSource === "string") {
236
+ pushRequest(chart.element, dataSource, function (data, textStatus, jqXHR) {
237
+ chart.rawData = data;
211
238
  errorCatcher(chart, callback);
212
239
  });
213
240
  } else {
214
- chart.data = chart.dataSource;
241
+ chart.rawData = dataSource;
215
242
  errorCatcher(chart, callback);
216
243
  }
217
244
  }
218
245
 
246
+ function addDownloadButton(chart) {
247
+ var element = chart.element;
248
+ var link = document.createElement("a");
249
+ link.download = chart.options.download === true ? "chart.png" : chart.options.download; // http://caniuse.com/download
250
+ link.style.position = "absolute";
251
+ link.style.top = "20px";
252
+ link.style.right = "20px";
253
+ link.style.zIndex = 1000;
254
+ link.style.lineHeight = "20px";
255
+ link.target = "_blank"; // for safari
256
+ var image = document.createElement("img");
257
+ image.alt = "Download";
258
+ image.style.border = "none";
259
+ // icon from font-awesome
260
+ // http://fa2png.io/
261
+ image.src = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAUCAMAAAC6V+0/AAABCFBMVEUAAADMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMywEsqxAAAAV3RSTlMAAQIDBggJCgsMDQ4PERQaHB0eISIjJCouLzE0OTo/QUJHSUpLTU5PUllhYmltcHh5foWLjI+SlaCio6atr7S1t7m6vsHHyM7R2tze5Obo7fHz9ff5+/1hlxK2AAAA30lEQVQYGUXBhVYCQQBA0TdYWAt2d3d3YWAHyur7/z9xgD16Lw0DW+XKx+1GgX+FRzM3HWQWrHl5N/oapW5RPe0PkBu+UYeICvozTWZVK23Ao04B79oJrOsJDOoxkZoQPWgX29pHpCZEk7rEvQYiNSFq1UMqvlCjJkRBS1R8hb00Vb/TajtBL7nTHE1X1vyMQF732dQhyF2o6SAwrzP06iUQzvwsArlnzcOdrgBhJyHa1QOgO9U1GsKuvjUTjavliZYQ8nNPapG6sap/3nrIdJ6bOWzmX/fy0XVpfzZP3S8OJT3g9EEiJwAAAABJRU5ErkJggg==";
262
+ link.appendChild(image);
263
+ element.style.position = "relative";
264
+
265
+ chart.downloadAttached = true;
266
+
267
+ // mouseenter
268
+ addEvent(element, "mouseover", function(e) {
269
+ var related = e.relatedTarget;
270
+ // check download option again to ensure it wasn't changed
271
+ if (!related || (related !== this && !childOf(this, related)) && chart.options.download) {
272
+ link.href = chart.toImage();
273
+ element.appendChild(link);
274
+ }
275
+ });
276
+
277
+ // mouseleave
278
+ addEvent(element, "mouseout", function(e) {
279
+ var related = e.relatedTarget;
280
+ if (!related || (related !== this && !childOf(this, related))) {
281
+ if (link.parentNode) {
282
+ link.parentNode.removeChild(link);
283
+ }
284
+ }
285
+ });
286
+ }
287
+
288
+ // http://stackoverflow.com/questions/10149963/adding-event-listener-cross-browser
289
+ function addEvent(elem, event, fn) {
290
+ if (elem.addEventListener) {
291
+ elem.addEventListener(event, fn, false);
292
+ } else {
293
+ elem.attachEvent("on" + event, function() {
294
+ // set the this pointer same as addEventListener when fn is called
295
+ return(fn.call(elem, window.event));
296
+ });
297
+ }
298
+ }
299
+
300
+ // https://gist.github.com/shawnbot/4166283
301
+ function childOf(p, c) {
302
+ if (p === c) return false;
303
+ while (c && c !== p) c = c.parentNode;
304
+ return c === p;
305
+ }
306
+
219
307
  // type conversions
220
308
 
221
309
  function toStr(n) {
@@ -263,6 +351,10 @@
263
351
  return a[0].getTime() - b[0].getTime();
264
352
  }
265
353
 
354
+ function sortByNumberSeries(a, b) {
355
+ return a[0] - b[0];
356
+ }
357
+
266
358
  function sortByNumber(a, b) {
267
359
  return a - b;
268
360
  }
@@ -318,8 +410,25 @@
318
410
  }
319
411
  };
320
412
 
321
- var hideLegend = function (options) {
322
- options.legend.enabled = false;
413
+ var hideLegend = function (options, legend, hideLegend) {
414
+ if (legend !== undefined) {
415
+ options.legend.enabled = !!legend;
416
+ 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
+ }
424
+ }
425
+ } else if (hideLegend) {
426
+ options.legend.enabled = false;
427
+ }
428
+ };
429
+
430
+ var setTitle = function (options, title) {
431
+ options.title.text = title;
323
432
  };
324
433
 
325
434
  var setMin = function (options, min) {
@@ -342,7 +451,7 @@
342
451
  options.yAxis.title.text = title;
343
452
  };
344
453
 
345
- var jsOptions = jsOptionsFunc(defaultOptions, hideLegend, setMin, setMax, setStacked, setXtitle, setYtitle);
454
+ var jsOptions = jsOptionsFunc(defaultOptions, hideLegend, setTitle, setMin, setMax, setStacked, setXtitle, setYtitle);
346
455
 
347
456
  this.renderLineChart = function (chart, chartType) {
348
457
  chartType = chartType || "spline";
@@ -353,6 +462,9 @@
353
462
  areaspline: {
354
463
  stacking: "normal"
355
464
  },
465
+ area: {
466
+ stacking: "normal"
467
+ },
356
468
  series: {
357
469
  marker: {
358
470
  enabled: false
@@ -361,8 +473,17 @@
361
473
  }
362
474
  };
363
475
  }
364
- var options = jsOptions(chart.data, chart.options, chartOptions), data, i, j;
365
- options.xAxis.type = chart.options.discrete ? "category" : "datetime";
476
+
477
+ if (chart.options.curve === false) {
478
+ if (chartType === "areaspline") {
479
+ chartType = "area";
480
+ } else if (chartType === "spline") {
481
+ chartType = "line";
482
+ }
483
+ }
484
+
485
+ var options = jsOptions(chart, chart.options, chartOptions), data, i, j;
486
+ options.xAxis.type = chart.discrete ? "category" : "datetime";
366
487
  if (!options.chart.type) {
367
488
  options.chart.type = chartType;
368
489
  }
@@ -371,45 +492,61 @@
371
492
  var series = chart.data;
372
493
  for (i = 0; i < series.length; i++) {
373
494
  data = series[i].data;
374
- if (!chart.options.discrete) {
495
+ if (!chart.discrete) {
375
496
  for (j = 0; j < data.length; j++) {
376
497
  data[j][0] = data[j][0].getTime();
377
498
  }
378
499
  }
379
500
  series[i].marker = {symbol: "circle"};
501
+ if (chart.options.points === false) {
502
+ series[i].marker.enabled = false;
503
+ }
380
504
  }
381
505
  options.series = series;
382
- new Highcharts.Chart(options);
506
+ chart.chart = new Highcharts.Chart(options);
383
507
  };
384
508
 
385
509
  this.renderScatterChart = function (chart) {
386
510
  var chartOptions = {};
387
- var options = jsOptions(chart.data, chart.options, chartOptions);
511
+ var options = jsOptions(chart, chart.options, chartOptions);
388
512
  options.chart.type = "scatter";
389
513
  options.chart.renderTo = chart.element.id;
390
514
  options.series = chart.data;
391
- new Highcharts.Chart(options);
515
+ chart.chart = new Highcharts.Chart(options);
392
516
  };
393
517
 
394
518
  this.renderPieChart = function (chart) {
395
- var chartOptions = {};
519
+ var chartOptions = merge(defaultOptions, {});
520
+
396
521
  if (chart.options.colors) {
397
522
  chartOptions.colors = chart.options.colors;
398
523
  }
399
- var options = merge(merge(defaultOptions, chartOptions), chart.options.library || {});
524
+ if (chart.options.donut) {
525
+ chartOptions.plotOptions = {pie: {innerSize: "50%"}};
526
+ }
527
+
528
+ if ("legend" in chart.options) {
529
+ hideLegend(chartOptions, chart.options.legend);
530
+ }
531
+
532
+ if (chart.options.title) {
533
+ setTitle(chartOptions, chart.options.title);
534
+ }
535
+
536
+ var options = merge(chartOptions, chart.options.library || {});
400
537
  options.chart.renderTo = chart.element.id;
401
538
  options.series = [{
402
539
  type: "pie",
403
540
  name: chart.options.label || "Value",
404
541
  data: chart.data
405
542
  }];
406
- new Highcharts.Chart(options);
543
+ chart.chart = new Highcharts.Chart(options);
407
544
  };
408
545
 
409
546
  this.renderColumnChart = function (chart, chartType) {
410
547
  chartType = chartType || "column";
411
548
  var series = chart.data;
412
- var options = jsOptions(series, chart.options), i, j, s, d, rows = [];
549
+ var options = jsOptions(chart, chart.options), i, j, s, d, rows = [], categories = [];
413
550
  options.chart.type = chartType;
414
551
  options.chart.renderTo = chart.element.id;
415
552
 
@@ -420,17 +557,16 @@
420
557
  d = s.data[j];
421
558
  if (!rows[d[0]]) {
422
559
  rows[d[0]] = new Array(series.length);
560
+ categories.push(d[0]);
423
561
  }
424
562
  rows[d[0]][i] = d[1];
425
563
  }
426
564
  }
427
565
 
428
- var categories = [];
429
- for (i in rows) {
430
- if (rows.hasOwnProperty(i)) {
431
- categories.push(i);
432
- }
566
+ if (chart.options.xtype === "number") {
567
+ categories.sort(sortByNumber);
433
568
  }
569
+
434
570
  options.xAxis.categories = categories;
435
571
 
436
572
  var newSeries = [];
@@ -447,7 +583,7 @@
447
583
  }
448
584
  options.series = newSeries;
449
585
 
450
- new Highcharts.Chart(options);
586
+ chart.chart = new Highcharts.Chart(options);
451
587
  };
452
588
 
453
589
  var self = this;
@@ -557,8 +693,25 @@
557
693
  }
558
694
  };
559
695
 
560
- var hideLegend = function (options) {
561
- options.legend.position = "none";
696
+ var hideLegend = function (options, legend, hideLegend) {
697
+ if (legend !== undefined) {
698
+ var position;
699
+ if (!legend) {
700
+ position = "none";
701
+ } else if (legend === true) {
702
+ position = "right";
703
+ } else {
704
+ position = legend;
705
+ }
706
+ options.legend.position = position;
707
+ } else if (hideLegend) {
708
+ options.legend.position = "none";
709
+ }
710
+ };
711
+
712
+ var setTitle = function (options, title) {
713
+ options.title = title;
714
+ options.titleTextStyle = {color: "#333", fontSize: "20px"};
562
715
  };
563
716
 
564
717
  var setMin = function (options, min) {
@@ -591,11 +744,11 @@
591
744
  options.vAxis.titleTextStyle.italic = false;
592
745
  };
593
746
 
594
- var jsOptions = jsOptionsFunc(defaultOptions, hideLegend, setMin, setMax, setStacked, setXtitle, setYtitle);
747
+ var jsOptions = jsOptionsFunc(defaultOptions, hideLegend, setTitle, setMin, setMax, setStacked, setXtitle, setYtitle);
595
748
 
596
749
  // cant use object as key
597
- var createDataTable = function (series, columnType) {
598
- var i, j, s, d, key, rows = [];
750
+ var createDataTable = function (series, columnType, xtype) {
751
+ var i, j, s, d, key, rows = [], sortedLabels = [];
599
752
  for (i = 0; i < series.length; i++) {
600
753
  s = series[i];
601
754
 
@@ -604,6 +757,7 @@
604
757
  key = (columnType === "datetime") ? d[0].getTime() : d[0];
605
758
  if (!rows[key]) {
606
759
  rows[key] = new Array(series.length);
760
+ sortedLabels.push(key);
607
761
  }
608
762
  rows[key][i] = toFloat(d[1]);
609
763
  }
@@ -612,21 +766,30 @@
612
766
  var rows2 = [];
613
767
  var day = true;
614
768
  var value;
615
- for (i in rows) {
616
- if (rows.hasOwnProperty(i)) {
617
- if (columnType === "datetime") {
618
- value = new Date(toFloat(i));
619
- day = day && isDay(value);
620
- } else if (columnType === "number") {
621
- value = toFloat(i);
622
- } else {
623
- value = i;
624
- }
625
- rows2.push([value].concat(rows[i]));
769
+ for (var j = 0; j < sortedLabels.length; j++) {
770
+ var i = sortedLabels[j];
771
+ if (columnType === "datetime") {
772
+ value = new Date(toFloat(i));
773
+ day = day && isDay(value);
774
+ } else if (columnType === "number") {
775
+ value = toFloat(i);
776
+ } else {
777
+ value = i;
626
778
  }
779
+ rows2.push([value].concat(rows[i]));
627
780
  }
628
781
  if (columnType === "datetime") {
629
782
  rows2.sort(sortByTime);
783
+ } else if (columnType === "number") {
784
+ rows2.sort(sortByNumberSeries);
785
+ }
786
+
787
+ if (xtype === "number") {
788
+ rows2.sort(sortByNumberSeries);
789
+
790
+ for (var i = 0; i < rows2.length; i++) {
791
+ rows2[i][0] = toStr(rows2[i][0]);
792
+ }
630
793
  }
631
794
 
632
795
  // create datatable
@@ -652,8 +815,22 @@
652
815
 
653
816
  this.renderLineChart = function (chart) {
654
817
  waitForLoaded(function () {
655
- var options = jsOptions(chart.data, chart.options);
656
- var data = createDataTable(chart.data, chart.options.discrete ? "string" : "datetime");
818
+ var chartOptions = {};
819
+
820
+ if (chart.options.curve === false) {
821
+ chartOptions.curveType = "none";
822
+ }
823
+
824
+ if (chart.options.points === false) {
825
+ chartOptions.pointSize = 0;
826
+ }
827
+
828
+ var options = jsOptions(chart, chart.options, chartOptions);
829
+ var columnType = chart.discrete ? "string" : "datetime";
830
+ if (chart.options.xtype === "number") {
831
+ columnType = "number";
832
+ }
833
+ var data = createDataTable(chart.data, columnType);
657
834
  chart.chart = new google.visualization.LineChart(chart.element);
658
835
  resize(function () {
659
836
  chart.chart.draw(data, options);
@@ -667,11 +844,21 @@
667
844
  chartArea: {
668
845
  top: "10%",
669
846
  height: "80%"
670
- }
847
+ },
848
+ legend: {}
671
849
  };
672
850
  if (chart.options.colors) {
673
851
  chartOptions.colors = chart.options.colors;
674
852
  }
853
+ if (chart.options.donut) {
854
+ chartOptions.pieHole = 0.5;
855
+ }
856
+ if ("legend" in chart.options) {
857
+ hideLegend(chartOptions, chart.options.legend);
858
+ }
859
+ if (chart.options.title) {
860
+ setTitle(chartOptions, chart.options.title);
861
+ }
675
862
  var options = merge(merge(defaultOptions, chartOptions), chart.options.library || {});
676
863
 
677
864
  var data = new google.visualization.DataTable();
@@ -688,8 +875,8 @@
688
875
 
689
876
  this.renderColumnChart = function (chart) {
690
877
  waitForLoaded(function () {
691
- var options = jsOptions(chart.data, chart.options);
692
- var data = createDataTable(chart.data, "string");
878
+ var options = jsOptions(chart, chart.options);
879
+ var data = createDataTable(chart.data, "string", chart.options.xtype);
693
880
  chart.chart = new google.visualization.ColumnChart(chart.element);
694
881
  resize(function () {
695
882
  chart.chart.draw(data, options);
@@ -706,8 +893,8 @@
706
893
  }
707
894
  }
708
895
  };
709
- var options = jsOptionsFunc(defaultOptions, hideLegend, setBarMin, setBarMax, setStacked, setXtitle, setYtitle)(chart.data, chart.options, chartOptions);
710
- var data = createDataTable(chart.data, "string");
896
+ var options = jsOptionsFunc(defaultOptions, hideLegend, setTitle, setBarMin, setBarMax, setStacked, setXtitle, setYtitle)(chart, chart.options, chartOptions);
897
+ var data = createDataTable(chart.data, "string", chart.options.xtype);
711
898
  chart.chart = new google.visualization.BarChart(chart.element);
712
899
  resize(function () {
713
900
  chart.chart.draw(data, options);
@@ -722,8 +909,13 @@
722
909
  pointSize: 0,
723
910
  areaOpacity: 0.5
724
911
  };
725
- var options = jsOptions(chart.data, chart.options, chartOptions);
726
- var data = createDataTable(chart.data, chart.options.discrete ? "string" : "datetime");
912
+
913
+ var options = jsOptions(chart, chart.options, chartOptions);
914
+ var columnType = chart.discrete ? "string" : "datetime";
915
+ if (chart.options.xtype === "number") {
916
+ columnType = "number";
917
+ }
918
+ var data = createDataTable(chart.data, columnType);
727
919
  chart.chart = new google.visualization.AreaChart(chart.element);
728
920
  resize(function () {
729
921
  chart.chart.draw(data, options);
@@ -756,8 +948,25 @@
756
948
  this.renderScatterChart = function (chart) {
757
949
  waitForLoaded(function () {
758
950
  var chartOptions = {};
759
- var options = jsOptions(chart.data, chart.options, chartOptions);
760
- var data = createDataTable(chart.data, "number");
951
+ var options = jsOptions(chart, chart.options, chartOptions);
952
+
953
+ var series = chart.data, rows2 = [], i, j, data, d;
954
+ for (i = 0; i < series.length; i++) {
955
+ d = series[i].data;
956
+ for (j = 0; j < d.length; j++) {
957
+ var row = new Array(series.length + 1);
958
+ row[0] = d[j][0];
959
+ row[i + 1] = d[j][1];
960
+ rows2.push(row);
961
+ }
962
+ }
963
+
964
+ var data = new google.visualization.DataTable();
965
+ data.addColumn("number", "");
966
+ for (i = 0; i < series.length; i++) {
967
+ data.addColumn("number", series[i].name);
968
+ }
969
+ data.addRows(rows2);
761
970
 
762
971
  chart.chart = new google.visualization.ScatterChart(chart.element);
763
972
  resize(function () {
@@ -803,7 +1012,12 @@
803
1012
 
804
1013
  var baseOptions = {
805
1014
  maintainAspectRatio: false,
806
- animation: false
1015
+ animation: false,
1016
+ tooltips: {
1017
+ displayColors: false
1018
+ },
1019
+ legend: {},
1020
+ title: {fontSize: 20, fontColor: "#333"}
807
1021
  };
808
1022
 
809
1023
  var defaultOptions = {
@@ -834,8 +1048,7 @@
834
1048
  ticks: {}
835
1049
  }
836
1050
  ]
837
- },
838
- legend: {}
1051
+ }
839
1052
  };
840
1053
 
841
1054
  // http://there4.io/2012/05/02/google-chart-color-list/
@@ -845,28 +1058,40 @@
845
1058
  "#6633CC", "#E67300", "#8B0707", "#329262", "#5574A6", "#3B3EAC"
846
1059
  ];
847
1060
 
848
- var hideLegend = function (options) {
849
- options.legend.display = false;
1061
+ var hideLegend = function (options, legend, hideLegend) {
1062
+ if (legend !== undefined) {
1063
+ options.legend.display = !!legend;
1064
+ if (legend && legend !== true) {
1065
+ options.legend.position = legend;
1066
+ }
1067
+ } else if (hideLegend) {
1068
+ options.legend.display = false;
1069
+ }
1070
+ };
1071
+
1072
+ var setTitle = function (options, title) {
1073
+ options.title.display = true;
1074
+ options.title.text = title;
850
1075
  };
851
1076
 
852
1077
  var setMin = function (options, min) {
853
1078
  if (min !== null) {
854
- options.scales.yAxes[0].ticks.min = min;
1079
+ options.scales.yAxes[0].ticks.min = toFloat(min);
855
1080
  }
856
1081
  };
857
1082
 
858
1083
  var setMax = function (options, max) {
859
- options.scales.yAxes[0].ticks.max = max;
1084
+ options.scales.yAxes[0].ticks.max = toFloat(max);
860
1085
  };
861
1086
 
862
1087
  var setBarMin = function (options, min) {
863
1088
  if (min !== null) {
864
- options.scales.xAxes[0].ticks.min = min;
1089
+ options.scales.xAxes[0].ticks.min = toFloat(min);
865
1090
  }
866
1091
  };
867
1092
 
868
1093
  var setBarMax = function (options, max) {
869
- options.scales.xAxes[0].ticks.max = max;
1094
+ options.scales.xAxes[0].ticks.max = toFloat(max);
870
1095
  };
871
1096
 
872
1097
  var setStacked = function (options, stacked) {
@@ -885,9 +1110,13 @@
885
1110
  };
886
1111
 
887
1112
  var drawChart = function(chart, type, data, options) {
888
- chart.element.innerHTML = "<canvas></canvas>";
889
- var ctx = chart.element.getElementsByTagName("CANVAS")[0];
1113
+ if (chart.chart) {
1114
+ chart.chart.destroy();
1115
+ } else {
1116
+ chart.element.innerHTML = "<canvas></canvas>";
1117
+ }
890
1118
 
1119
+ var ctx = chart.element.getElementsByTagName("CANVAS")[0];
891
1120
  chart.chart = new Chart(ctx, {
892
1121
  type: type,
893
1122
  data: data,
@@ -916,7 +1145,7 @@
916
1145
  };
917
1146
  };
918
1147
 
919
- var jsOptions = jsOptionsFunc(merge(baseOptions, defaultOptions), hideLegend, setMin, setMax, setStacked, setXtitle, setYtitle);
1148
+ var jsOptions = jsOptionsFunc(merge(baseOptions, defaultOptions), hideLegend, setTitle, setMin, setMax, setStacked, setXtitle, setYtitle);
920
1149
 
921
1150
  var createDataTable = function (chart, options, chartType) {
922
1151
  var datasets = [];
@@ -931,7 +1160,7 @@
931
1160
  var year = true;
932
1161
  var hour = true;
933
1162
  var minute = true;
934
- var detectType = (chartType === "line" || chartType === "area") && !chart.options.discrete;
1163
+ var detectType = (chartType === "line" || chartType === "area") && !chart.discrete;
935
1164
 
936
1165
  var series = chart.data;
937
1166
 
@@ -954,7 +1183,7 @@
954
1183
  }
955
1184
  }
956
1185
 
957
- if (detectType) {
1186
+ if (detectType || chart.options.xtype === "number") {
958
1187
  sortedLabels.sort(sortByNumber);
959
1188
  }
960
1189
 
@@ -984,25 +1213,36 @@
984
1213
  }
985
1214
  labels.push(value);
986
1215
  for (j = 0; j < series.length; j++) {
987
- rows2[j].push(rows[i][j]);
1216
+ // Chart.js doesn't like undefined
1217
+ rows2[j].push(rows[i][j] === undefined ? null : rows[i][j]);
988
1218
  }
989
1219
  }
990
1220
 
991
1221
  for (i = 0; i < series.length; i++) {
992
1222
  s = series[i];
993
1223
 
994
- var backgroundColor = chartType !== "line" ? addOpacity(colors[i], 0.5) : colors[i];
1224
+ var color = s.color || colors[i];
1225
+ var backgroundColor = chartType !== "line" ? addOpacity(color, 0.5) : color;
995
1226
 
996
1227
  var dataset = {
997
1228
  label: s.name,
998
1229
  data: rows2[i],
999
1230
  fill: chartType === "area",
1000
- borderColor: colors[i],
1231
+ borderColor: color,
1001
1232
  backgroundColor: backgroundColor,
1002
- pointBackgroundColor: colors[i],
1233
+ pointBackgroundColor: color,
1003
1234
  borderWidth: 2
1004
1235
  };
1005
1236
 
1237
+ if (chart.options.curve === false) {
1238
+ dataset.lineTension = 0;
1239
+ }
1240
+
1241
+ if (chart.options.points === false) {
1242
+ dataset.pointRadius = 0;
1243
+ dataset.pointHitRadius = 5;
1244
+ }
1245
+
1006
1246
  datasets.push(merge(dataset, s.library || {}));
1007
1247
  }
1008
1248
 
@@ -1071,27 +1311,44 @@
1071
1311
  };
1072
1312
 
1073
1313
  this.renderLineChart = function (chart, chartType) {
1074
- var areaOptions = {};
1314
+ if (chart.options.xtype === "number") {
1315
+ return self.renderScatterChart(chart, chartType, true);
1316
+ }
1317
+
1318
+ var chartOptions = {};
1075
1319
  if (chartType === "area") {
1076
1320
  // TODO fix area stacked
1077
- // areaOptions.stacked = true;
1321
+ // chartOptions.stacked = true;
1078
1322
  }
1079
1323
  // fix for https://github.com/chartjs/Chart.js/issues/2441
1080
1324
  if (!chart.options.max && allZeros(chart.data)) {
1081
- chart.options.max = 1;
1325
+ chartOptions.max = 1;
1082
1326
  }
1083
1327
 
1084
- var options = jsOptions(chart.data, merge(areaOptions, chart.options));
1328
+ var options = jsOptions(chart, merge(chartOptions, chart.options));
1085
1329
 
1086
1330
  var data = createDataTable(chart, options, chartType || "line");
1087
1331
 
1088
- options.scales.xAxes[0].type = chart.options.discrete ? "category" : "time";
1332
+ options.scales.xAxes[0].type = chart.discrete ? "category" : "time";
1089
1333
 
1090
1334
  drawChart(chart, "line", data, options);
1091
1335
  };
1092
1336
 
1093
1337
  this.renderPieChart = function (chart) {
1094
- var options = merge(baseOptions, chart.options.library || {});
1338
+ var options = merge({}, baseOptions);
1339
+ if (chart.options.donut) {
1340
+ options.cutoutPercentage = 50;
1341
+ }
1342
+
1343
+ if ("legend" in chart.options) {
1344
+ hideLegend(options, chart.options.legend);
1345
+ }
1346
+
1347
+ if (chart.options.title) {
1348
+ setTitle(options, chart.options.title);
1349
+ }
1350
+
1351
+ options = merge(options, chart.options.library || {});
1095
1352
 
1096
1353
  var labels = [];
1097
1354
  var values = [];
@@ -1117,9 +1374,9 @@
1117
1374
  this.renderColumnChart = function (chart, chartType) {
1118
1375
  var options;
1119
1376
  if (chartType === "bar") {
1120
- options = jsOptionsFunc(merge(baseOptions, defaultOptions), hideLegend, setBarMin, setBarMax, setStacked, setXtitle, setYtitle)(chart.data, chart.options);
1377
+ options = jsOptionsFunc(merge(baseOptions, defaultOptions), hideLegend, setTitle, setBarMin, setBarMax, setStacked, setXtitle, setYtitle)(chart, chart.options);
1121
1378
  } else {
1122
- options = jsOptions(chart.data, chart.options);
1379
+ options = jsOptions(chart, chart.options);
1123
1380
  }
1124
1381
  var data = createDataTable(chart, options, "column");
1125
1382
  setLabelSize(chart, data, options);
@@ -1136,8 +1393,10 @@
1136
1393
  self.renderColumnChart(chart, "bar");
1137
1394
  };
1138
1395
 
1139
- this.renderScatterChart = function (chart) {
1140
- var options = jsOptions(chart.data, chart.options);
1396
+ this.renderScatterChart = function (chart, chartType, lineChart) {
1397
+ chartType = chartType || "line";
1398
+
1399
+ var options = jsOptions(chart, chart.options);
1141
1400
 
1142
1401
  var colors = chart.options.colors || defaultColors;
1143
1402
 
@@ -1147,28 +1406,44 @@
1147
1406
  var s = series[i];
1148
1407
  var d = [];
1149
1408
  for (var j = 0; j < s.data.length; j++) {
1150
- d.push({
1409
+ var point = {
1151
1410
  x: toFloat(s.data[j][0]),
1152
1411
  y: toFloat(s.data[j][1])
1153
- });
1412
+ };
1413
+ if (chartType === "bubble") {
1414
+ point.r = toFloat(s.data[j][2]);
1415
+ }
1416
+ d.push(point);
1154
1417
  }
1155
1418
 
1419
+ var color = s.color || colors[i];
1420
+ var backgroundColor = chartType === "area" ? addOpacity(color, 0.5) : color;
1421
+
1156
1422
  datasets.push({
1157
1423
  label: s.name,
1158
- showLine: false,
1424
+ showLine: lineChart || false,
1159
1425
  data: d,
1160
- borderColor: colors[i],
1161
- backgroundColor: colors[i],
1162
- pointBackgroundColor: colors[i]
1426
+ borderColor: color,
1427
+ backgroundColor: backgroundColor,
1428
+ pointBackgroundColor: color,
1429
+ fill: chartType === "area"
1163
1430
  })
1164
1431
  }
1165
1432
 
1433
+ if (chartType === "area") {
1434
+ chartType = "line";
1435
+ }
1436
+
1166
1437
  var data = {datasets: datasets};
1167
1438
 
1168
1439
  options.scales.xAxes[0].type = "linear";
1169
1440
  options.scales.xAxes[0].position = "bottom";
1170
1441
 
1171
- drawChart(chart, "line", data, options);
1442
+ drawChart(chart, chartType, data, options);
1443
+ };
1444
+
1445
+ this.renderBubbleChart = function (chart) {
1446
+ this.renderScatterChart(chart, "bubble");
1172
1447
  };
1173
1448
  };
1174
1449
 
@@ -1176,9 +1451,16 @@
1176
1451
  }
1177
1452
  }
1178
1453
 
1454
+ function renderChart(chartType, chart) {
1455
+ callAdapter(chartType, chart);
1456
+ if (chart.options.download && !chart.downloadAttached && chart.adapter === "chartjs") {
1457
+ addDownloadButton(chart);
1458
+ }
1459
+ }
1460
+
1179
1461
  // TODO remove chartType if cross-browser way
1180
1462
  // to get the name of the chart class
1181
- function renderChart(chartType, chart) {
1463
+ function callAdapter(chartType, chart) {
1182
1464
  var i, adapter, fnName, adapterName;
1183
1465
  fnName = "render" + chartType;
1184
1466
  adapterName = chart.options.adapter;
@@ -1188,6 +1470,7 @@
1188
1470
  for (i = 0; i < adapters.length; i++) {
1189
1471
  adapter = adapters[i];
1190
1472
  if ((!adapterName || adapterName === adapter.name) && isFunction(adapter[fnName])) {
1473
+ chart.adapter = adapter.name;
1191
1474
  return adapter[fnName](chart);
1192
1475
  }
1193
1476
  }
@@ -1210,11 +1493,17 @@
1210
1493
  var formatSeriesData = function (data, keyType) {
1211
1494
  var r = [], key, j;
1212
1495
  for (j = 0; j < data.length; j++) {
1213
- key = toFormattedKey(data[j][0], keyType);
1214
- r.push([key, toFloat(data[j][1])]);
1496
+ if (keyType === "bubble") {
1497
+ r.push([toFloat(data[j][0]), toFloat(data[j][1]), toFloat(data[j][2])]);
1498
+ } else {
1499
+ key = toFormattedKey(data[j][0], keyType);
1500
+ r.push([key, toFloat(data[j][1])]);
1501
+ }
1215
1502
  }
1216
1503
  if (keyType === "datetime") {
1217
1504
  r.sort(sortByTime);
1505
+ } else if (keyType === "number") {
1506
+ r.sort(sortByNumberSeries);
1218
1507
  }
1219
1508
  return r;
1220
1509
  };
@@ -1273,22 +1562,30 @@
1273
1562
  return false;
1274
1563
  }
1275
1564
 
1276
- function processSeries(series, opts, keyType) {
1565
+ function processSeries(chart, keyType) {
1277
1566
  var i;
1278
1567
 
1568
+ var opts = chart.options;
1569
+ var series = chart.rawData;
1570
+
1279
1571
  // see if one series or multiple
1280
1572
  if (!isArray(series) || typeof series[0] !== "object" || isArray(series[0])) {
1281
1573
  series = [{name: opts.label || "Value", data: series}];
1282
- opts.hideLegend = true;
1574
+ chart.hideLegend = true;
1283
1575
  } else {
1284
- opts.hideLegend = false;
1576
+ chart.hideLegend = false;
1285
1577
  }
1286
- if ((opts.discrete === null || opts.discrete === undefined)) {
1287
- opts.discrete = detectDiscrete(series);
1578
+ if ((opts.discrete === null || opts.discrete === undefined) && keyType !== "bubble" && keyType !== "number") {
1579
+ chart.discrete = detectDiscrete(series);
1580
+ } else {
1581
+ chart.discrete = opts.discrete;
1288
1582
  }
1289
- if (opts.discrete) {
1583
+ if (chart.discrete) {
1290
1584
  keyType = "string";
1291
1585
  }
1586
+ if (chart.options.xtype) {
1587
+ keyType = chart.options.xtype;
1588
+ }
1292
1589
 
1293
1590
  // right format
1294
1591
  for (i = 0; i < series.length; i++) {
@@ -1298,17 +1595,17 @@
1298
1595
  return series;
1299
1596
  }
1300
1597
 
1301
- function processSimple(data) {
1302
- var perfectData = toArr(data), i;
1598
+ function processSimple(chart) {
1599
+ var perfectData = toArr(chart.rawData), i;
1303
1600
  for (i = 0; i < perfectData.length; i++) {
1304
1601
  perfectData[i] = [toStr(perfectData[i][0]), toFloat(perfectData[i][1])];
1305
1602
  }
1306
1603
  return perfectData;
1307
1604
  }
1308
1605
 
1309
- function processTime(data)
1606
+ function processTime(chart)
1310
1607
  {
1311
- var i;
1608
+ var i, data = chart.rawData;
1312
1609
  for (i = 0; i < data.length; i++) {
1313
1610
  data[i][1] = toDate(data[i][1]);
1314
1611
  data[i][2] = toDate(data[i][2]);
@@ -1317,46 +1614,30 @@
1317
1614
  }
1318
1615
 
1319
1616
  function processLineData(chart) {
1320
- chart.data = processSeries(chart.data, chart.options, "datetime");
1321
- renderChart("LineChart", chart);
1617
+ return processSeries(chart, "datetime");
1322
1618
  }
1323
1619
 
1324
1620
  function processColumnData(chart) {
1325
- chart.data = processSeries(chart.data, chart.options, "string");
1326
- renderChart("ColumnChart", chart);
1327
- }
1328
-
1329
- function processPieData(chart) {
1330
- chart.data = processSimple(chart.data);
1331
- renderChart("PieChart", chart);
1621
+ return processSeries(chart, "string");
1332
1622
  }
1333
1623
 
1334
1624
  function processBarData(chart) {
1335
- chart.data = processSeries(chart.data, chart.options, "string");
1336
- renderChart("BarChart", chart);
1625
+ return processSeries(chart, "string");
1337
1626
  }
1338
1627
 
1339
1628
  function processAreaData(chart) {
1340
- chart.data = processSeries(chart.data, chart.options, "datetime");
1341
- renderChart("AreaChart", chart);
1342
- }
1343
-
1344
- function processGeoData(chart) {
1345
- chart.data = processSimple(chart.data);
1346
- renderChart("GeoChart", chart);
1629
+ return processSeries(chart, "datetime");
1347
1630
  }
1348
1631
 
1349
1632
  function processScatterData(chart) {
1350
- chart.data = processSeries(chart.data, chart.options, "number");
1351
- renderChart("ScatterChart", chart);
1633
+ return processSeries(chart, "number");
1352
1634
  }
1353
1635
 
1354
- function processTimelineData(chart) {
1355
- chart.data = processTime(chart.data);
1356
- renderChart("Timeline", chart);
1636
+ function processBubbleData(chart) {
1637
+ return processSeries(chart, "bubble");
1357
1638
  }
1358
1639
 
1359
- function setElement(chart, element, dataSource, opts, callback) {
1640
+ function createChart(chartType, chart, element, dataSource, opts, processData) {
1360
1641
  var elementId;
1361
1642
  if (typeof element === "string") {
1362
1643
  elementId = element;
@@ -1365,51 +1646,119 @@
1365
1646
  throw new Error("No element with id " + elementId);
1366
1647
  }
1367
1648
  }
1649
+
1368
1650
  chart.element = element;
1369
- chart.options = opts || {};
1651
+ opts = merge(Chartkick.options, opts || {});
1652
+ chart.options = opts;
1370
1653
  chart.dataSource = dataSource;
1654
+
1655
+ if (!processData) {
1656
+ processData = function (chart) {
1657
+ return chart.rawData;
1658
+ }
1659
+ }
1660
+
1661
+ // getters
1371
1662
  chart.getElement = function () {
1372
1663
  return element;
1373
1664
  };
1665
+ chart.getDataSource = function () {
1666
+ return chart.dataSource;
1667
+ };
1374
1668
  chart.getData = function () {
1375
1669
  return chart.data;
1376
1670
  };
1377
1671
  chart.getOptions = function () {
1378
- return opts || {};
1672
+ return chart.options;
1379
1673
  };
1380
1674
  chart.getChartObject = function () {
1381
1675
  return chart.chart;
1382
1676
  };
1677
+ chart.getAdapter = function () {
1678
+ return chart.adapter;
1679
+ };
1680
+
1681
+ var callback = function () {
1682
+ chart.data = processData(chart);
1683
+ renderChart(chartType, chart);
1684
+ };
1685
+
1686
+ // functions
1687
+ chart.updateData = function (dataSource, options) {
1688
+ chart.dataSource = dataSource;
1689
+ if (options) {
1690
+ chart.options = merge(Chartkick.options, options);
1691
+ }
1692
+ fetchDataSource(chart, callback, dataSource);
1693
+ };
1694
+ chart.setOptions = function (options) {
1695
+ chart.options = merge(Chartkick.options, options);
1696
+ chart.redraw();
1697
+ };
1698
+ chart.redraw = function() {
1699
+ fetchDataSource(chart, callback, chart.rawData);
1700
+ };
1701
+ chart.refreshData = function () {
1702
+ if (typeof dataSource === "string") {
1703
+ // prevent browser from caching
1704
+ var sep = dataSource.indexOf("?") === -1 ? "?" : "&";
1705
+ var url = dataSource + sep + "_=" + (new Date()).getTime();
1706
+ fetchDataSource(chart, callback, url);
1707
+ }
1708
+ };
1709
+ chart.stopRefresh = function () {
1710
+ if (chart.intervalId) {
1711
+ clearInterval(chart.intervalId);
1712
+ }
1713
+ };
1714
+ chart.toImage = function () {
1715
+ if (chart.adapter === "chartjs") {
1716
+ return chart.chart.toBase64Image();
1717
+ } else {
1718
+ return null;
1719
+ }
1720
+ }
1721
+
1383
1722
  Chartkick.charts[element.id] = chart;
1384
- fetchDataSource(chart, callback);
1723
+
1724
+ fetchDataSource(chart, callback, dataSource);
1725
+
1726
+ if (opts.refresh) {
1727
+ chart.intervalId = setInterval( function () {
1728
+ chart.refreshData();
1729
+ }, opts.refresh * 1000);
1730
+ }
1385
1731
  }
1386
1732
 
1387
1733
  // define classes
1388
1734
 
1389
1735
  Chartkick = {
1390
- LineChart: function (element, dataSource, opts) {
1391
- setElement(this, element, dataSource, opts, processLineData);
1736
+ LineChart: function (element, dataSource, options) {
1737
+ createChart("LineChart", this, element, dataSource, options, processLineData);
1738
+ },
1739
+ PieChart: function (element, dataSource, options) {
1740
+ createChart("PieChart", this, element, dataSource, options, processSimple);
1392
1741
  },
1393
- PieChart: function (element, dataSource, opts) {
1394
- setElement(this, element, dataSource, opts, processPieData);
1742
+ ColumnChart: function (element, dataSource, options) {
1743
+ createChart("ColumnChart", this, element, dataSource, options, processColumnData);
1395
1744
  },
1396
- ColumnChart: function (element, dataSource, opts) {
1397
- setElement(this, element, dataSource, opts, processColumnData);
1745
+ BarChart: function (element, dataSource, options) {
1746
+ createChart("BarChart", this, element, dataSource, options, processBarData);
1398
1747
  },
1399
- BarChart: function (element, dataSource, opts) {
1400
- setElement(this, element, dataSource, opts, processBarData);
1748
+ AreaChart: function (element, dataSource, options) {
1749
+ createChart("AreaChart", this, element, dataSource, options, processAreaData);
1401
1750
  },
1402
- AreaChart: function (element, dataSource, opts) {
1403
- setElement(this, element, dataSource, opts, processAreaData);
1751
+ GeoChart: function (element, dataSource, options) {
1752
+ createChart("GeoChart", this, element, dataSource, options, processSimple);
1404
1753
  },
1405
- GeoChart: function (element, dataSource, opts) {
1406
- setElement(this, element, dataSource, opts, processGeoData);
1754
+ ScatterChart: function (element, dataSource, options) {
1755
+ createChart("ScatterChart", this, element, dataSource, options, processScatterData);
1407
1756
  },
1408
- ScatterChart: function (element, dataSource, opts) {
1409
- setElement(this, element, dataSource, opts, processScatterData);
1757
+ BubbleChart: function (element, dataSource, options) {
1758
+ createChart("BubbleChart", this, element, dataSource, options, processBubbleData);
1410
1759
  },
1411
- Timeline: function (element, dataSource, opts) {
1412
- setElement(this, element, dataSource, opts, processTimelineData);
1760
+ Timeline: function (element, dataSource, options) {
1761
+ createChart("Timeline", this, element, dataSource, options, processTime);
1413
1762
  },
1414
1763
  charts: {},
1415
1764
  configure: function (options) {
@@ -1418,7 +1767,17 @@
1418
1767
  config[key] = options[key];
1419
1768
  }
1420
1769
  }
1421
- }
1770
+ },
1771
+ eachChart: function (callback) {
1772
+ for (var chartId in Chartkick.charts) {
1773
+ if (Chartkick.charts.hasOwnProperty(chartId)) {
1774
+ callback(Chartkick.charts[chartId]);
1775
+ }
1776
+ }
1777
+ },
1778
+ options: {},
1779
+ adapters: adapters,
1780
+ createChart: createChart
1422
1781
  };
1423
1782
 
1424
1783
  if (typeof module === "object" && typeof module.exports === "object") {