mindreframer-riemann-dash 0.2.3

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 (55) hide show
  1. data/.gitignore +8 -0
  2. data/Gemfile +7 -0
  3. data/Gemfile.lock +52 -0
  4. data/LICENSE +21 -0
  5. data/README.markdown +52 -0
  6. data/Rakefile.rb +11 -0
  7. data/bin/riemann-dash +7 -0
  8. data/example/config.rb +17 -0
  9. data/lib/riemann/dash.rb +5 -0
  10. data/lib/riemann/dash/app.rb +32 -0
  11. data/lib/riemann/dash/config.rb +154 -0
  12. data/lib/riemann/dash/controller/css.rb +5 -0
  13. data/lib/riemann/dash/controller/index.rb +20 -0
  14. data/lib/riemann/dash/public/clock.js +45 -0
  15. data/lib/riemann/dash/public/dash.js +287 -0
  16. data/lib/riemann/dash/public/format.js +24 -0
  17. data/lib/riemann/dash/public/jquery-1.7.2.min.js +4 -0
  18. data/lib/riemann/dash/public/jquery-ui-1.9.0.custom.min.js +6 -0
  19. data/lib/riemann/dash/public/jquery.json-2.2.min.js +31 -0
  20. data/lib/riemann/dash/public/jquery.quickfit.js +144 -0
  21. data/lib/riemann/dash/public/jquery.simplemodal.1.4.3.min.js +26 -0
  22. data/lib/riemann/dash/public/keys.js +46 -0
  23. data/lib/riemann/dash/public/mustache.js +597 -0
  24. data/lib/riemann/dash/public/persistence.js +30 -0
  25. data/lib/riemann/dash/public/profile.js +33 -0
  26. data/lib/riemann/dash/public/subs.js +164 -0
  27. data/lib/riemann/dash/public/toastr.css +174 -0
  28. data/lib/riemann/dash/public/toastr.js +207 -0
  29. data/lib/riemann/dash/public/toolbar.js +217 -0
  30. data/lib/riemann/dash/public/underscore-min.js +5 -0
  31. data/lib/riemann/dash/public/util.js +34 -0
  32. data/lib/riemann/dash/public/vendor/smoothie.js +374 -0
  33. data/lib/riemann/dash/public/view.js +704 -0
  34. data/lib/riemann/dash/public/views/gauge.js +76 -0
  35. data/lib/riemann/dash/public/views/grid.js +279 -0
  36. data/lib/riemann/dash/public/views/help.js +28 -0
  37. data/lib/riemann/dash/public/views/timeseries.js +107 -0
  38. data/lib/riemann/dash/public/views/title.js +35 -0
  39. data/lib/riemann/dash/public/x.png +0 -0
  40. data/lib/riemann/dash/rack/static.rb +16 -0
  41. data/lib/riemann/dash/version.rb +4 -0
  42. data/lib/riemann/dash/views/css.scss +393 -0
  43. data/lib/riemann/dash/views/index.erubis +203 -0
  44. data/lib/riemann/dash/views/layout.erubis +21 -0
  45. data/riemann-dash.gemspec +28 -0
  46. data/sh/c +1 -0
  47. data/sh/env.rb +2 -0
  48. data/sh/test +1 -0
  49. data/test/config_test.rb +106 -0
  50. data/test/fixtures/config/basic_config.rb +2 -0
  51. data/test/fixtures/config/ws_config.rb +1 -0
  52. data/test/fixtures/ws_config/dummy_config.json +1 -0
  53. data/test/fixtures/ws_config/pretty_printed_config.json +6 -0
  54. data/test/test_helper.rb +10 -0
  55. metadata +202 -0
