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.
Files changed (41) hide show
  1. data/.gitignore +7 -0
  2. data/Capfile +26 -0
  3. data/Gemfile +3 -0
  4. data/MIT-LICENSE +20 -0
  5. data/README.md +138 -0
  6. data/Rakefile +19 -0
  7. data/TODO +32 -0
  8. data/bin/mouth +77 -0
  9. data/bin/mouth-console +18 -0
  10. data/bin/mouth-endoscope +18 -0
  11. data/lib/mouth.rb +61 -0
  12. data/lib/mouth/dashboard.rb +25 -0
  13. data/lib/mouth/endoscope.rb +120 -0
  14. data/lib/mouth/endoscope/public/222222_256x240_icons_icons.png +0 -0
  15. data/lib/mouth/endoscope/public/application.css +464 -0
  16. data/lib/mouth/endoscope/public/application.js +938 -0
  17. data/lib/mouth/endoscope/public/backbone.js +1158 -0
  18. data/lib/mouth/endoscope/public/d3.js +4707 -0
  19. data/lib/mouth/endoscope/public/d3.time.js +687 -0
  20. data/lib/mouth/endoscope/public/jquery-ui-1.8.16.custom.min.js +177 -0
  21. data/lib/mouth/endoscope/public/jquery.js +4 -0
  22. data/lib/mouth/endoscope/public/json2.js +480 -0
  23. data/lib/mouth/endoscope/public/keymaster.js +163 -0
  24. data/lib/mouth/endoscope/public/linen.js +46 -0
  25. data/lib/mouth/endoscope/public/seven.css +68 -0
  26. data/lib/mouth/endoscope/public/seven.js +291 -0
  27. data/lib/mouth/endoscope/public/underscore.js +931 -0
  28. data/lib/mouth/endoscope/views/dashboard.erb +67 -0
  29. data/lib/mouth/graph.rb +58 -0
  30. data/lib/mouth/instrument.rb +56 -0
  31. data/lib/mouth/record.rb +72 -0
  32. data/lib/mouth/runner.rb +89 -0
  33. data/lib/mouth/sequence.rb +284 -0
  34. data/lib/mouth/source.rb +76 -0
  35. data/lib/mouth/sucker.rb +235 -0
  36. data/lib/mouth/version.rb +3 -0
  37. data/mouth.gemspec +28 -0
  38. data/test/sequence_test.rb +163 -0
  39. data/test/sucker_test.rb +55 -0
  40. data/test/test_helper.rb +5 -0
  41. 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));