mouth 0.8.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 +7 -0
- data/Capfile +26 -0
- data/Gemfile +3 -0
- data/MIT-LICENSE +20 -0
- data/README.md +138 -0
- data/Rakefile +19 -0
- data/TODO +32 -0
- data/bin/mouth +77 -0
- data/bin/mouth-console +18 -0
- data/bin/mouth-endoscope +18 -0
- data/lib/mouth.rb +61 -0
- data/lib/mouth/dashboard.rb +25 -0
- data/lib/mouth/endoscope.rb +120 -0
- data/lib/mouth/endoscope/public/222222_256x240_icons_icons.png +0 -0
- data/lib/mouth/endoscope/public/application.css +464 -0
- data/lib/mouth/endoscope/public/application.js +938 -0
- data/lib/mouth/endoscope/public/backbone.js +1158 -0
- data/lib/mouth/endoscope/public/d3.js +4707 -0
- data/lib/mouth/endoscope/public/d3.time.js +687 -0
- data/lib/mouth/endoscope/public/jquery-ui-1.8.16.custom.min.js +177 -0
- data/lib/mouth/endoscope/public/jquery.js +4 -0
- data/lib/mouth/endoscope/public/json2.js +480 -0
- data/lib/mouth/endoscope/public/keymaster.js +163 -0
- data/lib/mouth/endoscope/public/linen.js +46 -0
- data/lib/mouth/endoscope/public/seven.css +68 -0
- data/lib/mouth/endoscope/public/seven.js +291 -0
- data/lib/mouth/endoscope/public/underscore.js +931 -0
- data/lib/mouth/endoscope/views/dashboard.erb +67 -0
- data/lib/mouth/graph.rb +58 -0
- data/lib/mouth/instrument.rb +56 -0
- data/lib/mouth/record.rb +72 -0
- data/lib/mouth/runner.rb +89 -0
- data/lib/mouth/sequence.rb +284 -0
- data/lib/mouth/source.rb +76 -0
- data/lib/mouth/sucker.rb +235 -0
- data/lib/mouth/version.rb +3 -0
- data/mouth.gemspec +28 -0
- data/test/sequence_test.rb +163 -0
- data/test/sucker_test.rb +55 -0
- data/test/test_helper.rb +5 -0
- metadata +167 -0
@@ -0,0 +1,163 @@
|
|
1
|
+
// keymaster.js
|
2
|
+
// (c) 2011 Thomas Fuchs
|
3
|
+
// keymaster.js may be freely distributed under the MIT license.
|
4
|
+
|
5
|
+
;(function(global){
|
6
|
+
var k,
|
7
|
+
_handlers = {},
|
8
|
+
_mods = { 16: false, 18: false, 17: false, 91: false },
|
9
|
+
_scope = 'all',
|
10
|
+
// modifier keys
|
11
|
+
_MODIFIERS = {
|
12
|
+
'⇧': 16, shift: 16,
|
13
|
+
'⌥': 18, alt: 18, option: 18,
|
14
|
+
'⌃': 17, ctrl: 17, control: 17,
|
15
|
+
'⌘': 91, command: 91
|
16
|
+
},
|
17
|
+
// special keys
|
18
|
+
_MAP = {
|
19
|
+
backspace: 8, tab: 9, clear: 12,
|
20
|
+
enter: 13, 'return': 13,
|
21
|
+
esc: 27, escape: 27, space: 32,
|
22
|
+
left: 37, up: 38,
|
23
|
+
right: 39, down: 40,
|
24
|
+
del: 46, 'delete': 46,
|
25
|
+
home: 36, end: 35,
|
26
|
+
pageup: 33, pagedown: 34,
|
27
|
+
',': 188, '.': 190, '/': 191,
|
28
|
+
'`': 192, '-': 189, '=': 187,
|
29
|
+
';': 186, '\'': 222,
|
30
|
+
'[': 219, ']': 221, '\\': 220
|
31
|
+
};
|
32
|
+
|
33
|
+
for(k=1;k<20;k++) _MODIFIERS['f'+k] = 111+k;
|
34
|
+
|
35
|
+
// IE doesn't support Array#indexOf, so have a simple replacement
|
36
|
+
function index(array, item){
|
37
|
+
var i = array.length;
|
38
|
+
while(i--) if(array[i]===item) return i;
|
39
|
+
return -1;
|
40
|
+
}
|
41
|
+
|
42
|
+
// handle keydown event
|
43
|
+
function dispatch(event){
|
44
|
+
var key, tagName, handler, k, i, modifiersMatch;
|
45
|
+
tagName = (event.target || event.srcElement).tagName;
|
46
|
+
key = event.keyCode;
|
47
|
+
|
48
|
+
// if a modifier key, set the key.<modifierkeyname> property to true and return
|
49
|
+
if(key == 93 || key == 224) key = 91; // right command on webkit, command on Gecko
|
50
|
+
if(key in _mods) {
|
51
|
+
_mods[key] = true;
|
52
|
+
// 'assignKey' from inside this closure is exported to window.key
|
53
|
+
for(k in _MODIFIERS) if(_MODIFIERS[k] == key) assignKey[k] = true;
|
54
|
+
return;
|
55
|
+
}
|
56
|
+
|
57
|
+
// ignore keypressed in any elements that support keyboard data input
|
58
|
+
if (tagName == 'INPUT' || tagName == 'SELECT' || tagName == 'TEXTAREA') return;
|
59
|
+
|
60
|
+
// abort if no potentially matching shortcuts found
|
61
|
+
if (!(key in _handlers)) return;
|
62
|
+
|
63
|
+
// for each potential shortcut
|
64
|
+
for (i = 0; i < _handlers[key].length; i++) {
|
65
|
+
handler = _handlers[key][i];
|
66
|
+
|
67
|
+
// see if it's in the current scope
|
68
|
+
if(handler.scope == _scope || handler.scope == 'all'){
|
69
|
+
// check if modifiers match if any
|
70
|
+
modifiersMatch = handler.mods.length > 0;
|
71
|
+
for(k in _mods)
|
72
|
+
if((!_mods[k] && index(handler.mods, +k) > -1) ||
|
73
|
+
(_mods[k] && index(handler.mods, +k) == -1)) modifiersMatch = false;
|
74
|
+
// call the handler and stop the event if neccessary
|
75
|
+
if((handler.mods.length == 0 && !_mods[16] && !_mods[18] && !_mods[17] && !_mods[91]) || modifiersMatch){
|
76
|
+
if(handler.method(event, handler)===false){
|
77
|
+
if(event.preventDefault) event.preventDefault();
|
78
|
+
else event.returnValue = false;
|
79
|
+
if(event.stopPropagation) event.stopPropagation();
|
80
|
+
if(event.cancelBubble) event.cancelBubble = true;
|
81
|
+
}
|
82
|
+
}
|
83
|
+
}
|
84
|
+
}
|
85
|
+
};
|
86
|
+
|
87
|
+
// unset modifier keys on keyup
|
88
|
+
function clearModifier(event){
|
89
|
+
var key = event.keyCode, k;
|
90
|
+
if(key == 93 || key == 224) key = 91;
|
91
|
+
if(key in _mods) {
|
92
|
+
_mods[key] = false;
|
93
|
+
for(k in _MODIFIERS) if(_MODIFIERS[k] == key) assignKey[k] = false;
|
94
|
+
}
|
95
|
+
};
|
96
|
+
|
97
|
+
function resetModifiers() {
|
98
|
+
for(k in _mods) _mods[k] = false;
|
99
|
+
for(k in _MODIFIERS) assignKey[k] = false;
|
100
|
+
}
|
101
|
+
|
102
|
+
// parse and assign shortcut
|
103
|
+
function assignKey(key, scope, method){
|
104
|
+
var keys, mods, i, mi;
|
105
|
+
if (method === undefined) {
|
106
|
+
method = scope;
|
107
|
+
scope = 'all';
|
108
|
+
}
|
109
|
+
key = key.replace(/\s/g,'');
|
110
|
+
keys = key.split(',');
|
111
|
+
|
112
|
+
if((keys[keys.length-1])=='')
|
113
|
+
keys[keys.length-2] += ',';
|
114
|
+
// for each shortcut
|
115
|
+
for (i = 0; i < keys.length; i++) {
|
116
|
+
// set modifier keys if any
|
117
|
+
mods = [];
|
118
|
+
key = keys[i].split('+');
|
119
|
+
if(key.length > 1){
|
120
|
+
mods = key.slice(0,key.length-1);
|
121
|
+
for (mi = 0; mi < mods.length; mi++)
|
122
|
+
mods[mi] = _MODIFIERS[mods[mi]];
|
123
|
+
key = [key[key.length-1]];
|
124
|
+
}
|
125
|
+
// convert to keycode and...
|
126
|
+
key = key[0]
|
127
|
+
key = _MAP[key] || key.toUpperCase().charCodeAt(0);
|
128
|
+
// ...store handler
|
129
|
+
if (!(key in _handlers)) _handlers[key] = [];
|
130
|
+
_handlers[key].push({ shortcut: keys[i], scope: scope, method: method, key: keys[i], mods: mods });
|
131
|
+
}
|
132
|
+
};
|
133
|
+
|
134
|
+
// initialize key.<modifier> to false
|
135
|
+
for(k in _MODIFIERS) assignKey[k] = false;
|
136
|
+
|
137
|
+
// set current scope (default 'all')
|
138
|
+
function setScope(scope){ _scope = scope || 'all' };
|
139
|
+
function getScope(){ return _scope || 'all' };
|
140
|
+
|
141
|
+
// cross-browser events
|
142
|
+
function addEvent(object, event, method) {
|
143
|
+
if (object.addEventListener)
|
144
|
+
object.addEventListener(event, method, false);
|
145
|
+
else if(object.attachEvent)
|
146
|
+
object.attachEvent('on'+event, function(){ method(window.event) });
|
147
|
+
};
|
148
|
+
|
149
|
+
// set the handlers globally on document
|
150
|
+
addEvent(document, 'keydown', dispatch);
|
151
|
+
addEvent(document, 'keyup', clearModifier);
|
152
|
+
|
153
|
+
// reset modifiers to false whenever the window is (re)focused.
|
154
|
+
addEvent(window, 'focus', resetModifiers);
|
155
|
+
|
156
|
+
// set window.key and window.key.setScope
|
157
|
+
global.key = assignKey;
|
158
|
+
global.key.setScope = setScope;
|
159
|
+
global.key.getScope = getScope;
|
160
|
+
|
161
|
+
if(typeof module !== 'undefined') module.exports = key;
|
162
|
+
|
163
|
+
})(this);
|
@@ -0,0 +1,46 @@
|
|
1
|
+
// Me function for linenizing the background
|
2
|
+
$(function() {
|
3
|
+
// temporary substitute for parameters:
|
4
|
+
var el = document.getElementById('current-dashboard');
|
5
|
+
|
6
|
+
|
7
|
+
var canvas = document.createElement('canvas');
|
8
|
+
var context = canvas.getContext('2d');
|
9
|
+
var x = parseInt(getStyle(el, 'width')), y = parseInt(getStyle(el, 'height'));
|
10
|
+
var hue = 0;
|
11
|
+
|
12
|
+
canvas.width = x;
|
13
|
+
canvas.height = y;
|
14
|
+
|
15
|
+
context.lineWidth = 0.25;
|
16
|
+
context.strokeStyle = 'hsla(0,0%,16%,1)';
|
17
|
+
|
18
|
+
var grad = context.createLinearGradient(0, 0, x, 0);
|
19
|
+
grad.addColorStop(0, 'hsla(0,0%,10%,1)');
|
20
|
+
grad.addColorStop(0.5, 'hsla(0,0%,13%,1)');
|
21
|
+
grad.addColorStop(1, 'hsla(0,0%,10%,1)');
|
22
|
+
context.fillStyle = grad;
|
23
|
+
context.fillRect(0, 0, x, y);
|
24
|
+
|
25
|
+
for (var i = 0; i <= x; i++) {
|
26
|
+
for (var j = 0; j <= y; j++) {
|
27
|
+
if (Math.round(Math.random() * 30) === 30) {
|
28
|
+
context.moveTo(i, j + 0.5);
|
29
|
+
context.lineTo(i + (Math.ceil(Math.random() * 200)), j + 0.5);
|
30
|
+
}
|
31
|
+
|
32
|
+
if (Math.round(Math.random() * 30) === 30) {
|
33
|
+
context.moveTo(i + 0.5, j + 0.5);
|
34
|
+
context.lineTo(i + 0.5, j + (Math.ceil(Math.random() * 200)));
|
35
|
+
}
|
36
|
+
}
|
37
|
+
}
|
38
|
+
|
39
|
+
context.stroke();
|
40
|
+
|
41
|
+
el.style.backgroundImage = 'url(' + canvas.toDataURL() + ')';
|
42
|
+
|
43
|
+
function getStyle(el, property) {
|
44
|
+
return el.style[property] || getComputedStyle(el, '').getPropertyValue(property);
|
45
|
+
}
|
46
|
+
});
|
@@ -0,0 +1,68 @@
|
|
1
|
+
path {
|
2
|
+
fill: none;
|
3
|
+
}
|
4
|
+
|
5
|
+
.seven-tip {
|
6
|
+
position: absolute;
|
7
|
+
background: #2e3a4f;
|
8
|
+
background: -moz-linear-gradient(top, #62769b 0%, #2e3a4f 35%, #2e3a4f 65%, #212a3d 100%);
|
9
|
+
background: -webkit-gradient(linear, left top, left bottom, color-stop(0%, #62769b), color-stop(35%, #2e3a4f), color-stop(65%, #2e3a4f), color-stop(100%, #212a3d));
|
10
|
+
background: -webkit-linear-gradient(top, #62769b 0%, #2e3a4f 35%, #2e3a4f 65%, #212a3d 100%);
|
11
|
+
background: -o-linear-gradient(top, #62769b 0%, #2e3a4f 35%, #2e3a4f 65%, #212a3d 100%);
|
12
|
+
background: -ms-linear-gradient(top, #62769b 0%, #2e3a4f 35%, #2e3a4f 65%, #212a3d 100%);
|
13
|
+
background: linear-gradient(top, #62769b 0%, #2e3a4f 35%, #2e3a4f 65%, #212a3d 100%);
|
14
|
+
|
15
|
+
border-radius: 4px 4px 4px 4px;
|
16
|
+
box-shadow: 5px 5px 10px rgba(0, 0, 0, 0.2);
|
17
|
+
|
18
|
+
min-width: 120px;
|
19
|
+
top: 0;
|
20
|
+
left: 0;
|
21
|
+
padding: 2px;
|
22
|
+
|
23
|
+
font-family: sans-serif;
|
24
|
+
font-size: 11px;
|
25
|
+
line-height: 12px;
|
26
|
+
|
27
|
+
}
|
28
|
+
|
29
|
+
.seven-tip-inner {
|
30
|
+
background-color: #FFF;
|
31
|
+
border-radius: 2px 2px 2px 2px;
|
32
|
+
}
|
33
|
+
|
34
|
+
.seven-tip .seven-tip-time {
|
35
|
+
background-color: #F3F6FA;
|
36
|
+
padding: 4px;
|
37
|
+
}
|
38
|
+
|
39
|
+
.seven-tip li {
|
40
|
+
padding: 2px 4px;
|
41
|
+
}
|
42
|
+
|
43
|
+
.seven-tip .color {
|
44
|
+
display: inline-block;
|
45
|
+
height: 12px;
|
46
|
+
margin-right: 4px;
|
47
|
+
vertical-align: text-top;
|
48
|
+
width: 12px;
|
49
|
+
}
|
50
|
+
|
51
|
+
.seven-scrubber {
|
52
|
+
top: 7px;
|
53
|
+
left: 0;
|
54
|
+
position: absolute;
|
55
|
+
width: 1px;
|
56
|
+
background: black;
|
57
|
+
z-index: 9000;
|
58
|
+
filter: progid:DXImageTransform.Microsoft.Alpha(Opacity=30);
|
59
|
+
opacity: 0.3;
|
60
|
+
-moz-box-shadow: 1px 0 0 rgba(255, 255, 255, 0.4);
|
61
|
+
-webkit-box-shadow: 1px 0 0 rgba(255, 255, 255, 0.4);
|
62
|
+
-o-box-shadow: 1px 0 0 rgba(255, 255, 255, 0.4);
|
63
|
+
box-shadow: 1px 0 0 rgba(255, 255, 255, 0.4);
|
64
|
+
}
|
65
|
+
|
66
|
+
.seven .novalue {
|
67
|
+
display: none;
|
68
|
+
}
|
@@ -0,0 +1,291 @@
|
|
1
|
+
(function($, window) {
|
2
|
+
|
3
|
+
// Throttles the calling of the callback function by delay milliseconds
|
4
|
+
function throttle(callback, delay) {
|
5
|
+
var lasttime = 0;
|
6
|
+
|
7
|
+
return function() {
|
8
|
+
var elapsed = +new Date() - lasttime
|
9
|
+
, args = arguments
|
10
|
+
, self = this;
|
11
|
+
|
12
|
+
if (elapsed > delay) {
|
13
|
+
lasttime = +new Date();
|
14
|
+
callback.apply(self, args);
|
15
|
+
}
|
16
|
+
}
|
17
|
+
};
|
18
|
+
|
19
|
+
function dateFor(startDate, granularityInMinutes, dataPoints) {
|
20
|
+
return new Date(+startDate + (granularityInMinutes * 60000 * (dataPoints - 1)));
|
21
|
+
}
|
22
|
+
|
23
|
+
var colors = d3.scale.ordinal().range("#007AD1 #469623 #EDDC00 #EB6D0E #BA0211 #572382 #029186 #9EA81D #F2A500 #E04107 #911258 #2144C7".split(' '));
|
24
|
+
|
25
|
+
window.Seven = function(selector, opts) {
|
26
|
+
this.selector = selector;
|
27
|
+
this.element = $(selector);
|
28
|
+
this.dataSets = {};
|
29
|
+
this.startDate = new Date(opts.start);
|
30
|
+
this.granularityInMinutes = opts.granularityInMinutes;
|
31
|
+
this.points = opts.points;
|
32
|
+
this.endDate = dateFor(this.startDate, this.granularityInMinutes, this.points);
|
33
|
+
this.kind = opts.kind || 'counter';
|
34
|
+
this.dateFormat = d3.time.format("%Y-%m-%d %H:%M:%S");
|
35
|
+
}
|
36
|
+
|
37
|
+
Seven.prototype = {
|
38
|
+
|
39
|
+
graph: function(opts) {
|
40
|
+
var self = this
|
41
|
+
, name = opts.name || "default"
|
42
|
+
;
|
43
|
+
|
44
|
+
// Save our data. Counters are named and there can be many. Timers take up the whole graph.
|
45
|
+
this.dataSets[name] = {data: opts.data};
|
46
|
+
if (this.kind == 'timer') {
|
47
|
+
this.timingData = opts.data;
|
48
|
+
}
|
49
|
+
|
50
|
+
// Empty out the graph.
|
51
|
+
// NOTE: improvement opportunity
|
52
|
+
this.element.empty();
|
53
|
+
|
54
|
+
this.graphWidth = this.element.width();
|
55
|
+
this.graphHeight = this.element.height();
|
56
|
+
|
57
|
+
var elD3 = d3.select(this.selector);
|
58
|
+
|
59
|
+
// Calculate max value in slightly different ways for counter vs timer
|
60
|
+
var maxValue = null;
|
61
|
+
if (this.kind == 'counter') {
|
62
|
+
var allData = [];
|
63
|
+
_.each(this.dataSets, function(v, k) {
|
64
|
+
_.each(v.data, function(d) {
|
65
|
+
allData.push(d);
|
66
|
+
})
|
67
|
+
});
|
68
|
+
maxValue = d3.max(allData);
|
69
|
+
} else if (this.kind == 'timer') {
|
70
|
+
var allData = [];
|
71
|
+
_.each(this.timingData, function(d) {
|
72
|
+
if (d.mean !== null) {
|
73
|
+
allData.push(d.mean + d.stddev);
|
74
|
+
}
|
75
|
+
})
|
76
|
+
maxValue = d3.max(allData);
|
77
|
+
}
|
78
|
+
|
79
|
+
// Calculate scales
|
80
|
+
this.xScale = d3.time.scale().domain([this.startDate, this.endDate]).range([7, this.graphWidth - 12]);
|
81
|
+
this.xScaleIndex = d3.time.scale().domain([this.startDate, this.endDate]).range([0, this.points - 1]);
|
82
|
+
this.yScale = d3.scale.linear().domain([0, maxValue]).range([7, this.graphHeight - 7]);
|
83
|
+
|
84
|
+
// Add in our svg element
|
85
|
+
var svg = elD3
|
86
|
+
.append('svg:svg')
|
87
|
+
.attr('class', 'seven ' + this.kind)
|
88
|
+
.attr("width", this.graphWidth)
|
89
|
+
.attr("height", this.graphHeight);
|
90
|
+
|
91
|
+
// Create a translating group
|
92
|
+
var svgGroup = svg.append("svg:g")
|
93
|
+
.attr("transform", "translate(" + 0 + "," + this.graphHeight + ")");
|
94
|
+
|
95
|
+
if (this.kind == 'counter') {
|
96
|
+
// Add the paths
|
97
|
+
var i = 0;
|
98
|
+
_.each(this.dataSets, function(datasetObj, datasetName) {
|
99
|
+
var datasetValues = datasetObj.data;
|
100
|
+
datasetObj.color = colors(i);
|
101
|
+
|
102
|
+
var line = d3.svg.line()
|
103
|
+
//.interpolate("basis")
|
104
|
+
.x(function(d, i) { return self.xScale(dateFor(self.startDate, self.granularityInMinutes, i + 1)); })
|
105
|
+
.y(function(d) { return -1 * self.yScale(d); });
|
106
|
+
|
107
|
+
var path = svgGroup.append("svg:path").attr("d", line(datasetValues))
|
108
|
+
.attr('stroke', datasetObj.color)
|
109
|
+
.attr('stroke-width', 1);
|
110
|
+
|
111
|
+
i += 1;
|
112
|
+
});
|
113
|
+
} else if (this.kind == 'timer') {
|
114
|
+
this.barWidth = (this.graphWidth - 19) / this.points;
|
115
|
+
this.barScale = d3.scale.linear().domain([0, 1]).range([0, this.barWidth]);
|
116
|
+
|
117
|
+
svgGroup.selectAll('rect.stddev')
|
118
|
+
.data(this.timingData)
|
119
|
+
.enter().append("rect")
|
120
|
+
.attr('class', function(d, i) { return d.mean !== null ? 'stddev' : 'novalue'; })
|
121
|
+
.attr("x", function(d, i) { return self.barScale(i) + 6.5; })
|
122
|
+
.attr("y", function(d) { return -self.yScale(d.mean + d.stddev) - .5; })
|
123
|
+
.attr("width", this.barWidth)
|
124
|
+
.attr("height", function(d) { return self.yScale(d.stddev * 2); })
|
125
|
+
.attr('fill', colors(0));
|
126
|
+
|
127
|
+
svgGroup.selectAll('rect.mean')
|
128
|
+
.data(this.timingData)
|
129
|
+
.enter().append("rect")
|
130
|
+
.attr('class', function(d, i) { return d.mean !== null ? 'mean' : 'novalue'; })
|
131
|
+
.attr("x", function(d, i) { return self.barScale(i) + 6.5; })
|
132
|
+
.attr("y", function(d) { return -self.yScale(d.mean) + 2; })
|
133
|
+
.attr("width", this.barWidth)
|
134
|
+
.attr("height", function(d) { return 2; })
|
135
|
+
.attr('fill', 'white');
|
136
|
+
}
|
137
|
+
|
138
|
+
// Draw axis
|
139
|
+
svgGroup.append("svg:line") // x-axis
|
140
|
+
.attr("x1", this.xScale(this.startDate) - 2)
|
141
|
+
.attr("y1", -1 * this.yScale(0) + 2.5)
|
142
|
+
.attr("x2", this.xScale(this.endDate))
|
143
|
+
.attr("y2", -1 * this.yScale(0) + 2.5)
|
144
|
+
.attr('stroke', '#AAA').attr('stroke-width', 1);
|
145
|
+
|
146
|
+
svgGroup.append("svg:line") // y-axis
|
147
|
+
.attr("x1", this.xScale(this.startDate) - 2.5)
|
148
|
+
.attr("y1", -1 * this.yScale(0) + 2)
|
149
|
+
.attr("x2", this.xScale(this.startDate) - 2.5)
|
150
|
+
.attr("y2", -1 * this.graphHeight - 7)
|
151
|
+
.attr('stroke', '#AAA').attr('stroke-width', 1);
|
152
|
+
|
153
|
+
// Handle the tool tip
|
154
|
+
this.element.bind('mousemove', throttle(function(e) {
|
155
|
+
var offset = self.element.offset()
|
156
|
+
, cx = e.pageX - offset.left
|
157
|
+
, cy = e.pageY - offset.top
|
158
|
+
;
|
159
|
+
|
160
|
+
self.showTip({
|
161
|
+
x: cx,
|
162
|
+
y: cy
|
163
|
+
});
|
164
|
+
}, 50));
|
165
|
+
|
166
|
+
this.element.bind('mouseleave', function(e) {
|
167
|
+
self.hideTip();
|
168
|
+
});
|
169
|
+
|
170
|
+
return this;
|
171
|
+
},
|
172
|
+
|
173
|
+
showTip: function(opts) {
|
174
|
+
var self = this
|
175
|
+
, html
|
176
|
+
, date = this.xScale.invert(opts.x)
|
177
|
+
, index = Math.round(this.xScaleIndex(date))
|
178
|
+
, nearestDate = this.xScaleIndex.invert(index)
|
179
|
+
, leftAnchor = this.xScale(nearestDate)
|
180
|
+
, tipRows = self.kind == 'counter' ? _.size(this.dataSets) : 7
|
181
|
+
, tipWidth
|
182
|
+
, tipHeight
|
183
|
+
, tipLeft
|
184
|
+
, tipTop
|
185
|
+
;
|
186
|
+
|
187
|
+
if (index < 0 || index >= this.points) {
|
188
|
+
this.hideTip();
|
189
|
+
return;
|
190
|
+
}
|
191
|
+
|
192
|
+
// Timers have a slightly different 'center' to align to the scrubber
|
193
|
+
if (this.kind == 'timer') {
|
194
|
+
leftAnchor = (index + 0.5) * this.barWidth + 6;
|
195
|
+
}
|
196
|
+
|
197
|
+
// Build the tip if it doesn't exist
|
198
|
+
if (!this.tip) {
|
199
|
+
html = [
|
200
|
+
'<div class="seven-tip">',
|
201
|
+
'<div class="seven-tip-inner">',
|
202
|
+
'<div class="seven-tip-time"></div>',
|
203
|
+
'<ul class="seven-tip-values"></ul>',
|
204
|
+
'</div>',
|
205
|
+
'</div>'
|
206
|
+
];
|
207
|
+
|
208
|
+
this.tip = $(html.join(''));
|
209
|
+
this.element.append(this.tip);
|
210
|
+
this.tipSide = 'right';
|
211
|
+
}
|
212
|
+
|
213
|
+
this.tip.find('.seven-tip-time').text(this.dateFormat(nearestDate));
|
214
|
+
|
215
|
+
this.tip.css({display: 'block'});
|
216
|
+
|
217
|
+
tipWidth = this.tip.width();
|
218
|
+
tipHeight = this.tip.height();
|
219
|
+
if (this.tipSide == 'right' && ((leftAnchor + 25 + tipWidth) >= this.graphWidth)) {
|
220
|
+
this.tipSide = 'left';
|
221
|
+
} else if (this.tipSide == 'left' && ((leftAnchor - 25 - tipWidth) <= 0)) {
|
222
|
+
this.tipSide = 'right';
|
223
|
+
}
|
224
|
+
|
225
|
+
if (this.tipSide == 'right') {
|
226
|
+
tipLeft = leftAnchor + 20;
|
227
|
+
} else {
|
228
|
+
tipLeft = leftAnchor - 20 - tipWidth;
|
229
|
+
}
|
230
|
+
|
231
|
+
tipTop = opts.y - 10;
|
232
|
+
if ((tipTop + tipHeight + 10) > this.graphHeight) {
|
233
|
+
tipTop = this.graphHeight - tipHeight - 10;
|
234
|
+
}
|
235
|
+
|
236
|
+
if (tipTop < 0) {
|
237
|
+
tipTop = 5;
|
238
|
+
}
|
239
|
+
|
240
|
+
this.tip.css({
|
241
|
+
top: tipTop,
|
242
|
+
left: tipLeft
|
243
|
+
});
|
244
|
+
|
245
|
+
html = [];
|
246
|
+
_.each(this.dataSets, function(datasetObj, datasetName) {
|
247
|
+
var datasetValues = datasetObj.data
|
248
|
+
|
249
|
+
if (self.kind == 'counter') {
|
250
|
+
var n = datasetValues[index];
|
251
|
+
if (n !== null) n = Math.round(n * 100) / 100;
|
252
|
+
html.push([
|
253
|
+
'<li>',
|
254
|
+
'<span class="key"><span class="color" style="background-color:' + datasetObj.color + '"></span>' + datasetName + ': </span>',
|
255
|
+
'<span class="value">' + n + '</span>',
|
256
|
+
'</li>'
|
257
|
+
].join(''));
|
258
|
+
} else if (self.kind == 'timer') {
|
259
|
+
var t = datasetValues[index]
|
260
|
+
, fields = ['count', 'mean', 'median', 'stddev', 'min', 'max', 'sum']
|
261
|
+
;
|
262
|
+
|
263
|
+
_.each(fields, function(f) {
|
264
|
+
var n = t[f];
|
265
|
+
if (n !== null) n = Math.round(n * 100) / 100;
|
266
|
+
html.push('<li><span class="key">' + f + ': </span><span class="value">' + n + '</span></li>');
|
267
|
+
});
|
268
|
+
}
|
269
|
+
});
|
270
|
+
this.tip.find('.seven-tip-values').html(html.join(''));
|
271
|
+
|
272
|
+
if (!this.scrubber) {
|
273
|
+
html = '<div class="seven-scrubber"></div>'
|
274
|
+
this.scrubber = $(html);
|
275
|
+
this.element.append(this.scrubber);
|
276
|
+
}
|
277
|
+
this.scrubber.css({display: 'block', left: leftAnchor, height: this.graphHeight - 14});
|
278
|
+
},
|
279
|
+
|
280
|
+
hideTip: function() {
|
281
|
+
if (this.tip) {
|
282
|
+
this.tip.css({display: 'none'});
|
283
|
+
}
|
284
|
+
if (this.scrubber) {
|
285
|
+
this.scrubber.css({display: 'none'});
|
286
|
+
}
|
287
|
+
}
|
288
|
+
|
289
|
+
};
|
290
|
+
|
291
|
+
}(jQuery, this));
|