@@ -0,0 +1,217 @@
1
+ var toolbar = (function() {
2
+ // Build UI
3
+ var toolbar = $('#toolbar');
4
+ var form = $('<form/>');
5
+ toolbar.append(form);
6
+
7
+ var pager = $('<div class="pager">');
8
+ var load = $('<div class="load"><div class="bar load1" /><div class="bar load5" /><span title="1- and 5-second subscription manager load averages">Load</span></div>');
9
+ var server = $('<input class="server" type="text" name="text">');
10
+ form.append(pager);
11
+ form.append(server);
12
+ form.append(load);
13
+ form.submit(function(e) {
14
+ return false;
15
+ });
16
+
17
+ // Load /////////////////////////////////////////////////////////////////////
18
+
19
+ window.setInterval(function() {
20
+ load.find('span').text("Load " +
21
+ format.float(subs.load1()) + ', ' +
22
+ format.float(subs.load5()));
23
+ load.find(".load1").animate({width: (subs.load1() * 100) + "%"}, 200);
24
+ load.find(".load5").animate({width: (subs.load5() * 100) + "%"}, 1000);
25
+ }, 1000);
26
+
27
+ // Server ///////////////////////////////////////////////////////////////////
28
+
29
+ // Callbacks
30
+ var onServerChangeCallbacks = [];
31
+
32
+ // React to server being set.
33
+ var onServerChange = function(callback) {
34
+ onServerChangeCallbacks.push(callback);
35
+ }
36
+
37
+ // When server is set, call callbacks.
38
+ server.change(function() {
39
+ onServerChangeCallbacks.forEach(function(f) {
40
+ f(server.val());
41
+ });
42
+ server.blur();
43
+ });
44
+
45
+ // Suppress keybindings
46
+ server.focus(keys.disable);
47
+ server.blur(keys.enable);
48
+
49
+ // Pager ////////////////////////////////////////////////////////////////////
50
+
51
+ var onWorkspaceChangeCallbacks = [];
52
+ var onWorkspaceReorderCallbacks = [];
53
+ var onWorkspaceSwitchCallbacks = [];
54
+ var onWorkspaceAddCallbacks = [];
55
+ var onWorkspaceDeleteCallbacks = [];
56
+ var onWorkspaceChange = function(callback) {
57
+ onWorkspaceChangeCallbacks.push(callback);
58
+ };
59
+ var onWorkspaceReorder = function(callback) {
60
+ onWorkspaceReorderCallbacks.push(callback);
61
+ }
62
+ var onWorkspaceSwitch = function(callback) {
63
+ onWorkspaceSwitchCallbacks.push(callback);
64
+ }
65
+ var onWorkspaceAdd = function(callback) {
66
+ onWorkspaceAddCallbacks.push(callback);
67
+ }
68
+ var onWorkspaceDelete = function(callback) {
69
+ onWorkspaceDeleteCallbacks.push(callback);
70
+ }
71
+
72
+ // Set workspaces.
73
+ var workspaces = function(workspaces) {
74
+ pager.empty();
75
+
76
+ // Workspaces
77
+ var workspaceList = $('<ol>');
78
+ pager.append(workspaceList);
79
+
80
+ workspaces.forEach(function(workspace) {
81
+ workspaceList.append(workspaceTile(workspace));
82
+ });
83
+
84
+ // Reordering
85
+ workspaceList.sortable({
86
+ axis: "x",
87
+ containment: pager,
88
+ delay: 20,
89
+ distance: 4,
90
+ tolerance: "intersect",
91
+ update: function() {
92
+ console.log("hi");
93
+ var ids = workspaceList.find('li').map(function() {
94
+ return $(this).data('workspaceId');
95
+ });
96
+ console.log("New ids are: ", ids);
97
+ onWorkspaceReorderCallbacks.forEach(function(f) {
98
+ f(ids);
99
+ });
100
+ }
101
+ });
102
+
103
+ // New button
104
+ var add = $('<div class="add button">+</div>');
105
+ add.click(function() {
106
+ onWorkspaceAddCallbacks.forEach(function(f) {
107
+ f();
108
+ });
109
+ });
110
+
111
+ pager.append(add);
112
+ };
113
+
114
+ // Returns a tile for a workspace.
115
+ var workspaceTile = function(workspace) {
116
+ var tile = $('<li class="button" />');
117
+ tile.text(workspace.name);
118
+ tile.data('workspaceId', workspace.id);
119
+ // tile.disableTextSelect();
120
+
121
+ // Switch to this workspace.
122
+ tile.click(function() {
123
+ if (! tile.hasClass("current")) {
124
+ onWorkspaceSwitchCallbacks.forEach(function(f) {
125
+ f(workspace);
126
+ });
127
+ }
128
+ });
129
+
130
+ // Edit this workspace name.
131
+ tile.dblclick(function() {
132
+ namer = workspaceNamer(workspace);
133
+ keys.disable();
134
+ tile.replaceWith(namer);
135
+ namer.focus();
136
+ });
137
+
138
+ // Delete
139
+ var del = $('<div class="delete">×</div>');
140
+ del.click(function() {
141
+ onWorkspaceDeleteCallbacks.forEach(function(f) {
142
+ f(workspace);
143
+ });
144
+ });
145
+ tile.append(del);
146
+
147
+ return tile;
148
+ }
149
+
150
+ // A box to rename a workspace.
151
+ var workspaceNamer = function(workspace) {
152
+ var field = $('<input type="text" />');
153
+ field.val(workspace.name);
154
+
155
+ // Change the workspace, firing callbacks and replacing the pager tile.
156
+ var submit = function(w2) {
157
+ onWorkspaceChangeCallbacks.forEach(function(f) {
158
+ f(workspace, w2);
159
+ });
160
+
161
+ keys.enable();
162
+ }
163
+
164
+ // When the input changes, change the workspace.
165
+ field.change(function() {
166
+ var newWorkspace = _.clone(workspace);
167
+ newWorkspace.name = field.val();
168
+ submit(newWorkspace);
169
+ });
170
+
171
+ // When we leave focus, revert.
172
+ field.blur(function() { submit(workspace) });
173
+ field.keydown(function(e) {
174
+ if (e.which === 13) {
175
+ field.change();
176
+ } else if (e.which === 27) {
177
+ submit(workspace);
178
+ }
179
+ });
180
+
181
+ return field;
182
+ }
183
+
184
+ // Focus a workspace.
185
+ var workspace = function(workspace) {
186
+ console.log("Switching to workspace", workspace);
187
+ pager.find('li').removeClass('current');
188
+ if (workspace === null) {
189
+ return;
190
+ }
191
+
192
+ pager.find('li').each(function(i, el) {
193
+ if ($(el).data('workspaceId') === workspace.id) {
194
+ $(el).addClass('current');
195
+ }
196
+ });
197
+ }
198
+
199
+ return {
200
+ server: function(s) {
201
+ if (s === undefined) {
202
+ return server.val();
203
+ } else {
204
+ server.val(s);
205
+ return s;
206
+ }
207
+ },
208
+ onServerChange: onServerChange,
209
+ onWorkspaceChange: onWorkspaceChange,
210
+ onWorkspaceReorder: onWorkspaceReorder,
211
+ onWorkspaceSwitch: onWorkspaceSwitch,
212
+ onWorkspaceAdd: onWorkspaceAdd,
213
+ onWorkspaceDelete: onWorkspaceDelete,
214
+ workspaces: workspaces,
215
+ workspace: workspace
216
+ }
217
+ })();
@@ -0,0 +1,5 @@
1
+ // Underscore.js 1.4.2
2
+ // http://underscorejs.org
3
+ // (c) 2009-2012 Jeremy Ashkenas, DocumentCloud Inc.
4
+ // Underscore may be freely distributed under the MIT license.
5
+ (function(){var e=this,t=e._,n={},r=Array.prototype,i=Object.prototype,s=Function.prototype,o=r.push,u=r.slice,a=r.concat,f=r.unshift,l=i.toString,c=i.hasOwnProperty,h=r.forEach,p=r.map,d=r.reduce,v=r.reduceRight,m=r.filter,g=r.every,y=r.some,b=r.indexOf,w=r.lastIndexOf,E=Array.isArray,S=Object.keys,x=s.bind,T=function(e){if(e instanceof T)return e;if(!(this instanceof T))return new T(e);this._wrapped=e};typeof exports!="undefined"?(typeof module!="undefined"&&module.exports&&(exports=module.exports=T),exports._=T):e._=T,T.VERSION="1.4.2";var N=T.each=T.forEach=function(e,t,r){if(e==null)return;if(h&&e.forEach===h)e.forEach(t,r);else if(e.length===+e.length){for(var i=0,s=e.length;i<s;i++)if(t.call(r,e[i],i,e)===n)return}else for(var o in e)if(T.has(e,o)&&t.call(r,e[o],o,e)===n)return};T.map=T.collect=function(e,t,n){var r=[];return e==null?r:p&&e.map===p?e.map(t,n):(N(e,function(e,i,s){r[r.length]=t.call(n,e,i,s)}),r)},T.reduce=T.foldl=T.inject=function(e,t,n,r){var i=arguments.length>2;e==null&&(e=[]);if(d&&e.reduce===d)return r&&(t=T.bind(t,r)),i?e.reduce(t,n):e.reduce(t);N(e,function(e,s,o){i?n=t.call(r,n,e,s,o):(n=e,i=!0)});if(!i)throw new TypeError("Reduce of empty array with no initial value");return n},T.reduceRight=T.foldr=function(e,t,n,r){var i=arguments.length>2;e==null&&(e=[]);if(v&&e.reduceRight===v)return r&&(t=T.bind(t,r)),arguments.length>2?e.reduceRight(t,n):e.reduceRight(t);var s=e.length;if(s!==+s){var o=T.keys(e);s=o.length}N(e,function(u,a,f){a=o?o[--s]:--s,i?n=t.call(r,n,e[a],a,f):(n=e[a],i=!0)});if(!i)throw new TypeError("Reduce of empty array with no initial value");return n},T.find=T.detect=function(e,t,n){var r;return C(e,function(e,i,s){if(t.call(n,e,i,s))return r=e,!0}),r},T.filter=T.select=function(e,t,n){var r=[];return e==null?r:m&&e.filter===m?e.filter(t,n):(N(e,function(e,i,s){t.call(n,e,i,s)&&(r[r.length]=e)}),r)},T.reject=function(e,t,n){var r=[];return e==null?r:(N(e,function(e,i,s){t.call(n,e,i,s)||(r[r.length]=e)}),r)},T.every=T.all=function(e,t,r){t||(t=T.identity);var i=!0;return e==null?i:g&&e.every===g?e.every(t,r):(N(e,function(e,s,o){if(!(i=i&&t.call(r,e,s,o)))return n}),!!i)};var C=T.some=T.any=function(e,t,r){t||(t=T.identity);var i=!1;return e==null?i:y&&e.some===y?e.some(t,r):(N(e,function(e,s,o){if(i||(i=t.call(r,e,s,o)))return n}),!!i)};T.contains=T.include=function(e,t){var n=!1;return e==null?n:b&&e.indexOf===b?e.indexOf(t)!=-1:(n=C(e,function(e){return e===t}),n)},T.invoke=function(e,t){var n=u.call(arguments,2);return T.map(e,function(e){return(T.isFunction(t)?t:e[t]).apply(e,n)})},T.pluck=function(e,t){return T.map(e,function(e){return e[t]})},T.where=function(e,t){return T.isEmpty(t)?[]:T.filter(e,function(e){for(var n in t)if(t[n]!==e[n])return!1;return!0})},T.max=function(e,t,n){if(!t&&T.isArray(e)&&e[0]===+e[0]&&e.length<65535)return Math.max.apply(Math,e);if(!t&&T.isEmpty(e))return-Infinity;var r={computed:-Infinity};return N(e,function(e,i,s){var o=t?t.call(n,e,i,s):e;o>=r.computed&&(r={value:e,computed:o})}),r.value},T.min=function(e,t,n){if(!t&&T.isArray(e)&&e[0]===+e[0]&&e.length<65535)return Math.min.apply(Math,e);if(!t&&T.isEmpty(e))return Infinity;var r={computed:Infinity};return N(e,function(e,i,s){var o=t?t.call(n,e,i,s):e;o<r.computed&&(r={value:e,computed:o})}),r.value},T.shuffle=function(e){var t,n=0,r=[];return N(e,function(e){t=T.random(n++),r[n-1]=r[t],r[t]=e}),r};var k=function(e){return T.isFunction(e)?e:function(t){return t[e]}};T.sortBy=function(e,t,n){var r=k(t);return T.pluck(T.map(e,function(e,t,i){return{value:e,index:t,criteria:r.call(n,e,t,i)}}).sort(function(e,t){var n=e.criteria,r=t.criteria;if(n!==r){if(n>r||n===void 0)return 1;if(n<r||r===void 0)return-1}return e.index<t.index?-1:1}),"value")};var L=function(e,t,n,r){var i={},s=k(t);return N(e,function(t,o){var u=s.call(n,t,o,e);r(i,u,t)}),i};T.groupBy=function(e,t,n){return L(e,t,n,function(e,t,n){(T.has(e,t)?e[t]:e[t]=[]).push(n)})},T.countBy=function(e,t,n){return L(e,t,n,function(e,t,n){T.has(e,t)||(e[t]=0),e[t]++})},T.sortedIndex=function(e,t,n,r){n=n==null?T.identity:k(n);var i=n.call(r,t),s=0,o=e.length;while(s<o){var u=s+o>>>1;n.call(r,e[u])<i?s=u+1:o=u}return s},T.toArray=function(e){return e?e.length===+e.length?u.call(e):T.values(e):[]},T.size=function(e){return e.length===+e.length?e.length:T.keys(e).length},T.first=T.head=T.take=function(e,t,n){return t!=null&&!n?u.call(e,0,t):e[0]},T.initial=function(e,t,n){return u.call(e,0,e.length-(t==null||n?1:t))},T.last=function(e,t,n){return t!=null&&!n?u.call(e,Math.max(e.length-t,0)):e[e.length-1]},T.rest=T.tail=T.drop=function(e,t,n){return u.call(e,t==null||n?1:t)},T.compact=function(e){return T.filter(e,function(e){return!!e})};var A=function(e,t,n){return N(e,function(e){T.isArray(e)?t?o.apply(n,e):A(e,t,n):n.push(e)}),n};T.flatten=function(e,t){return A(e,t,[])},T.without=function(e){return T.difference(e,u.call(arguments,1))},T.uniq=T.unique=function(e,t,n,r){var i=n?T.map(e,n,r):e,s=[],o=[];return N(i,function(n,r){if(t?!r||o[o.length-1]!==n:!T.contains(o,n))o.push(n),s.push(e[r])}),s},T.union=function(){return T.uniq(a.apply(r,arguments))},T.intersection=function(e){var t=u.call(arguments,1);return T.filter(T.uniq(e),function(e){return T.every(t,function(t){return T.indexOf(t,e)>=0})})},T.difference=function(e){var t=a.apply(r,u.call(arguments,1));return T.filter(e,function(e){return!T.contains(t,e)})},T.zip=function(){var e=u.call(arguments),t=T.max(T.pluck(e,"length")),n=new Array(t);for(var r=0;r<t;r++)n[r]=T.pluck(e,""+r);return n},T.object=function(e,t){var n={};for(var r=0,i=e.length;r<i;r++)t?n[e[r]]=t[r]:n[e[r][0]]=e[r][1];return n},T.indexOf=function(e,t,n){if(e==null)return-1;var r=0,i=e.length;if(n){if(typeof n!="number")return r=T.sortedIndex(e,t),e[r]===t?r:-1;r=n<0?Math.max(0,i+n):n}if(b&&e.indexOf===b)return e.indexOf(t,n);for(;r<i;r++)if(e[r]===t)return r;return-1},T.lastIndexOf=function(e,t,n){if(e==null)return-1;var r=n!=null;if(w&&e.lastIndexOf===w)return r?e.lastIndexOf(t,n):e.lastIndexOf(t);var i=r?n:e.length;while(i--)if(e[i]===t)return i;return-1},T.range=function(e,t,n){arguments.length<=1&&(t=e||0,e=0),n=arguments[2]||1;var r=Math.max(Math.ceil((t-e)/n),0),i=0,s=new Array(r);while(i<r)s[i++]=e,e+=n;return s};var O=function(){};T.bind=function(t,n){var r,i;if(t.bind===x&&x)return x.apply(t,u.call(arguments,1));if(!T.isFunction(t))throw new TypeError;return i=u.call(arguments,2),r=function(){if(this instanceof r){O.prototype=t.prototype;var e=new O,s=t.apply(e,i.concat(u.call(arguments)));return Object(s)===s?s:e}return t.apply(n,i.concat(u.call(arguments)))}},T.bindAll=function(e){var t=u.call(arguments,1);return t.length==0&&(t=T.functions(e)),N(t,function(t){e[t]=T.bind(e[t],e)}),e},T.memoize=function(e,t){var n={};return t||(t=T.identity),function(){var r=t.apply(this,arguments);return T.has(n,r)?n[r]:n[r]=e.apply(this,arguments)}},T.delay=function(e,t){var n=u.call(arguments,2);return setTimeout(function(){return e.apply(null,n)},t)},T.defer=function(e){return T.delay.apply(T,[e,1].concat(u.call(arguments,1)))},T.throttle=function(e,t){var n,r,i,s,o,u,a=T.debounce(function(){o=s=!1},t);return function(){n=this,r=arguments;var f=function(){i=null,o&&(u=e.apply(n,r)),a()};return i||(i=setTimeout(f,t)),s?o=!0:(s=!0,u=e.apply(n,r)),a(),u}},T.debounce=function(e,t,n){var r,i;return function(){var s=this,o=arguments,u=function(){r=null,n||(i=e.apply(s,o))},a=n&&!r;return clearTimeout(r),r=setTimeout(u,t),a&&(i=e.apply(s,o)),i}},T.once=function(e){var t=!1,n;return function(){return t?n:(t=!0,n=e.apply(this,arguments),e=null,n)}},T.wrap=function(e,t){return function(){var n=[e];return o.apply(n,arguments),t.apply(this,n)}},T.compose=function(){var e=arguments;return function(){var t=arguments;for(var n=e.length-1;n>=0;n--)t=[e[n].apply(this,t)];return t[0]}},T.after=function(e,t){return e<=0?t():function(){if(--e<1)return t.apply(this,arguments)}},T.keys=S||function(e){if(e!==Object(e))throw new TypeError("Invalid object");var t=[];for(var n in e)T.has(e,n)&&(t[t.length]=n);return t},T.values=function(e){var t=[];for(var n in e)T.has(e,n)&&t.push(e[n]);return t},T.pairs=function(e){var t=[];for(var n in e)T.has(e,n)&&t.push([n,e[n]]);return t},T.invert=function(e){var t={};for(var n in e)T.has(e,n)&&(t[e[n]]=n);return t},T.functions=T.methods=function(e){var t=[];for(var n in e)T.isFunction(e[n])&&t.push(n);return t.sort()},T.extend=function(e){return N(u.call(arguments,1),function(t){for(var n in t)e[n]=t[n]}),e},T.pick=function(e){var t={},n=a.apply(r,u.call(arguments,1));return N(n,function(n){n in e&&(t[n]=e[n])}),t},T.omit=function(e){var t={},n=a.apply(r,u.call(arguments,1));for(var i in e)T.contains(n,i)||(t[i]=e[i]);return t},T.defaults=function(e){return N(u.call(arguments,1),function(t){for(var n in t)e[n]==null&&(e[n]=t[n])}),e},T.clone=function(e){return T.isObject(e)?T.isArray(e)?e.slice():T.extend({},e):e},T.tap=function(e,t){return t(e),e};var M=function(e,t,n,r){if(e===t)return e!==0||1/e==1/t;if(e==null||t==null)return e===t;e instanceof T&&(e=e._wrapped),t instanceof T&&(t=t._wrapped);var i=l.call(e);if(i!=l.call(t))return!1;switch(i){case"[object String]":return e==String(t);case"[object Number]":return e!=+e?t!=+t:e==0?1/e==1/t:e==+t;case"[object Date]":case"[object Boolean]":return+e==+t;case"[object RegExp]":return e.source==t.source&&e.global==t.global&&e.multiline==t.multiline&&e.ignoreCase==t.ignoreCase}if(typeof e!="object"||typeof t!="object")return!1;var s=n.length;while(s--)if(n[s]==e)return r[s]==t;n.push(e),r.push(t);var o=0,u=!0;if(i=="[object Array]"){o=e.length,u=o==t.length;if(u)while(o--)if(!(u=M(e[o],t[o],n,r)))break}else{var a=e.constructor,f=t.constructor;if(a!==f&&!(T.isFunction(a)&&a instanceof a&&T.isFunction(f)&&f instanceof f))return!1;for(var c in e)if(T.has(e,c)){o++;if(!(u=T.has(t,c)&&M(e[c],t[c],n,r)))break}if(u){for(c in t)if(T.has(t,c)&&!(o--))break;u=!o}}return n.pop(),r.pop(),u};T.isEqual=function(e,t){return M(e,t,[],[])},T.isEmpty=function(e){if(e==null)return!0;if(T.isArray(e)||T.isString(e))return e.length===0;for(var t in e)if(T.has(e,t))return!1;return!0},T.isElement=function(e){return!!e&&e.nodeType===1},T.isArray=E||function(e){return l.call(e)=="[object Array]"},T.isObject=function(e){return e===Object(e)},N(["Arguments","Function","String","Number","Date","RegExp"],function(e){T["is"+e]=function(t){return l.call(t)=="[object "+e+"]"}}),T.isArguments(arguments)||(T.isArguments=function(e){return!!e&&!!T.has(e,"callee")}),typeof /./!="function"&&(T.isFunction=function(e){return typeof e=="function"}),T.isFinite=function(e){return T.isNumber(e)&&isFinite(e)},T.isNaN=function(e){return T.isNumber(e)&&e!=+e},T.isBoolean=function(e){return e===!0||e===!1||l.call(e)=="[object Boolean]"},T.isNull=function(e){return e===null},T.isUndefined=function(e){return e===void 0},T.has=function(e,t){return c.call(e,t)},T.noConflict=function(){return e._=t,this},T.identity=function(e){return e},T.times=function(e,t,n){for(var r=0;r<e;r++)t.call(n,r)},T.random=function(e,t){return t==null&&(t=e,e=0),e+(0|Math.random()*(t-e+1))};var _={escape:{"&":"&amp;","<":"&lt;",">":"&gt;",'"':"&quot;","'":"&#x27;","/":"&#x2F;"}};_.unescape=T.invert(_.escape);var D={escape:new RegExp("["+T.keys(_.escape).join("")+"]","g"),unescape:new RegExp("("+T.keys(_.unescape).join("|")+")","g")};T.each(["escape","unescape"],function(e){T[e]=function(t){return t==null?"":(""+t).replace(D[e],function(t){return _[e][t]})}}),T.result=function(e,t){if(e==null)return null;var n=e[t];return T.isFunction(n)?n.call(e):n},T.mixin=function(e){N(T.functions(e),function(t){var n=T[t]=e[t];T.prototype[t]=function(){var e=[this._wrapped];return o.apply(e,arguments),F.call(this,n.apply(T,e))}})};var P=0;T.uniqueId=function(e){var t=P++;return e?e+t:t},T.templateSettings={evaluate:/<%([\s\S]+?)%>/g,interpolate:/<%=([\s\S]+?)%>/g,escape:/<%-([\s\S]+?)%>/g};var H=/(.)^/,B={"'":"'","\\":"\\","\r":"r","\n":"n"," ":"t","\u2028":"u2028","\u2029":"u2029"},j=/\\|'|\r|\n|\t|\u2028|\u2029/g;T.template=function(e,t,n){n=T.defaults({},n,T.templateSettings);var r=new RegExp([(n.escape||H).source,(n.interpolate||H).source,(n.evaluate||H).source].join("|")+"|$","g"),i=0,s="__p+='";e.replace(r,function(t,n,r,o,u){s+=e.slice(i,u).replace(j,function(e){return"\\"+B[e]}),s+=n?"'+\n((__t=("+n+"))==null?'':_.escape(__t))+\n'":r?"'+\n((__t=("+r+"))==null?'':__t)+\n'":o?"';\n"+o+"\n__p+='":"",i=u+t.length}),s+="';\n",n.variable||(s="with(obj||{}){\n"+s+"}\n"),s="var __t,__p='',__j=Array.prototype.join,print=function(){__p+=__j.call(arguments,'');};\n"+s+"return __p;\n";try{var o=new Function(n.variable||"obj","_",s)}catch(u){throw u.source=s,u}if(t)return o(t,T);var a=function(e){return o.call(this,e,T)};return a.source="function("+(n.variable||"obj")+"){\n"+s+"}",a},T.chain=function(e){return T(e).chain()};var F=function(e){return this._chain?T(e).chain():e};T.mixin(T),N(["pop","push","reverse","shift","sort","splice","unshift"],function(e){var t=r[e];T.prototype[e]=function(){var n=this._wrapped;return t.apply(n,arguments),(e=="shift"||e=="splice")&&n.length===0&&delete n[0],F.call(this,n)}}),N(["concat","join","slice"],function(e){var t=r[e];T.prototype[e]=function(){return F.call(this,t.apply(this._wrapped,arguments))}}),T.extend(T.prototype,{chain:function(){return this._chain=!0,this},value:function(){return this._wrapped}})}).call(this);
@@ -0,0 +1,34 @@
1
+ var util = (function() {
2
+ return {
3
+ merge: function(m1, m2) {
4
+ // Merge two maps nondestructively.
5
+ return _.extend({}, m1, m2)
6
+ },
7
+ slur: function(period, f) {
8
+ // Wraps a function in another, which calls f at most once every period
9
+ // milliseconds. Tries to minimize latency.
10
+ return _.throttle(f, period)
11
+ },
12
+ uniqueId: function(length) {
13
+ // Unique-ish IDs as a length sized string of hex
14
+ var id = '', hex = '0123456789abcdef';
15
+ _(length || 40).times(function() { id += hex[_.random(15)]; });
16
+ return id;
17
+ }
18
+ };
19
+ })();
20
+
21
+ $(function() {
22
+ // Allow disabling text selection.
23
+ $.extend($.fn.disableTextSelect = function() {
24
+ return this.each(function(){
25
+ if($.browser.mozilla){//Firefox
26
+ $(this).css('MozUserSelect','none');
27
+ }else if($.browser.msie){//IE
28
+ $(this).bind('selectstart',function(){return false;});
29
+ }else{//Opera, etc.
30
+ $(this).mousedown(function(){return false;});
31
+ }
32
+ });
33
+ });
34
+ });
@@ -0,0 +1,374 @@
1
+ // MIT License:
2
+ //
3
+ // Copyright (c) 2010-2011, Joe Walnes
4
+ //
5
+ // Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ // of this software and associated documentation files (the "Software"), to deal
7
+ // in the Software without restriction, including without limitation the rights
8
+ // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ // copies of the Software, and to permit persons to whom the Software is
10
+ // furnished to do so, subject to the following conditions:
11
+ //
12
+ // The above copyright notice and this permission notice shall be included in
13
+ // all copies or substantial portions of the Software.
14
+ //
15
+ // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
+ // THE SOFTWARE.
22
+
23
+ /**
24
+ * Smoothie Charts - http://smoothiecharts.org/
25
+ * (c) 2010-2012, Joe Walnes
26
+ *
27
+ * v1.0: Main charting library, by Joe Walnes
28
+ * v1.1: Auto scaling of axis, by Neil Dunn
29
+ * v1.2: fps (frames per second) option, by Mathias Petterson
30
+ * v1.3: Fix for divide by zero, by Paul Nikitochkin
31
+ * v1.4: Set minimum, top-scale padding, remove timeseries, add optional timer to reset bounds, by Kelley Reynolds
32
+ * v1.5: Set default frames per second to 50... smoother.
33
+ * .start(), .stop() methods for conserving CPU, by Dmitry Vyal
34
+ * options.interpolation = 'bezier' or 'line', by Dmitry Vyal
35
+ * options.maxValue to fix scale, by Dmitry Vyal
36
+ * v1.6: minValue/maxValue will always get converted to floats, by Przemek Matylla
37
+ * v1.7: options.grid.fillStyle may be a transparent color, by Dmitry A. Shashkin
38
+ * Smooth rescaling, by Kostas Michalopoulos
39
+ * v1.8: Set max length to customize number of live points in the dataset with options.maxDataSetLength, by Krishna Narni
40
+ * v1.9: Display timestamps along the bottom, by Nick and Stev-io
41
+ * (https://groups.google.com/forum/?fromgroups#!topic/smoothie-charts/-Ywse8FCpKI%5B1-25%5D)
42
+ * Refactored by Krishna Narni, to support timestamp formatting function
43
+ * v1.10: Switch to requestAnimationFrame, removed the now obsoleted options.fps, by Gergely Imreh
44
+ * v1.11: options.grid.sharpLines option added, by @drewnoakes
45
+ * Addressed warning seen in Firefox when seriesOption.fillStyle undefined, by @drewnoakes
46
+ */
47
+
48
+ function TimeSeries(options) {
49
+ options = options || {};
50
+ options.resetBoundsInterval = options.resetBoundsInterval || 3000; // Reset the max/min bounds after this many milliseconds
51
+ options.resetBounds = options.resetBounds === undefined ? true : options.resetBounds; // Enable or disable the resetBounds timer
52
+ this.options = options;
53
+ this.data = [];
54
+
55
+ this.maxValue = Number.NaN; // The maximum value ever seen in this time series.
56
+ this.minValue = Number.NaN; // The minimum value ever seen in this time series.
57
+
58
+ // Start a resetBounds Interval timer desired
59
+ if (options.resetBounds) {
60
+ this.boundsTimer = setInterval((function(thisObj) { return function() { thisObj.resetBounds(); } })(this), options.resetBoundsInterval);
61
+ }
62
+ }
63
+
64
+ // Reset the min and max for this timeseries so the graph rescales itself
65
+ TimeSeries.prototype.resetBounds = function() {
66
+ this.maxValue = Number.NaN;
67
+ this.minValue = Number.NaN;
68
+ for (var i = 0; i < this.data.length; i++) {
69
+ this.maxValue = !isNaN(this.maxValue) ? Math.max(this.maxValue, this.data[i][1]) : this.data[i][1];
70
+ this.minValue = !isNaN(this.minValue) ? Math.min(this.minValue, this.data[i][1]) : this.data[i][1];
71
+ }
72
+ };
73
+
74
+ TimeSeries.prototype.append = function(timestamp, value) {
75
+ this.data.push([timestamp, value]);
76
+ this.maxValue = !isNaN(this.maxValue) ? Math.max(this.maxValue, value) : value;
77
+ this.minValue = !isNaN(this.minValue) ? Math.min(this.minValue, value) : value;
78
+ };
79
+
80
+ function SmoothieChart(options) {
81
+ // Defaults
82
+ options = options || {};
83
+ options.grid = options.grid || { fillStyle:'#000000', strokeStyle: '#777777', lineWidth: 1, sharpLines: false, millisPerLine: 1000, verticalSections: 2 };
84
+ options.millisPerPixel = options.millisPerPixel || 20;
85
+ options.maxValueScale = options.maxValueScale || 1;
86
+ // NOTE there are no default values for 'minValue' and 'maxValue'
87
+ options.labels = options.labels || { fillStyle:'#ffffff' };
88
+ options.interpolation = options.interpolation || "bezier";
89
+ options.scaleSmoothing = options.scaleSmoothing || 0.125;
90
+ options.maxDataSetLength = options.maxDataSetLength || 2;
91
+ options.timestampFormatter = options.timestampFormatter || null;
92
+ this.options = options;
93
+ this.seriesSet = [];
94
+ this.currentValueRange = 1;
95
+ this.currentVisMinValue = 0;
96
+ }
97
+
98
+ // Based on http://inspirit.github.com/jsfeat/js/compatibility.js
99
+ SmoothieChart.AnimateCompatibility = (function() {
100
+ var lastTime = 0,
101
+
102
+ requestAnimationFrame = function(callback, element) {
103
+ var requestAnimationFrame =
104
+ window.requestAnimationFrame ||
105
+ window.webkitRequestAnimationFrame ||
106
+ window.mozRequestAnimationFrame ||
107
+ window.oRequestAnimationFrame ||
108
+ window.msRequestAnimationFrame ||
109
+ function(callback, element) {
110
+ var currTime = new Date().getTime();
111
+ var timeToCall = Math.max(0, 16 - (currTime - lastTime));
112
+ var id = window.setTimeout(function() {
113
+ callback(currTime + timeToCall);
114
+ }, timeToCall);
115
+ lastTime = currTime + timeToCall;
116
+ return id;
117
+ };
118
+ return requestAnimationFrame.call(window, callback, element);
119
+ },
120
+
121
+ cancelAnimationFrame = function(id) {
122
+ var cancelAnimationFrame =
123
+ window.cancelAnimationFrame ||
124
+ function(id) {
125
+ clearTimeout(id);
126
+ };
127
+ return cancelAnimationFrame.call(window, id);
128
+ };
129
+
130
+ return {
131
+ requestAnimationFrame: requestAnimationFrame,
132
+ cancelAnimationFrame: cancelAnimationFrame
133
+ };
134
+ })();
135
+
136
+ SmoothieChart.prototype.addTimeSeries = function(timeSeries, options) {
137
+ this.seriesSet.push({timeSeries: timeSeries, options: options || {}});
138
+ };
139
+
140
+ SmoothieChart.prototype.removeTimeSeries = function(timeSeries) {
141
+ this.seriesSet.splice(this.seriesSet.indexOf(timeSeries), 1);
142
+ };
143
+
144
+ SmoothieChart.prototype.streamTo = function(canvas, delay) {
145
+ this.canvas = canvas;
146
+ this.delay = delay;
147
+ this.start();
148
+ };
149
+
150
+ SmoothieChart.prototype.start = function() {
151
+ if (!this.frame) {
152
+ this.animate();
153
+ }
154
+ };
155
+
156
+ SmoothieChart.prototype.animate = function() {
157
+ this.frame = SmoothieChart.AnimateCompatibility.requestAnimationFrame(this.animate.bind(this));
158
+ this.render(this.canvas, new Date().getTime() - (this.delay || 0));
159
+ };
160
+
161
+ SmoothieChart.prototype.stop = function() {
162
+ if (this.frame) {
163
+ SmootheiChart.AnimateCompatibility.cancelAnimationFrame( this.frame );
164
+ delete this.frame;
165
+ }
166
+ };
167
+
168
+ // Sample timestamp formatting function
169
+ SmoothieChart.timeFormatter = function(dateObject) {
170
+ function pad2(number){return (number < 10 ? '0' : '') + number};
171
+ return pad2(dateObject.getHours())+':'+pad2(dateObject.getMinutes())+':'+pad2(dateObject.getSeconds());
172
+ };
173
+
174
+ SmoothieChart.prototype.render = function(canvas, time) {
175
+ var canvasContext = canvas.getContext("2d");
176
+ var options = this.options;
177
+ var dimensions = {top: 0, left: 0, width: canvas.clientWidth, height: canvas.clientHeight};
178
+
179
+ // Save the state of the canvas context, any transformations applied in this method
180
+ // will get removed from the stack at the end of this method when .restore() is called.
181
+ canvasContext.save();
182
+
183
+ // Round time down to pixel granularity, so motion appears smoother.
184
+ time = time - time % options.millisPerPixel;
185
+
186
+ // Move the origin.
187
+ canvasContext.translate(dimensions.left, dimensions.top);
188
+
189
+ // Create a clipped rectangle - anything we draw will be constrained to this rectangle.
190
+ // This prevents the occasional pixels from curves near the edges overrunning and creating
191
+ // screen cheese (that phrase should need no explanation).
192
+ canvasContext.beginPath();
193
+ canvasContext.rect(0, 0, dimensions.width, dimensions.height);
194
+ canvasContext.clip();
195
+
196
+ // Clear the working area.
197
+ canvasContext.save();
198
+ canvasContext.fillStyle = options.grid.fillStyle;
199
+ canvasContext.clearRect(0, 0, dimensions.width, dimensions.height);
200
+ canvasContext.fillRect(0, 0, dimensions.width, dimensions.height);
201
+ canvasContext.restore();
202
+
203
+ // Grid lines....
204
+ canvasContext.save();
205
+ canvasContext.lineWidth = options.grid.lineWidth || 1;
206
+ canvasContext.strokeStyle = options.grid.strokeStyle || '#ffffff';
207
+ // Vertical (time) dividers.
208
+ if (options.grid.millisPerLine > 0) {
209
+ for (var t = time - (time % options.grid.millisPerLine); t >= time - (dimensions.width * options.millisPerPixel); t -= options.grid.millisPerLine) {
210
+ canvasContext.beginPath();
211
+ var gx = Math.round(dimensions.width - ((time - t) / options.millisPerPixel));
212
+ if (options.grid.sharpLines)
213
+ gx -= 0.5;
214
+ canvasContext.moveTo(gx, 0);
215
+ canvasContext.lineTo(gx, dimensions.height);
216
+ canvasContext.stroke();
217
+ // To display timestamps along the bottom
218
+ // May have to adjust millisPerLine to display non-overlapping timestamps, depending on the canvas size
219
+ if (options.timestampFormatter){
220
+ var tx=new Date(t);
221
+ // Formats the timestamp based on user specified formatting function
222
+ // SmoothieChart.timeFormatter function above is one such formatting option
223
+ var ts = options.timestampFormatter(tx);
224
+ var txtwidth=(canvasContext.measureText(ts).width/2)+canvasContext.measureText(minValueString).width + 4;
225
+ if (gx<dimensions.width - txtwidth){
226
+ canvasContext.fillStyle = options.labels.fillStyle;
227
+ // Insert the time string so it doesn't overlap on the minimum value
228
+ canvasContext.fillText(ts, gx-(canvasContext.measureText(ts).width / 2), dimensions.height-2);
229
+ }
230
+ }
231
+ canvasContext.closePath();
232
+ }
233
+ }
234
+
235
+ // Horizontal (value) dividers.
236
+ for (var v = 1; v < options.grid.verticalSections; v++) {
237
+ var gy = Math.round(v * dimensions.height / options.grid.verticalSections);
238
+ if (options.grid.sharpLines)
239
+ gy -= 0.5;
240
+ canvasContext.beginPath();
241
+ canvasContext.moveTo(0, gy);
242
+ canvasContext.lineTo(dimensions.width, gy);
243
+ canvasContext.stroke();
244
+ canvasContext.closePath();
245
+ }
246
+ // Bounding rectangle.
247
+ canvasContext.beginPath();
248
+ canvasContext.strokeRect(0, 0, dimensions.width, dimensions.height);
249
+ canvasContext.closePath();
250
+ canvasContext.restore();
251
+
252
+ // Calculate the current scale of the chart, from all time series.
253
+ var maxValue = Number.NaN;
254
+ var minValue = Number.NaN;
255
+
256
+ for (var d = 0; d < this.seriesSet.length; d++) {
257
+ // TODO(ndunn): We could calculate / track these values as they stream in.
258
+ var timeSeries = this.seriesSet[d].timeSeries;
259
+ if (!isNaN(timeSeries.maxValue)) {
260
+ maxValue = !isNaN(maxValue) ? Math.max(maxValue, timeSeries.maxValue) : timeSeries.maxValue;
261
+ }
262
+
263
+ if (!isNaN(timeSeries.minValue)) {
264
+ minValue = !isNaN(minValue) ? Math.min(minValue, timeSeries.minValue) : timeSeries.minValue;
265
+ }
266
+ }
267
+
268
+ if (isNaN(maxValue) && isNaN(minValue)) {
269
+ canvasContext.restore(); // without this there is crash in Android browser
270
+ return;
271
+ }
272
+
273
+ // Scale the maxValue to add padding at the top if required
274
+ if (options.maxValue != null)
275
+ maxValue = options.maxValue;
276
+ else
277
+ maxValue = maxValue * options.maxValueScale;
278
+ // Set the minimum if we've specified one
279
+ if (options.minValue != null)
280
+ minValue = options.minValue;
281
+ var targetValueRange = maxValue - minValue;
282
+ this.currentValueRange += options.scaleSmoothing*(targetValueRange - this.currentValueRange);
283
+ this.currentVisMinValue += options.scaleSmoothing*(minValue - this.currentVisMinValue);
284
+ var valueRange = this.currentValueRange;
285
+ var visMinValue = this.currentVisMinValue;
286
+
287
+ // For each data set...
288
+ for (var d = 0; d < this.seriesSet.length; d++) {
289
+ canvasContext.save();
290
+ var timeSeries = this.seriesSet[d].timeSeries;
291
+ var dataSet = timeSeries.data;
292
+ var seriesOptions = this.seriesSet[d].options;
293
+
294
+ // Delete old data that's moved off the left of the chart.
295
+ // We must always keep the last expired data point as we need this to draw the
296
+ // line that comes into the chart, but any points prior to that can be removed.
297
+ while (dataSet.length >= options.maxDataSetLength && dataSet[1][0] < time - (dimensions.width * options.millisPerPixel)) {
298
+ dataSet.splice(0, 1);
299
+ }
300
+
301
+ // Set style for this dataSet.
302
+ canvasContext.lineWidth = seriesOptions.lineWidth || 1;
303
+ canvasContext.strokeStyle = seriesOptions.strokeStyle || '#ffffff';
304
+ // Draw the line...
305
+ canvasContext.beginPath();
306
+ // Retain lastX, lastY for calculating the control points of bezier curves.
307
+ var firstX = 0, lastX = 0, lastY = 0;
308
+ for (var i = 0; i < dataSet.length; i++) {
309
+ // TODO: Deal with dataSet.length < 2.
310
+ var x = Math.round(dimensions.width - ((time - dataSet[i][0]) / options.millisPerPixel));
311
+ var value = dataSet[i][1];
312
+ var offset = value - visMinValue;
313
+ var scaledValue = dimensions.height - (valueRange ? Math.round((offset / valueRange) * dimensions.height) : 0);
314
+ var y = Math.max(Math.min(scaledValue, dimensions.height - 1), 1); // Ensure line is always on chart.
315
+
316
+ if (i == 0) {
317
+ firstX = x;
318
+ canvasContext.moveTo(x, y);
319
+ }
320
+ // Great explanation of Bezier curves: http://en.wikipedia.org/wiki/Bezier_curve#Quadratic_curves
321
+ //
322
+ // Assuming A was the last point in the line plotted and B is the new point,
323
+ // we draw a curve with control points P and Q as below.
324
+ //
325
+ // A---P
326
+ // |
327
+ // |
328
+ // |
329
+ // Q---B
330
+ //
331
+ // Importantly, A and P are at the same y coordinate, as are B and Q. This is
332
+ // so adjacent curves appear to flow as one.
333
+ //
334
+ else {
335
+ switch (options.interpolation) {
336
+ case "line":
337
+ canvasContext.lineTo(x,y);
338
+ break;
339
+ case "bezier":
340
+ default:
341
+ canvasContext.bezierCurveTo( // startPoint (A) is implicit from last iteration of loop
342
+ Math.round((lastX + x) / 2), lastY, // controlPoint1 (P)
343
+ Math.round((lastX + x)) / 2, y, // controlPoint2 (Q)
344
+ x, y); // endPoint (B)
345
+ break;
346
+ }
347
+ }
348
+
349
+ lastX = x; lastY = y;
350
+ }
351
+ if (dataSet.length > 0 && seriesOptions.fillStyle) {
352
+ // Close up the fill region.
353
+ canvasContext.lineTo(dimensions.width + seriesOptions.lineWidth + 1, lastY);
354
+ canvasContext.lineTo(dimensions.width + seriesOptions.lineWidth + 1, dimensions.height + seriesOptions.lineWidth + 1);
355
+ canvasContext.lineTo(firstX, dimensions.height + seriesOptions.lineWidth);
356
+ canvasContext.fillStyle = seriesOptions.fillStyle;
357
+ canvasContext.fill();
358
+ }
359
+ canvasContext.stroke();
360
+ canvasContext.closePath();
361
+ canvasContext.restore();
362
+ }
363
+
364
+ // Draw the axis values on the chart.
365
+ if (!options.labels.disabled) {
366
+ canvasContext.fillStyle = options.labels.fillStyle;
367
+ var maxValueString = parseFloat(maxValue).toFixed(2);
368
+ var minValueString = parseFloat(minValue).toFixed(2);
369
+ canvasContext.fillText(maxValueString, dimensions.width - canvasContext.measureText(maxValueString).width - 2, 10);
370
+ canvasContext.fillText(minValueString, dimensions.width - canvasContext.measureText(minValueString).width - 2, dimensions.height - 2);
371
+ }
372
+
373
+ canvasContext.restore(); // See .save() above.
374
+ };