mouth 0.8.0

Sign up to get free protection for your applications and to get access to all the features.
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));