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.
- data/.gitignore +8 -0
- data/Gemfile +7 -0
- data/Gemfile.lock +52 -0
- data/LICENSE +21 -0
- data/README.markdown +52 -0
- data/Rakefile.rb +11 -0
- data/bin/riemann-dash +7 -0
- data/example/config.rb +17 -0
- data/lib/riemann/dash.rb +5 -0
- data/lib/riemann/dash/app.rb +32 -0
- data/lib/riemann/dash/config.rb +154 -0
- data/lib/riemann/dash/controller/css.rb +5 -0
- data/lib/riemann/dash/controller/index.rb +20 -0
- data/lib/riemann/dash/public/clock.js +45 -0
- data/lib/riemann/dash/public/dash.js +287 -0
- data/lib/riemann/dash/public/format.js +24 -0
- data/lib/riemann/dash/public/jquery-1.7.2.min.js +4 -0
- data/lib/riemann/dash/public/jquery-ui-1.9.0.custom.min.js +6 -0
- data/lib/riemann/dash/public/jquery.json-2.2.min.js +31 -0
- data/lib/riemann/dash/public/jquery.quickfit.js +144 -0
- data/lib/riemann/dash/public/jquery.simplemodal.1.4.3.min.js +26 -0
- data/lib/riemann/dash/public/keys.js +46 -0
- data/lib/riemann/dash/public/mustache.js +597 -0
- data/lib/riemann/dash/public/persistence.js +30 -0
- data/lib/riemann/dash/public/profile.js +33 -0
- data/lib/riemann/dash/public/subs.js +164 -0
- data/lib/riemann/dash/public/toastr.css +174 -0
- data/lib/riemann/dash/public/toastr.js +207 -0
- data/lib/riemann/dash/public/toolbar.js +217 -0
- data/lib/riemann/dash/public/underscore-min.js +5 -0
- data/lib/riemann/dash/public/util.js +34 -0
- data/lib/riemann/dash/public/vendor/smoothie.js +374 -0
- data/lib/riemann/dash/public/view.js +704 -0
- data/lib/riemann/dash/public/views/gauge.js +76 -0
- data/lib/riemann/dash/public/views/grid.js +279 -0
- data/lib/riemann/dash/public/views/help.js +28 -0
- data/lib/riemann/dash/public/views/timeseries.js +107 -0
- data/lib/riemann/dash/public/views/title.js +35 -0
- data/lib/riemann/dash/public/x.png +0 -0
- data/lib/riemann/dash/rack/static.rb +16 -0
- data/lib/riemann/dash/version.rb +4 -0
- data/lib/riemann/dash/views/css.scss +393 -0
- data/lib/riemann/dash/views/index.erubis +203 -0
- data/lib/riemann/dash/views/layout.erubis +21 -0
- data/riemann-dash.gemspec +28 -0
- data/sh/c +1 -0
- data/sh/env.rb +2 -0
- data/sh/test +1 -0
- data/test/config_test.rb +106 -0
- data/test/fixtures/config/basic_config.rb +2 -0
- data/test/fixtures/config/ws_config.rb +1 -0
- data/test/fixtures/ws_config/dummy_config.json +1 -0
- data/test/fixtures/ws_config/pretty_printed_config.json +6 -0
- data/test/test_helper.rb +10 -0
- metadata +202 -0
@@ -0,0 +1,76 @@
|
|
1
|
+
(function() {
|
2
|
+
var fitopts = {min: 6, max: 1000};
|
3
|
+
|
4
|
+
var Gauge = function(json) {
|
5
|
+
view.View.call(this, json);
|
6
|
+
this.query = json.query;
|
7
|
+
this.title = json.title;
|
8
|
+
this.clickFocusable = true;
|
9
|
+
this.el.addClass('gauge');
|
10
|
+
this.el.append(
|
11
|
+
'<div class="box">' +
|
12
|
+
'<div class="quickfit metric value">?</div>' +
|
13
|
+
'<h2 class="quickfit"></div>' +
|
14
|
+
'</div>'
|
15
|
+
);
|
16
|
+
|
17
|
+
this.box = this.el.find('.box');
|
18
|
+
this.el.find('h2').text(this.title);
|
19
|
+
|
20
|
+
if (this.query) {
|
21
|
+
var reflowed = false;
|
22
|
+
var me = this;
|
23
|
+
var value = this.el.find('.value');
|
24
|
+
this.sub = subs.subscribe(this.query, function(e) {
|
25
|
+
me.box.attr('class', 'box state ' + e.state);
|
26
|
+
value.text(format.float(e.metric));
|
27
|
+
value.attr('title', e.description);
|
28
|
+
|
29
|
+
// The first time, do a full-height reflow.
|
30
|
+
if (reflowed) {
|
31
|
+
value.quickfit(fitopts);
|
32
|
+
} else {
|
33
|
+
me.reflow();
|
34
|
+
reflowed = true;
|
35
|
+
}
|
36
|
+
});
|
37
|
+
}
|
38
|
+
}
|
39
|
+
|
40
|
+
view.inherit(view.View, Gauge);
|
41
|
+
view.Gauge = Gauge;
|
42
|
+
view.types.Gauge = Gauge;
|
43
|
+
|
44
|
+
Gauge.prototype.json = function() {
|
45
|
+
return $.extend(view.View.prototype.json.call(this), {
|
46
|
+
type: 'Gauge',
|
47
|
+
title: this.title,
|
48
|
+
query: this.query
|
49
|
+
});
|
50
|
+
}
|
51
|
+
|
52
|
+
Gauge.prototype.editForm = function() {
|
53
|
+
return Mustache.render('<label for="title">Title</label>' +
|
54
|
+
'<input type="text" name="title" value="{{title}}" /><br />' +
|
55
|
+
'<label for="query">Query</label>' +
|
56
|
+
'<input type="text" name="query" value="{{query}}" />',
|
57
|
+
this)
|
58
|
+
}
|
59
|
+
|
60
|
+
Gauge.prototype.reflow = function() {
|
61
|
+
// Size metric
|
62
|
+
var value = this.el.find('.value');
|
63
|
+
value.quickfit({min: 6, max: 1000, font_height_scale: 1});
|
64
|
+
|
65
|
+
// Size title
|
66
|
+
var title = this.el.find('h2');
|
67
|
+
title.quickfit(fitopts);
|
68
|
+
}
|
69
|
+
|
70
|
+
Gauge.prototype.delete = function() {
|
71
|
+
if (this.sub) {
|
72
|
+
subs.unsubscribe(this.sub);
|
73
|
+
}
|
74
|
+
view.View.prototype.delete.call(this);
|
75
|
+
}
|
76
|
+
})();
|
@@ -0,0 +1,279 @@
|
|
1
|
+
(function() {
|
2
|
+
var fitopts = {min: 6, max: 1000};
|
3
|
+
|
4
|
+
var Grid = function(json) {
|
5
|
+
view.View.call(this, json);
|
6
|
+
this.query = json.query;
|
7
|
+
this.title = json.title;
|
8
|
+
this.max = json.max || "all";
|
9
|
+
this.clickFocusable = true;
|
10
|
+
|
11
|
+
// Initial display
|
12
|
+
this.el.addClass('grid');
|
13
|
+
this.el.append(
|
14
|
+
'<h2></h2>' +
|
15
|
+
'<div class="container"><table></table></div>'
|
16
|
+
);
|
17
|
+
this.box = this.el.find('.box');
|
18
|
+
this.el.find('h2').text(this.title);
|
19
|
+
|
20
|
+
// State
|
21
|
+
this.hosts = [];
|
22
|
+
this.services = [];
|
23
|
+
this.events = {};
|
24
|
+
if (this.max === "service" || this.max === "host") {
|
25
|
+
this.currentMax = {};
|
26
|
+
}
|
27
|
+
|
28
|
+
// Subscribe
|
29
|
+
if (this.query) {
|
30
|
+
var me = this;
|
31
|
+
this.sub = subs.subscribe(this.query, function(e) {
|
32
|
+
me.update.call(me, e);
|
33
|
+
});
|
34
|
+
}
|
35
|
+
}
|
36
|
+
|
37
|
+
view.inherit(view.View, Grid);
|
38
|
+
view.Grid = Grid;
|
39
|
+
view.types.Grid = Grid;
|
40
|
+
|
41
|
+
Grid.prototype.json = function() {
|
42
|
+
return $.extend(view.View.prototype.json.call(this), {
|
43
|
+
type: 'Grid',
|
44
|
+
title: this.title,
|
45
|
+
query: this.query,
|
46
|
+
max: this.max,
|
47
|
+
});
|
48
|
+
}
|
49
|
+
|
50
|
+
Grid.prototype.editForm = function() {
|
51
|
+
return Mustache.render('<label for="title">Title</label>' +
|
52
|
+
'<input type="text" name="title" value="{{title}}" /><br />' +
|
53
|
+
'<label for="query">Query</label><br />' +
|
54
|
+
'<textarea name="query" class="query">{{query}}</textarea><br />' +
|
55
|
+
'<label for="max">Max</label>' +
|
56
|
+
'<input type="text" name="max" value="{{max}}" /><br />' +
|
57
|
+
'<span class="desc">"all", "host", "service", or any number.</span>',
|
58
|
+
this)
|
59
|
+
}
|
60
|
+
|
61
|
+
// Returns all events, flat.
|
62
|
+
Grid.prototype.allEvents = function() {
|
63
|
+
var events = [];
|
64
|
+
for (host in this.events) {
|
65
|
+
for (service in this.events[host]) {
|
66
|
+
events.push(this.events[host][service]);
|
67
|
+
}
|
68
|
+
}
|
69
|
+
}
|
70
|
+
|
71
|
+
// What is the maximum for this event?
|
72
|
+
Grid.prototype.eventMax = function(event) {
|
73
|
+
if (this.max === "host") {
|
74
|
+
return this.currentMax[event.host] || -1/0;
|
75
|
+
} else if (this.max === "service") {
|
76
|
+
return this.currentMax[event.service] || -1/0;
|
77
|
+
} else {
|
78
|
+
return this.currentMax || -1/0;
|
79
|
+
}
|
80
|
+
}
|
81
|
+
|
82
|
+
// Update a single jq element with information about an event.
|
83
|
+
Grid.prototype.renderElement = function(element, event) {
|
84
|
+
if (event === undefined) {
|
85
|
+
return;
|
86
|
+
}
|
87
|
+
|
88
|
+
// State
|
89
|
+
element.attr('class', "state box " + event.state);
|
90
|
+
|
91
|
+
// Metric
|
92
|
+
element.find('.metric').text(format.float(event.metric));
|
93
|
+
|
94
|
+
// Description
|
95
|
+
element.attr('title', event.state + ' at ' + event.time + "\n\n" +
|
96
|
+
event.description);
|
97
|
+
|
98
|
+
// Bar chart
|
99
|
+
if (event.metric) {
|
100
|
+
element.find('.bar').css('width',
|
101
|
+
(event.metric / this.eventMax(event) * 100) + "%");
|
102
|
+
}
|
103
|
+
}
|
104
|
+
|
105
|
+
|
106
|
+
// Render a single event if there's been no change to table structure.
|
107
|
+
Grid.prototype.partialRender = function(event) {
|
108
|
+
var table = this.el.find('table');
|
109
|
+
var hostIndex = this.hosts.indexOf(event.host);
|
110
|
+
var serviceIndex = this.services.indexOf(event.service);
|
111
|
+
var row = this.el.find('tbody tr')[hostIndex];
|
112
|
+
var td = $($(row).find('td')[serviceIndex]);
|
113
|
+
|
114
|
+
this.renderElement(td, event);
|
115
|
+
}
|
116
|
+
|
117
|
+
// Rerender the table
|
118
|
+
Grid.prototype.render = util.slur(200, function() {
|
119
|
+
var table = this.el.find('table');
|
120
|
+
table.empty();
|
121
|
+
|
122
|
+
// Header
|
123
|
+
table.append("<thead><tr><th></th></tr></thead>");
|
124
|
+
var row = table.find("thead tr");
|
125
|
+
this.services.forEach(function(service) {
|
126
|
+
var element = $('<th>');
|
127
|
+
element.text(service);
|
128
|
+
row.append(element);
|
129
|
+
});
|
130
|
+
|
131
|
+
this.hosts.forEach(function(host) {
|
132
|
+
row = $("<tr><th></th>");
|
133
|
+
table.append(row);
|
134
|
+
row.find('th').text(host);
|
135
|
+
this.services.forEach(function(service) {
|
136
|
+
var event = this.events[host][service];
|
137
|
+
var element = $('<td><span class="bar"><span class="metric"/></span></td>');
|
138
|
+
this.renderElement(element, event);
|
139
|
+
row.append(element);
|
140
|
+
}, this);
|
141
|
+
}, this);
|
142
|
+
});
|
143
|
+
|
144
|
+
// Update cached maxima with a new event. Returns true if maxima changed.
|
145
|
+
Grid.prototype.updateMax = function(event) {
|
146
|
+
if (event !== undefined &&
|
147
|
+
(event.metric === undefined ||
|
148
|
+
event.metric <= (this.eventMax(event) || -1/0))) {
|
149
|
+
// We haven't bumped our max; no change.
|
150
|
+
return false;
|
151
|
+
}
|
152
|
+
|
153
|
+
var e;
|
154
|
+
if (this.max === "all") {
|
155
|
+
this.currentMax = -1/0;
|
156
|
+
for (host in this.events) {
|
157
|
+
for (service in this.events[host]) {
|
158
|
+
e = this.events[host][service];
|
159
|
+
this.currentMax = Math.max(e.metric, this.currentMax || -1/0);
|
160
|
+
}
|
161
|
+
}
|
162
|
+
} else if (this.max === "host" ) {
|
163
|
+
this.currentMax = {};
|
164
|
+
for (host in this.events) {
|
165
|
+
for (service in this.events[host]) {
|
166
|
+
e = this.events[host][service];
|
167
|
+
this.currentMax[e.host] =
|
168
|
+
Math.max(e.metric, this.currentMax[e.host] || -1/0)
|
169
|
+
}
|
170
|
+
}
|
171
|
+
} else if (this.max === "service") {
|
172
|
+
this.currentMax = {};
|
173
|
+
for (host in this.events) {
|
174
|
+
for (service in this.events[host]) {
|
175
|
+
e = this.events[host][service];
|
176
|
+
this.currentMax[e.service] =
|
177
|
+
Math.max(e.metric, this.currentMax[e.service] || -1/0);
|
178
|
+
}
|
179
|
+
}
|
180
|
+
} else {
|
181
|
+
this.currentMax = this.max;
|
182
|
+
return false;
|
183
|
+
}
|
184
|
+
|
185
|
+
return true;
|
186
|
+
}
|
187
|
+
|
188
|
+
// Stores an event in the internal state tables. Returns true if we
|
189
|
+
// haven't seen this host/service before.
|
190
|
+
Grid.prototype.saveEvent = function(e) {
|
191
|
+
// Host list
|
192
|
+
if (this.hosts.indexOf(e.host) === -1) {
|
193
|
+
this.hosts.push(e.host);
|
194
|
+
this.hosts = _.uniq(this.hosts.sort(), true);
|
195
|
+
}
|
196
|
+
|
197
|
+
// Services list
|
198
|
+
if (this.services.indexOf(e.service) === -1) {
|
199
|
+
this.services.push(e.service);
|
200
|
+
this.services = _.uniq(this.services.sort(), true);
|
201
|
+
}
|
202
|
+
|
203
|
+
// Events map
|
204
|
+
if (this.events[e.host] === undefined) {
|
205
|
+
// New host
|
206
|
+
this.events[e.host] = {};
|
207
|
+
}
|
208
|
+
if (this.events[e.host][e.service] === undefined) {
|
209
|
+
// New event
|
210
|
+
var newEvent = true;
|
211
|
+
} else {
|
212
|
+
var newEvent = false;
|
213
|
+
}
|
214
|
+
|
215
|
+
// Store event
|
216
|
+
this.events[e.host][e.service] = e;
|
217
|
+
|
218
|
+
return newEvent;
|
219
|
+
}
|
220
|
+
|
221
|
+
// Add an event.
|
222
|
+
Grid.prototype.add = function(e) {
|
223
|
+
var newEvent = this.saveEvent(e);
|
224
|
+
var newMax = this.updateMax(e);
|
225
|
+
|
226
|
+
if (newEvent || newMax) {
|
227
|
+
this.render();
|
228
|
+
} else {
|
229
|
+
this.partialRender(e);
|
230
|
+
}
|
231
|
+
}
|
232
|
+
|
233
|
+
// Remove an event.
|
234
|
+
Grid.prototype.remove = function(e) {
|
235
|
+
delete this.events[e.host][e.service];
|
236
|
+
if (_.isEmpty(this.events[e.host])) {
|
237
|
+
delete this.events[e.host];
|
238
|
+
};
|
239
|
+
|
240
|
+
// Recompute hosts
|
241
|
+
this.hosts = _.keys(this.events).sort();
|
242
|
+
|
243
|
+
// Recompute services
|
244
|
+
var services = {};
|
245
|
+
for (var host in this.events) {
|
246
|
+
for (var service in this.events[host]) {
|
247
|
+
services[this.events[host][service].service] = true;
|
248
|
+
}
|
249
|
+
}
|
250
|
+
this.services = _.keys(services).sort();
|
251
|
+
|
252
|
+
this.updateMax();
|
253
|
+
this.render();
|
254
|
+
}
|
255
|
+
|
256
|
+
// Accept an event.
|
257
|
+
Grid.prototype.update = function(e) {
|
258
|
+
if (e.state === "expired") {
|
259
|
+
this.remove(e);
|
260
|
+
} else {
|
261
|
+
this.add(e);
|
262
|
+
}
|
263
|
+
}
|
264
|
+
|
265
|
+
Grid.prototype.reflow = function() {
|
266
|
+
// this.el.find('table').height(
|
267
|
+
// this.height() -
|
268
|
+
// this.el.find('h2').height()
|
269
|
+
// );
|
270
|
+
}
|
271
|
+
|
272
|
+
Grid.prototype.delete = function() {
|
273
|
+
if (this.sub != undefined) {
|
274
|
+
subs.unsubscribe(this.sub);
|
275
|
+
}
|
276
|
+
this.update = function() {};
|
277
|
+
view.View.prototype.delete.call(this);
|
278
|
+
}
|
279
|
+
})();
|
@@ -0,0 +1,28 @@
|
|
1
|
+
(function() {
|
2
|
+
var Help = function(json) {
|
3
|
+
view.View.call(this, json);
|
4
|
+
this.clickFocusable = true;
|
5
|
+
this.el.addClass("help");
|
6
|
+
this.el.append('<div class="box">' +
|
7
|
+
"<p>Welcome to Riemann-Dash.</p>" +
|
8
|
+
"<p>Click to select a view. Escape unfocuses. Use the arrow keys to move a view. Use Control+arrow to <i>split</i> a view in the given direction.</p>" +
|
9
|
+
"<p>To edit a view, hit e. Use enter, or click 'apply', to apply your changes. Escape cancels.</p>" +
|
10
|
+
"<p>To save your changes to the server, press s. You can refresh the page, or press r to reload.</p>" +
|
11
|
+
"<p>Make views bigger and smaller with the +/- keys. Pageup selects the parent of the current view. To delete a view, use the delete key.</p>" +
|
12
|
+
"<p>Switch between workspaces with alt-1, alt-2, etc.</p>" +
|
13
|
+
"<p>My sincere apologies for layout jankiness. There are a few gross bugs and incapabilities in the current hstack/vstack system; if things get really bad, you can always edit ws/config.json on the server. The control scheme will probably change; I appreciate your ideas and patches.</p>" +
|
14
|
+
'</div>'
|
15
|
+
);
|
16
|
+
}
|
17
|
+
|
18
|
+
view.inherit(view.View, Help);
|
19
|
+
view.Help = Help;
|
20
|
+
view.types.Help = Help;
|
21
|
+
|
22
|
+
Help.prototype.json = function() {
|
23
|
+
return {
|
24
|
+
type: 'Help',
|
25
|
+
title: this.title
|
26
|
+
};
|
27
|
+
}
|
28
|
+
})();
|
@@ -0,0 +1,107 @@
|
|
1
|
+
(function() {
|
2
|
+
var fitopts = {min: 6, max: 1000};
|
3
|
+
|
4
|
+
var TimeSeriesView = function(json) {
|
5
|
+
view.View.call(this, json);
|
6
|
+
this.query = json.query;
|
7
|
+
this.title = json.title;
|
8
|
+
this.delay = json.delay;
|
9
|
+
this.lineWidth = json.lineWidth;
|
10
|
+
this.strokeStyle = json.strokeStyle;
|
11
|
+
this.fillStyle = json.fillStyle;
|
12
|
+
|
13
|
+
this.clickFocusable = true;
|
14
|
+
this.el.addClass('timeseries');
|
15
|
+
this.el.append(
|
16
|
+
'<div class="box">' +
|
17
|
+
'<div class="title">' +
|
18
|
+
'<h2>' + this.title + '</h2>' +
|
19
|
+
'</div>' +
|
20
|
+
'<canvas class="timeseries"></canvas>' +
|
21
|
+
'</div>'
|
22
|
+
);
|
23
|
+
|
24
|
+
this.$canvas = this.el.find(".timeseries");
|
25
|
+
this.$titlecontainer = this.el.find("div.title");
|
26
|
+
this.$titlecontainer.css({"color": "#f2f2f2",
|
27
|
+
"text-shadow": "#262626 2px 2px 4px",
|
28
|
+
"font-size": "2em"})
|
29
|
+
|
30
|
+
this.$title = this.$titlecontainer.find("h2");
|
31
|
+
this.canvas = this.$canvas.get(0);
|
32
|
+
|
33
|
+
this.reflow();
|
34
|
+
|
35
|
+
this.smoothie = new SmoothieChart();
|
36
|
+
this.smoothie.streamTo(this.canvas, this.delay);
|
37
|
+
|
38
|
+
this.series = new TimeSeries();
|
39
|
+
this.smoothie.addTimeSeries(this.series, {lineWidth: this.lineWidth || 2,
|
40
|
+
strokeStyle: this.strokeStyle || "#FFF",
|
41
|
+
fillStyle: this.fillStyle});
|
42
|
+
|
43
|
+
if (this.query) {
|
44
|
+
var reflowed = false;
|
45
|
+
var me = this;
|
46
|
+
this.sub = subs.subscribe(this.query, function(e) {
|
47
|
+
var metric = format.float(e.metric);
|
48
|
+
me.series.append(new Date(e.time).getTime(), metric);
|
49
|
+
_.delay(function() {
|
50
|
+
if (me.$title) {
|
51
|
+
me.$title.text(me.title + ": " + metric);
|
52
|
+
}
|
53
|
+
}, +me.delay)
|
54
|
+
|
55
|
+
});
|
56
|
+
}
|
57
|
+
}
|
58
|
+
|
59
|
+
view.inherit(view.View, TimeSeriesView);
|
60
|
+
view.TimeSeries = TimeSeriesView;
|
61
|
+
view.types.TimeSeries = TimeSeriesView;
|
62
|
+
|
63
|
+
TimeSeriesView.prototype.json = function() {
|
64
|
+
return $.extend(view.View.prototype.json.call(this), {
|
65
|
+
type: 'TimeSeries',
|
66
|
+
title: this.title,
|
67
|
+
delay: this.delay,
|
68
|
+
query: this.query,
|
69
|
+
strokeStyle: this.strokeStyle,
|
70
|
+
lineWidth: this.lineWidth,
|
71
|
+
fillStyle: this.fillStyle
|
72
|
+
});
|
73
|
+
}
|
74
|
+
|
75
|
+
TimeSeriesView.prototype.editForm = function() {
|
76
|
+
return Mustache.render('<label for="title">Title</label>' +
|
77
|
+
'<input type="text" name="title" value="{{title}}" /><br />' +
|
78
|
+
'<label for="query">Query</label>' +
|
79
|
+
'<input type="text" name="query" value="{{query}}" /><br />' +
|
80
|
+
'<label for="strokeStyle">StrokeStyle</label>' +
|
81
|
+
'<input type="text" name="strokeStyle" value="{{strokeStyle}}" /><br />' +
|
82
|
+
'<label for="lineWidth">LineWidth</label>' +
|
83
|
+
'<input type="text" name="lineWidth" value="{{lineWidth}}" /><br />' +
|
84
|
+
'<label for="fillStyle">FillStyle</label>' +
|
85
|
+
'<input type="text" name="fillStyle" value="{{fillStyle}}" /><br />' +
|
86
|
+
'<label for="delay">Delay</label>' +
|
87
|
+
'<input type="text" name="delay" value="{{delay}}" />',
|
88
|
+
this)
|
89
|
+
}
|
90
|
+
|
91
|
+
TimeSeriesView.prototype.reflow = function() {
|
92
|
+
// Size metric
|
93
|
+
var width = this.el.width();
|
94
|
+
var height = this.el.height();
|
95
|
+
this.$canvas.attr("width", width - 10);
|
96
|
+
this.$canvas.attr("height", height - 10);
|
97
|
+
|
98
|
+
}
|
99
|
+
|
100
|
+
TimeSeriesView.prototype.delete = function() {
|
101
|
+
if (this.sub) {
|
102
|
+
subs.unsubscribe(this.sub);
|
103
|
+
}
|
104
|
+
view.View.prototype.delete.call(this);
|
105
|
+
}
|
106
|
+
})();
|
107
|
+
|