visage-app 2.0.5 → 2.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/.gitignore +1 -0
- data/AUTHORS +1 -0
- data/CHANGELOG.md +4 -0
- data/Gemfile.lock +1 -1
- data/README.md +5 -5
- data/Rakefile +1 -1
- data/features/json.feature +10 -6
- data/features/profiles.feature +4 -9
- data/features/step_definitions/cli_steps.rb +2 -2
- data/features/step_definitions/json_steps.rb +26 -3
- data/features/step_definitions/site_steps.rb +0 -1
- data/lib/visage-app.rb +18 -10
- data/lib/visage-app/collectd/json.rb +124 -24
- data/lib/visage-app/profile.rb +13 -7
- data/lib/visage-app/public/javascripts/builder.js +48 -5
- data/lib/visage-app/public/javascripts/graph.js +144 -9
- data/lib/visage-app/public/javascripts/highcharts-mootools-adapter.js +12 -0
- data/lib/visage-app/public/javascripts/highcharts-mootools-adapter.src.js +298 -0
- data/lib/visage-app/public/javascripts/highcharts.js +198 -131
- data/lib/visage-app/public/javascripts/highcharts.src.js +13543 -8724
- data/lib/visage-app/version.rb +1 -1
- data/lib/visage-app/views/builder.haml +2 -32
- data/lib/visage-app/views/builder_form.haml +7 -0
- data/lib/visage-app/views/layout.haml +1 -0
- data/lib/visage-app/views/profile.haml +14 -24
- data/lib/visage-app/views/profiles.haml +8 -5
- metadata +49 -47
data/lib/visage-app/profile.rb
CHANGED
@@ -9,6 +9,7 @@ require 'digest/md5'
|
|
9
9
|
module Visage
|
10
10
|
class Profile
|
11
11
|
attr_reader :options, :selected_hosts, :hosts, :selected_metrics, :metrics,
|
12
|
+
:selected_percentiles, :percentiles,
|
12
13
|
:name, :errors
|
13
14
|
|
14
15
|
def self.old_format?
|
@@ -33,7 +34,8 @@ module Visage
|
|
33
34
|
def self.all(opts={})
|
34
35
|
sort = opts[:sort]
|
35
36
|
profiles = self.load
|
36
|
-
profiles = sort == "name" ? profiles.sort_by {|k,v| v[:profile_name]}.map {|i| i.last } : profiles.values
|
37
|
+
profiles = ((sort == "name") or not sort) ? profiles.sort_by {|k,v| v[:profile_name]}.map {|i| i.last } : profiles.values
|
38
|
+
# FIXME - to sort by creation time we need to save creation time on each profile
|
37
39
|
profiles.map { |prof| self.new(prof) }
|
38
40
|
end
|
39
41
|
|
@@ -41,8 +43,9 @@ module Visage
|
|
41
43
|
@options = opts
|
42
44
|
@options[:url] = @options[:profile_name] ? @options[:profile_name].downcase.gsub(/[^\w]+/, "+") : nil
|
43
45
|
@errors = {}
|
44
|
-
@options[:hosts]
|
45
|
-
@options[:metrics]
|
46
|
+
@options[:hosts] = @options[:hosts].values if @options[:hosts].class == Hash
|
47
|
+
@options[:metrics] = @options[:metrics].values if @options[:metrics].class == Hash
|
48
|
+
@options[:percentiles] = @options[:percentiles].values if @options[:percentiles].class == Hash
|
46
49
|
end
|
47
50
|
|
48
51
|
# Hashed based access to @options.
|
@@ -55,6 +58,7 @@ module Visage
|
|
55
58
|
# Construct record.
|
56
59
|
attrs = { :hosts => @options[:hosts],
|
57
60
|
:metrics => @options[:metrics],
|
61
|
+
:percentiles => @options[:percentiles],
|
58
62
|
:profile_name => @options[:profile_name],
|
59
63
|
:url => @options[:profile_name].downcase.gsub(/[^\w]+/, "+") }
|
60
64
|
|
@@ -78,9 +82,10 @@ module Visage
|
|
78
82
|
end
|
79
83
|
|
80
84
|
def graphs
|
81
|
-
graphs
|
82
|
-
hosts
|
83
|
-
metrics
|
85
|
+
graphs = []
|
86
|
+
hosts = @options[:hosts]
|
87
|
+
metrics = @options[:metrics]
|
88
|
+
percentiles = @options[:percentiles]
|
84
89
|
|
85
90
|
hosts.each do |host|
|
86
91
|
attrs = {}
|
@@ -96,7 +101,8 @@ module Visage
|
|
96
101
|
attrs.each_pair do |plugin, instances|
|
97
102
|
graphs << Visage::Graph.new(:host => host,
|
98
103
|
:plugin => plugin,
|
99
|
-
:instances => instances
|
104
|
+
:instances => instances,
|
105
|
+
:percentiles => percentiles)
|
100
106
|
end
|
101
107
|
end
|
102
108
|
|
@@ -436,6 +436,7 @@ var ChartBuilder = new Class({
|
|
436
436
|
this.searchers = new Object;
|
437
437
|
this.setupHostSearch();
|
438
438
|
this.setupMetricSearch();
|
439
|
+
this.setupPercentileSelection();
|
439
440
|
this.setupShow();
|
440
441
|
|
441
442
|
/* Display graphs if hosts + metrics have been selected */
|
@@ -460,6 +461,33 @@ var ChartBuilder = new Class({
|
|
460
461
|
});
|
461
462
|
this.searchers.metric = searcher;
|
462
463
|
},
|
464
|
+
setupPercentileSelection: function() {
|
465
|
+
var container = this.builder.getElement("div#profile-options div.percentiles");
|
466
|
+
if (this.options.percentiles) {
|
467
|
+
if (this.options.percentiles.length > 0) {
|
468
|
+
this.options.percentile95 = true;
|
469
|
+
}
|
470
|
+
}
|
471
|
+
if (container) {
|
472
|
+
var percentileSelector = new Element('input', {
|
473
|
+
'type': 'checkbox',
|
474
|
+
'id': this.parentElement + '-percentile95',
|
475
|
+
'name': 'percentile_95',
|
476
|
+
'checked': this.options.percentile95,
|
477
|
+
'events': {
|
478
|
+
'click': function() {
|
479
|
+
this.options.percentile95 = !this.options.percentile95
|
480
|
+
}.bind(this)
|
481
|
+
},
|
482
|
+
'styles': {
|
483
|
+
'margin-right': '4px',
|
484
|
+
'cursor': 'pointer'
|
485
|
+
}
|
486
|
+
});
|
487
|
+
container.grab(percentileSelector);
|
488
|
+
}
|
489
|
+
|
490
|
+
},
|
463
491
|
setupSave: function() {
|
464
492
|
if (!this.save) {
|
465
493
|
var profile_name = this.profile_name = new Element('input', {
|
@@ -479,7 +507,12 @@ var ChartBuilder = new Class({
|
|
479
507
|
'click': function() {
|
480
508
|
var hosts = this.searchers.host.tokenValues(),
|
481
509
|
metrics = this.searchers.metric.tokenValues();
|
510
|
+
percentiles = [];
|
511
|
+
percentile95 = this.options.percentile95;
|
482
512
|
|
513
|
+
if (percentile95) {
|
514
|
+
percentiles.push(95);
|
515
|
+
}
|
483
516
|
var jsonRequest = new Request.JSON({
|
484
517
|
method: 'post',
|
485
518
|
url: '/builder',
|
@@ -495,7 +528,8 @@ var ChartBuilder = new Class({
|
|
495
528
|
}).send({'data': {
|
496
529
|
'hosts': hosts,
|
497
530
|
'metrics': metrics,
|
498
|
-
'profile_name': profile_name.get('value')
|
531
|
+
'profile_name': profile_name.get('value'),
|
532
|
+
'percentiles': percentiles
|
499
533
|
}});
|
500
534
|
|
501
535
|
}.bind(this)
|
@@ -525,7 +559,14 @@ var ChartBuilder = new Class({
|
|
525
559
|
|
526
560
|
|
527
561
|
var hosts = $(this.searchers.host).getElements("div.token.finalized"),
|
528
|
-
metrics = $(this.searchers.metric).getElements("div.token.finalized")
|
562
|
+
metrics = $(this.searchers.metric).getElements("div.token.finalized"),
|
563
|
+
percentiles = [];
|
564
|
+
percentile95 = this.options.percentile95;
|
565
|
+
|
566
|
+
if (percentile95) {
|
567
|
+
percentiles.push(95);
|
568
|
+
}
|
569
|
+
this.options.percentiles = percentiles;
|
529
570
|
|
530
571
|
if (hosts.length > 0 && metrics.length > 0) {
|
531
572
|
this.showGraphs();
|
@@ -554,8 +595,9 @@ var ChartBuilder = new Class({
|
|
554
595
|
var hosts = hosts.map(function(el) { return el.get('text') }),
|
555
596
|
metrics = metrics.map(function(el) { return el.get('text') }),
|
556
597
|
graphs = $('graphs'),
|
557
|
-
save = this.save
|
558
|
-
profile_name = this.profile_name
|
598
|
+
save = this.save,
|
599
|
+
profile_name = this.profile_name,
|
600
|
+
percentiles = this.options.percentiles;
|
559
601
|
|
560
602
|
graphs.empty();
|
561
603
|
hosts.each(function(host) {
|
@@ -579,7 +621,8 @@ var ChartBuilder = new Class({
|
|
579
621
|
graphs.grab(element);
|
580
622
|
|
581
623
|
var graph = new VisageGraph(element, host, plugin, {
|
582
|
-
pluginInstance: metrics.join(',')
|
624
|
+
pluginInstance: metrics.join(','),
|
625
|
+
percentiles: percentiles
|
583
626
|
});
|
584
627
|
|
585
628
|
window.Graphs.include(graph);
|
@@ -121,6 +121,7 @@ function formatValue(value, options) {
|
|
121
121
|
break
|
122
122
|
}
|
123
123
|
|
124
|
+
if (!(label)) { label = 0; }
|
124
125
|
return label.format({decimals: precision, suffix: unit})
|
125
126
|
}
|
126
127
|
|
@@ -168,6 +169,7 @@ var VisageBase = new Class({
|
|
168
169
|
},
|
169
170
|
dataURL: function() {
|
170
171
|
var url = ['data', this.options.host, this.options.plugin];
|
172
|
+
|
171
173
|
// if the data exists on another host (useful for embedding)
|
172
174
|
if (this.options.baseurl) {
|
173
175
|
url.unshift(this.options.baseurl.replace(/\/$/, ''))
|
@@ -180,7 +182,14 @@ var VisageBase = new Class({
|
|
180
182
|
if (!url[0].match(/http\:\/\//)) {
|
181
183
|
url[0] = '/' + url[0]
|
182
184
|
}
|
183
|
-
|
185
|
+
var options = '';
|
186
|
+
if (this.options.percentiles) {
|
187
|
+
if (this.options.percentiles.length > 0) {
|
188
|
+
options = '?percentiles=true';
|
189
|
+
this.options.percentile95 = true;
|
190
|
+
}
|
191
|
+
}
|
192
|
+
return url.join('/') + options;
|
184
193
|
},
|
185
194
|
getData: function() {
|
186
195
|
this.request = new Request.JSONP({
|
@@ -250,9 +259,11 @@ var VisageGraph = new Class({
|
|
250
259
|
});
|
251
260
|
|
252
261
|
this.chart.redraw();
|
262
|
+
this.drawPercentiles(this.chart)
|
253
263
|
break;
|
254
264
|
default:
|
255
265
|
this.drawChart()
|
266
|
+
this.drawPercentiles(this.chart)
|
256
267
|
break;
|
257
268
|
}
|
258
269
|
},
|
@@ -264,8 +275,8 @@ var VisageGraph = new Class({
|
|
264
275
|
|
265
276
|
$each(data[host][plugin], function(instance, instanceName) {
|
266
277
|
$each(instance, function(metric, metricName) {
|
267
|
-
var start
|
268
|
-
finish
|
278
|
+
var start = metric.start,
|
279
|
+
finish = metric.finish,
|
269
280
|
interval = (finish - start) / metric.data.length;
|
270
281
|
|
271
282
|
var data = metric.data.map(function(value, index) {
|
@@ -278,12 +289,12 @@ var VisageGraph = new Class({
|
|
278
289
|
var set = {
|
279
290
|
name: [ host, plugin, instanceName, metricName ],
|
280
291
|
data: data,
|
292
|
+
percentile95: metric.percentile_95
|
281
293
|
};
|
282
294
|
|
283
295
|
series.push(set)
|
284
296
|
}, this);
|
285
297
|
}, this);
|
286
|
-
|
287
298
|
return series
|
288
299
|
},
|
289
300
|
getSeriesMinMax: function(series) {
|
@@ -313,7 +324,40 @@ var VisageGraph = new Class({
|
|
313
324
|
|
314
325
|
return {'min': min, 'max': max};
|
315
326
|
},
|
327
|
+
removePercentiles: function(chart) {
|
328
|
+
|
329
|
+
var series = this.series;
|
330
|
+
|
331
|
+
series.each(function(set) {
|
332
|
+
chart.yAxis[0].removePlotLine('95e_' + set.name[2] + set.name[3]);
|
333
|
+
});
|
334
|
+
},
|
335
|
+
drawPercentiles: function(chart) {
|
336
|
+
var series = this.series;
|
337
|
+
|
338
|
+
/* Get the maximum value across all sets.
|
339
|
+
* Used later on to determine the decimal place in the label. */
|
340
|
+
meta = this.getSeriesMinMax(series);
|
341
|
+
var min = meta.min,
|
342
|
+
max = meta.max;
|
343
|
+
|
344
|
+
series.each(function(set) {
|
345
|
+
formattedValue = formatValue(set.percentile95, { 'precision': 2, 'min': min, 'max': max });
|
346
|
+
chart.yAxis[0].removePlotLine('95e_' + set.name[2] + set.name[3]);
|
347
|
+
chart.yAxis[0].addPlotLine({
|
348
|
+
id: '95e_' + set.name[2] + set.name[3],
|
349
|
+
value: set.percentile95,
|
350
|
+
color: '#ff0000',
|
351
|
+
width: 1,
|
352
|
+
zIndex: 5,
|
353
|
+
label: {
|
354
|
+
text: '95e ' + set.name[3] + ": " + formattedValue
|
355
|
+
}
|
356
|
+
})
|
357
|
+
});
|
358
|
+
},
|
316
359
|
drawChart: function() {
|
360
|
+
|
317
361
|
var series = this.series,
|
318
362
|
title = this.title(),
|
319
363
|
element = this.parentElement,
|
@@ -344,6 +388,9 @@ var VisageGraph = new Class({
|
|
344
388
|
'finish': this.lastFinish / 1000 + 10,
|
345
389
|
'live': true };
|
346
390
|
this.requestData = data;
|
391
|
+
// FIXME: for 95e plotLines - need to update them each data retrieval
|
392
|
+
// but perhaps 'live' just sends incremental data and so won't cause
|
393
|
+
// a recalculation of the 95e figures on the server side?
|
347
394
|
this.getData()
|
348
395
|
}
|
349
396
|
}.bind(this), 10000);
|
@@ -400,7 +447,7 @@ var VisageGraph = new Class({
|
|
400
447
|
});
|
401
448
|
return value
|
402
449
|
}
|
403
|
-
}
|
450
|
+
},
|
404
451
|
},
|
405
452
|
plotOptions: {
|
406
453
|
series: {
|
@@ -447,8 +494,9 @@ var VisageGraph = new Class({
|
|
447
494
|
layout: 'horizontal',
|
448
495
|
align: 'center',
|
449
496
|
verticalAlign: 'top',
|
450
|
-
y:
|
497
|
+
y: 255,
|
451
498
|
borderWidth: 0,
|
499
|
+
floating: true,
|
452
500
|
labelFormatter: function() {
|
453
501
|
return formatSeriesLabel(this.name)
|
454
502
|
},
|
@@ -522,7 +570,7 @@ var VisageGraph = new Class({
|
|
522
570
|
'7 days': 168,
|
523
571
|
'2 weeks': 336,
|
524
572
|
'1 month': 774,
|
525
|
-
'3
|
573
|
+
'3 months': 2322,
|
526
574
|
'6 months': 4368,
|
527
575
|
'1 year': 8760,
|
528
576
|
'2 years': 17520 });
|
@@ -540,14 +588,45 @@ var VisageGraph = new Class({
|
|
540
588
|
select.grab(option)
|
541
589
|
});
|
542
590
|
|
543
|
-
|
591
|
+
/* Calendar month timescales dropdown */
|
592
|
+
var monthlyTimescales = new Hash({ 'current month': 0,
|
593
|
+
'previous month': 1,
|
594
|
+
'two months ago': 2,
|
595
|
+
'three months ago': 3});
|
596
|
+
|
597
|
+
monthlyTimescales.each(function(monthsAgo, label) {
|
598
|
+
var current = this.currentTimePeriod == label;
|
599
|
+
var value = "start=" + (new Date().decrement('month', monthsAgo).set('date', 1).set('hr', 0).set('min', 0).set('sec', 0).getTime() / 1000);
|
600
|
+
value += '&finish=' + (new Date().decrement('month', monthsAgo - 1).set('date', 1).set('hr', 0).set('min', 0).set('sec', 0).getTime() / 1000);
|
601
|
+
|
602
|
+
var option = new Element('option', {
|
603
|
+
'html': label,
|
604
|
+
'value': value,
|
605
|
+
'selected': (current ? 'selected' : ''),
|
606
|
+
});
|
607
|
+
select.grab(option)
|
608
|
+
});
|
609
|
+
|
610
|
+
var liveToggler = this.liveToggler = new Element('input', {
|
544
611
|
'type': 'checkbox',
|
545
612
|
'id': this.parentElement + '-live',
|
546
613
|
'name': 'live',
|
547
614
|
'checked': this.options.live,
|
615
|
+
'disabled': this.options.percentile95,
|
548
616
|
'events': {
|
549
|
-
'click': function() {
|
617
|
+
'click': function(e) {
|
550
618
|
this.options.live = !this.options.live
|
619
|
+
if (this.options.live) {
|
620
|
+
// tell percentiles95Toggler to be unchecked
|
621
|
+
if (this.options.percentile95) {
|
622
|
+
this.percentile95Toggler.fireEvent('click');
|
623
|
+
}
|
624
|
+
this.percentile95Toggler.set('disabled', true);
|
625
|
+
} else {
|
626
|
+
this.requestData.live = false;
|
627
|
+
this.percentile95Toggler.set('disabled', false);
|
628
|
+
e.target.form.fireEvent('submit', e)
|
629
|
+
}
|
551
630
|
}.bind(this)
|
552
631
|
},
|
553
632
|
'styles': {
|
@@ -567,6 +646,60 @@ var VisageGraph = new Class({
|
|
567
646
|
}
|
568
647
|
});
|
569
648
|
|
649
|
+
var percentile95Toggler = this.percentile95Toggler = new Element('input', {
|
650
|
+
'type': 'checkbox',
|
651
|
+
'id': this.parentElement + '-percentile95',
|
652
|
+
'name': 'percentile95',
|
653
|
+
'checked': this.options.percentile95,
|
654
|
+
'events': {
|
655
|
+
'click': function() {
|
656
|
+
this.options.percentile95 = !this.options.percentile95;
|
657
|
+
if (!(this.options.percentiles)) {
|
658
|
+
this.options.percentiles = [];
|
659
|
+
}
|
660
|
+
if (this.options.percentile95) {
|
661
|
+
this.options.percentiles.push('95');
|
662
|
+
if ((this.chart) && (this.series[0].percentile95)) {
|
663
|
+
this.drawPercentiles(this.chart);
|
664
|
+
} else {
|
665
|
+
this.getData();
|
666
|
+
}
|
667
|
+
// tell liveToggler to be unchecked
|
668
|
+
if (this.options.live) {
|
669
|
+
this.liveToggler.fireEvent('click');
|
670
|
+
}
|
671
|
+
this.liveToggler.set('disabled', true);
|
672
|
+
} else {
|
673
|
+
// FIXME - when adding support for 5th and 50th percentiles
|
674
|
+
// etc we'll need to switch the options.percentiles array
|
675
|
+
// to a hash for easy enabling / disabling of percentiles
|
676
|
+
this.options.percentiles = [];
|
677
|
+
if (this.chart) {
|
678
|
+
this.removePercentiles(this.chart);
|
679
|
+
} else {
|
680
|
+
this.getData();
|
681
|
+
}
|
682
|
+
this.liveToggler.set('disabled', false);
|
683
|
+
}
|
684
|
+
}.bind(this)
|
685
|
+
},
|
686
|
+
'styles': {
|
687
|
+
'margin-right': '4px',
|
688
|
+
'cursor': 'pointer'
|
689
|
+
}
|
690
|
+
});
|
691
|
+
|
692
|
+
var percentile95Label = new Element('label', {
|
693
|
+
'for': this.parentElement + '-percentile95',
|
694
|
+
'html': '95th Percentile',
|
695
|
+
'styles': {
|
696
|
+
'font-family': 'sans-serif',
|
697
|
+
'font-size': '11px',
|
698
|
+
'margin-right': '8px',
|
699
|
+
'cursor': 'pointer'
|
700
|
+
}
|
701
|
+
});
|
702
|
+
|
570
703
|
var exportLink = new Element('a', {
|
571
704
|
'href': this.dataURL(),
|
572
705
|
'html': 'Export data',
|
@@ -594,6 +727,8 @@ var VisageGraph = new Class({
|
|
594
727
|
});
|
595
728
|
|
596
729
|
form.grab(exportLink)
|
730
|
+
form.grab(percentile95Toggler)
|
731
|
+
form.grab(percentile95Label)
|
597
732
|
form.grab(liveToggler)
|
598
733
|
form.grab(liveLabel)
|
599
734
|
form.grab(select)
|
@@ -0,0 +1,12 @@
|
|
1
|
+
/*
|
2
|
+
Highcharts JS v2.2.1 (2012-03-15)
|
3
|
+
MooTools adapter
|
4
|
+
|
5
|
+
(c) 2010-2011 Torstein H?nsi
|
6
|
+
|
7
|
+
License: www.highcharts.com/license
|
8
|
+
*/
|
9
|
+
(function(){var e=window,i=document,f=e.MooTools.version.substring(0,3),g=f==="1.2"||f==="1.1",j=g||f==="1.3",h=e.$extend||function(){return Object.append.apply(Object,arguments)};e.HighchartsAdapter={init:function(a){var b=Fx.prototype,c=b.start,d=Fx.Morph.prototype,e=d.compute;b.start=function(b,d){var e=this.element;if(b.d)this.paths=a.init(e,e.d,this.toD);c.apply(this,arguments);return this};d.compute=function(b,c,d){var f=this.paths;if(f)this.element.attr("d",a.step(f[0],f[1],d,this.toD));else return e.apply(this,
|
10
|
+
arguments)}},getScript:function(a,b){var c=i.getElementsByTagName("head")[0],d=i.createElement("script");d.type="text/javascript";d.src=a;d.onload=b;c.appendChild(d)},animate:function(a,b,c){var d=a.attr,f=c&&c.complete;if(d&&!a.setStyle)a.getStyle=a.attr,a.setStyle=function(){var b=arguments;a.attr.call(a,b[0],b[1][0])},a.$family=function(){return!0};e.HighchartsAdapter.stop(a);c=new Fx.Morph(d?a:$(a),h({transition:Fx.Transitions.Quad.easeInOut},c));if(d)c.element=a;if(b.d)c.toD=b.d;f&&c.addEvent("complete",
|
11
|
+
f);c.start(b);a.fx=c},each:function(a,b){return g?$each(a,b):Array.each(a,b)},map:function(a,b){return a.map(b)},grep:function(a,b){return a.filter(b)},merge:function(){var a=arguments,b=[{}],c=a.length;if(g)a=$merge.apply(null,a);else{for(;c--;)typeof a[c]!=="boolean"&&(b[c+1]=a[c]);a=Object.merge.apply(Object,b)}return a},offset:function(a){a=$(a).getOffsets();return{left:a.x,top:a.y}},extendWithEvents:function(a){a.addEvent||(a.nodeName?$(a):h(a,new Events))},addEvent:function(a,b,c){typeof b===
|
12
|
+
"string"&&(b==="unload"&&(b="beforeunload"),e.HighchartsAdapter.extendWithEvents(a),a.addEvent(b,c))},removeEvent:function(a,b,c){typeof a!=="string"&&(e.HighchartsAdapter.extendWithEvents(a),b?(b==="unload"&&(b="beforeunload"),c?a.removeEvent(b,c):a.removeEvents(b)):a.removeEvents())},fireEvent:function(a,b,c,d){b={type:b,target:a};b=j?new Event(b):new DOMEvent(b);b=h(b,c);b.preventDefault=function(){d=null};a.fireEvent&&a.fireEvent(b.type,b);d&&d(b)},stop:function(a){a.fx&&a.fx.cancel()}}})();
|
@@ -0,0 +1,298 @@
|
|
1
|
+
/**
|
2
|
+
* @license Highcharts JS v2.2.1 (2012-03-15)
|
3
|
+
* MooTools adapter
|
4
|
+
*
|
5
|
+
* (c) 2010-2011 Torstein Hønsi
|
6
|
+
*
|
7
|
+
* License: www.highcharts.com/license
|
8
|
+
*/
|
9
|
+
|
10
|
+
// JSLint options:
|
11
|
+
/*global Fx, $, $extend, $each, $merge, Events, Event, DOMEvent */
|
12
|
+
|
13
|
+
(function () {
|
14
|
+
|
15
|
+
var win = window,
|
16
|
+
doc = document,
|
17
|
+
mooVersion = win.MooTools.version.substring(0, 3), // Get the first three characters of the version number
|
18
|
+
legacy = mooVersion === '1.2' || mooVersion === '1.1', // 1.1 && 1.2 considered legacy, 1.3 is not.
|
19
|
+
legacyEvent = legacy || mooVersion === '1.3', // In versions 1.1 - 1.3 the event class is named Event, in newer versions it is named DOMEvent.
|
20
|
+
$extend = win.$extend || function () {
|
21
|
+
return Object.append.apply(Object, arguments);
|
22
|
+
};
|
23
|
+
|
24
|
+
win.HighchartsAdapter = {
|
25
|
+
/**
|
26
|
+
* Initialize the adapter. This is run once as Highcharts is first run.
|
27
|
+
* @param {Object} pathAnim The helper object to do animations across adapters.
|
28
|
+
*/
|
29
|
+
init: function (pathAnim) {
|
30
|
+
var fxProto = Fx.prototype,
|
31
|
+
fxStart = fxProto.start,
|
32
|
+
morphProto = Fx.Morph.prototype,
|
33
|
+
morphCompute = morphProto.compute;
|
34
|
+
|
35
|
+
// override Fx.start to allow animation of SVG element wrappers
|
36
|
+
/*jslint unparam: true*//* allow unused parameters in fx functions */
|
37
|
+
fxProto.start = function (from, to) {
|
38
|
+
var fx = this,
|
39
|
+
elem = fx.element;
|
40
|
+
|
41
|
+
// special for animating paths
|
42
|
+
if (from.d) {
|
43
|
+
//this.fromD = this.element.d.split(' ');
|
44
|
+
fx.paths = pathAnim.init(
|
45
|
+
elem,
|
46
|
+
elem.d,
|
47
|
+
fx.toD
|
48
|
+
);
|
49
|
+
}
|
50
|
+
fxStart.apply(fx, arguments);
|
51
|
+
|
52
|
+
return this; // chainable
|
53
|
+
};
|
54
|
+
|
55
|
+
// override Fx.step to allow animation of SVG element wrappers
|
56
|
+
morphProto.compute = function (from, to, delta) {
|
57
|
+
var fx = this,
|
58
|
+
paths = fx.paths;
|
59
|
+
|
60
|
+
if (paths) {
|
61
|
+
fx.element.attr(
|
62
|
+
'd',
|
63
|
+
pathAnim.step(paths[0], paths[1], delta, fx.toD)
|
64
|
+
);
|
65
|
+
} else {
|
66
|
+
return morphCompute.apply(fx, arguments);
|
67
|
+
}
|
68
|
+
};
|
69
|
+
/*jslint unparam: false*/
|
70
|
+
},
|
71
|
+
|
72
|
+
/**
|
73
|
+
* Downloads a script and executes a callback when done.
|
74
|
+
* @param {String} scriptLocation
|
75
|
+
* @param {Function} callback
|
76
|
+
*/
|
77
|
+
getScript: function (scriptLocation, callback) {
|
78
|
+
// We cannot assume that Assets class from mootools-more is available so instead insert a script tag to download script.
|
79
|
+
var head = doc.getElementsByTagName('head')[0];
|
80
|
+
var script = doc.createElement('script');
|
81
|
+
|
82
|
+
script.type = 'text/javascript';
|
83
|
+
script.src = scriptLocation;
|
84
|
+
script.onload = callback;
|
85
|
+
|
86
|
+
head.appendChild(script);
|
87
|
+
},
|
88
|
+
|
89
|
+
/**
|
90
|
+
* Animate a HTML element or SVG element wrapper
|
91
|
+
* @param {Object} el
|
92
|
+
* @param {Object} params
|
93
|
+
* @param {Object} options jQuery-like animation options: duration, easing, callback
|
94
|
+
*/
|
95
|
+
animate: function (el, params, options) {
|
96
|
+
var isSVGElement = el.attr,
|
97
|
+
effect,
|
98
|
+
complete = options && options.complete;
|
99
|
+
|
100
|
+
if (isSVGElement && !el.setStyle) {
|
101
|
+
// add setStyle and getStyle methods for internal use in Moo
|
102
|
+
el.getStyle = el.attr;
|
103
|
+
el.setStyle = function () { // property value is given as array in Moo - break it down
|
104
|
+
var args = arguments;
|
105
|
+
el.attr.call(el, args[0], args[1][0]);
|
106
|
+
};
|
107
|
+
// dirty hack to trick Moo into handling el as an element wrapper
|
108
|
+
el.$family = function () { return true; };
|
109
|
+
}
|
110
|
+
|
111
|
+
// stop running animations
|
112
|
+
win.HighchartsAdapter.stop(el);
|
113
|
+
|
114
|
+
// define and run the effect
|
115
|
+
effect = new Fx.Morph(
|
116
|
+
isSVGElement ? el : $(el),
|
117
|
+
$extend({
|
118
|
+
transition: Fx.Transitions.Quad.easeInOut
|
119
|
+
}, options)
|
120
|
+
);
|
121
|
+
|
122
|
+
// Make sure that the element reference is set when animating svg elements
|
123
|
+
if (isSVGElement) {
|
124
|
+
effect.element = el;
|
125
|
+
}
|
126
|
+
|
127
|
+
// special treatment for paths
|
128
|
+
if (params.d) {
|
129
|
+
effect.toD = params.d;
|
130
|
+
}
|
131
|
+
|
132
|
+
// jQuery-like events
|
133
|
+
if (complete) {
|
134
|
+
effect.addEvent('complete', complete);
|
135
|
+
}
|
136
|
+
|
137
|
+
// run
|
138
|
+
effect.start(params);
|
139
|
+
|
140
|
+
// record for use in stop method
|
141
|
+
el.fx = effect;
|
142
|
+
},
|
143
|
+
|
144
|
+
/**
|
145
|
+
* MooTool's each function
|
146
|
+
*
|
147
|
+
*/
|
148
|
+
each: function (arr, fn) {
|
149
|
+
return legacy ?
|
150
|
+
$each(arr, fn) :
|
151
|
+
Array.each(arr, fn);
|
152
|
+
},
|
153
|
+
|
154
|
+
/**
|
155
|
+
* Map an array
|
156
|
+
* @param {Array} arr
|
157
|
+
* @param {Function} fn
|
158
|
+
*/
|
159
|
+
map: function (arr, fn) {
|
160
|
+
return arr.map(fn);
|
161
|
+
},
|
162
|
+
|
163
|
+
/**
|
164
|
+
* Grep or filter an array
|
165
|
+
* @param {Array} arr
|
166
|
+
* @param {Function} fn
|
167
|
+
*/
|
168
|
+
grep: function (arr, fn) {
|
169
|
+
return arr.filter(fn);
|
170
|
+
},
|
171
|
+
|
172
|
+
/**
|
173
|
+
* Deep merge two objects and return a third
|
174
|
+
*/
|
175
|
+
merge: function () {
|
176
|
+
var args = arguments,
|
177
|
+
args13 = [{}], // MooTools 1.3+
|
178
|
+
i = args.length,
|
179
|
+
ret;
|
180
|
+
|
181
|
+
if (legacy) {
|
182
|
+
ret = $merge.apply(null, args);
|
183
|
+
} else {
|
184
|
+
while (i--) {
|
185
|
+
// Boolean argumens should not be merged.
|
186
|
+
// JQuery explicitly skips this, so we do it here as well.
|
187
|
+
if (typeof args[i] !== 'boolean') {
|
188
|
+
args13[i + 1] = args[i];
|
189
|
+
}
|
190
|
+
}
|
191
|
+
ret = Object.merge.apply(Object, args13);
|
192
|
+
}
|
193
|
+
|
194
|
+
return ret;
|
195
|
+
},
|
196
|
+
|
197
|
+
/**
|
198
|
+
* Get the offset of an element relative to the top left corner of the web page
|
199
|
+
*/
|
200
|
+
offset: function (el) {
|
201
|
+
var offsets = $(el).getOffsets();
|
202
|
+
return {
|
203
|
+
left: offsets.x,
|
204
|
+
top: offsets.y
|
205
|
+
};
|
206
|
+
},
|
207
|
+
|
208
|
+
/**
|
209
|
+
* Extends an object with Events, if its not done
|
210
|
+
*/
|
211
|
+
extendWithEvents: function (el) {
|
212
|
+
// if the addEvent method is not defined, el is a custom Highcharts object
|
213
|
+
// like series or point
|
214
|
+
if (!el.addEvent) {
|
215
|
+
if (el.nodeName) {
|
216
|
+
el = $(el); // a dynamically generated node
|
217
|
+
} else {
|
218
|
+
$extend(el, new Events()); // a custom object
|
219
|
+
}
|
220
|
+
}
|
221
|
+
},
|
222
|
+
|
223
|
+
/**
|
224
|
+
* Add an event listener
|
225
|
+
* @param {Object} el HTML element or custom object
|
226
|
+
* @param {String} type Event type
|
227
|
+
* @param {Function} fn Event handler
|
228
|
+
*/
|
229
|
+
addEvent: function (el, type, fn) {
|
230
|
+
if (typeof type === 'string') { // chart broke due to el being string, type function
|
231
|
+
|
232
|
+
if (type === 'unload') { // Moo self destructs before custom unload events
|
233
|
+
type = 'beforeunload';
|
234
|
+
}
|
235
|
+
|
236
|
+
win.HighchartsAdapter.extendWithEvents(el);
|
237
|
+
|
238
|
+
el.addEvent(type, fn);
|
239
|
+
}
|
240
|
+
},
|
241
|
+
|
242
|
+
removeEvent: function (el, type, fn) {
|
243
|
+
if (typeof el === 'string') {
|
244
|
+
// el.removeEvents below apperantly calls this method again. Do not quite understand why, so for now just bail out.
|
245
|
+
return;
|
246
|
+
}
|
247
|
+
win.HighchartsAdapter.extendWithEvents(el);
|
248
|
+
if (type) {
|
249
|
+
if (type === 'unload') { // Moo self destructs before custom unload events
|
250
|
+
type = 'beforeunload';
|
251
|
+
}
|
252
|
+
|
253
|
+
if (fn) {
|
254
|
+
el.removeEvent(type, fn);
|
255
|
+
} else {
|
256
|
+
el.removeEvents(type);
|
257
|
+
}
|
258
|
+
} else {
|
259
|
+
el.removeEvents();
|
260
|
+
}
|
261
|
+
},
|
262
|
+
|
263
|
+
fireEvent: function (el, event, eventArguments, defaultFunction) {
|
264
|
+
var eventArgs = {
|
265
|
+
type: event,
|
266
|
+
target: el
|
267
|
+
};
|
268
|
+
// create an event object that keeps all functions
|
269
|
+
event = legacyEvent ? new Event(eventArgs) : new DOMEvent(eventArgs);
|
270
|
+
event = $extend(event, eventArguments);
|
271
|
+
// override the preventDefault function to be able to use
|
272
|
+
// this for custom events
|
273
|
+
event.preventDefault = function () {
|
274
|
+
defaultFunction = null;
|
275
|
+
};
|
276
|
+
// if fireEvent is not available on the object, there hasn't been added
|
277
|
+
// any events to it above
|
278
|
+
if (el.fireEvent) {
|
279
|
+
el.fireEvent(event.type, event);
|
280
|
+
}
|
281
|
+
|
282
|
+
// fire the default if it is passed and it is not prevented above
|
283
|
+
if (defaultFunction) {
|
284
|
+
defaultFunction(event);
|
285
|
+
}
|
286
|
+
},
|
287
|
+
|
288
|
+
/**
|
289
|
+
* Stop running animations on the object
|
290
|
+
*/
|
291
|
+
stop: function (el) {
|
292
|
+
if (el.fx) {
|
293
|
+
el.fx.cancel();
|
294
|
+
}
|
295
|
+
}
|
296
|
+
};
|
297
|
+
|
298
|
+
}());
|