riemann-dash 0.2.1 → 0.2.3
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +9 -0
- data/Gemfile +7 -0
- data/Gemfile.lock +52 -0
- data/README.markdown +29 -5
- data/Rakefile.rb +11 -0
- data/bin/riemann-dash +2 -2
- data/example/config.rb +17 -0
- data/lib/riemann/dash/app.rb +32 -0
- data/lib/riemann/dash/config.rb +154 -0
- data/lib/riemann/dash/controller/css.rb +1 -1
- data/lib/riemann/dash/controller/index.rb +6 -36
- data/lib/riemann/dash/public/dash.js +44 -18
- data/lib/riemann/dash/public/format.js +3 -3
- data/lib/riemann/dash/public/persistence.js +2 -2
- data/lib/riemann/dash/public/subs.js +63 -48
- data/lib/riemann/dash/public/util.js +37 -44
- data/lib/riemann/dash/public/vendor/backbone.js +1571 -0
- data/lib/riemann/dash/public/vendor/jquery/jquery-1.9.1.min.js +5 -0
- data/lib/riemann/dash/public/{jquery-ui-1.9.0.custom.min.js → vendor/jquery/jquery-ui-1.9.0.custom.min.js} +0 -0
- data/lib/riemann/dash/public/{jquery.quickfit.js → vendor/jquery/jquery.quickfit.js} +0 -0
- data/lib/riemann/dash/public/vendor/jquery/jquery.simplemodal.1.4.4.min.js +26 -0
- data/lib/riemann/dash/public/vendor/lodash.min.js +40 -0
- data/lib/riemann/dash/public/vendor/smoothie.js +376 -0
- data/lib/riemann/dash/public/{toastr.css → vendor/toastr/toastr.css} +1 -1
- data/lib/riemann/dash/public/{toastr.js → vendor/toastr/toastr.js} +0 -0
- data/lib/riemann/dash/public/views/gauge.js +8 -5
- data/lib/riemann/dash/public/views/grid.js +138 -67
- data/lib/riemann/dash/public/views/timeseries.js +230 -0
- data/lib/riemann/dash/public/views/title.js +6 -3
- data/lib/riemann/dash/version.rb +2 -2
- data/lib/riemann/dash/views/css.scss +52 -2
- data/lib/riemann/dash/views/index.erubis +38 -192
- data/lib/riemann/dash.rb +3 -97
- 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 +114 -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 +43 -18
- data/lib/riemann/dash/public/jquery-1.7.2.min.js +0 -4
- data/lib/riemann/dash/public/jquery.json-2.2.min.js +0 -31
- data/lib/riemann/dash/public/jquery.simplemodal.1.4.3.min.js +0 -26
- data/lib/riemann/dash/public/mustache.js +0 -597
- data/lib/riemann/dash/public/underscore-min.js +0 -5
@@ -0,0 +1,376 @@
|
|
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 = 0;
|
71
|
+
//this.minValue = !isNaN(this.minValue) ? Math.min(this.minValue, this.data[i][1]) : this.data[i][1];
|
72
|
+
}
|
73
|
+
};
|
74
|
+
|
75
|
+
TimeSeries.prototype.append = function(timestamp, value) {
|
76
|
+
this.data.push([timestamp, value]);
|
77
|
+
this.maxValue = !isNaN(this.maxValue) ? Math.max(this.maxValue, value) : value;
|
78
|
+
//this.minValue = !isNaN(this.minValue) ? Math.min(this.minValue, value) : value;
|
79
|
+
this.minValue = 0;
|
80
|
+
};
|
81
|
+
|
82
|
+
function SmoothieChart(options) {
|
83
|
+
// Defaults
|
84
|
+
options = options || {};
|
85
|
+
options.grid = options.grid || { fillStyle:'#000000', strokeStyle: '#777777', lineWidth: 1, sharpLines: false, millisPerLine: 1000, verticalSections: 2 };
|
86
|
+
options.millisPerPixel = options.millisPerPixel || 20;
|
87
|
+
options.maxValueScale = options.maxValueScale || 1;
|
88
|
+
// NOTE there are no default values for 'minValue' and 'maxValue'
|
89
|
+
options.labels = options.labels || { fillStyle:'#ffffff' };
|
90
|
+
options.interpolation = options.interpolation || "bezier";
|
91
|
+
options.scaleSmoothing = options.scaleSmoothing || 0.125;
|
92
|
+
options.maxDataSetLength = options.maxDataSetLength || 2;
|
93
|
+
options.timestampFormatter = options.timestampFormatter || null;
|
94
|
+
this.options = options;
|
95
|
+
this.seriesSet = [];
|
96
|
+
this.currentValueRange = 1;
|
97
|
+
this.currentVisMinValue = 0;
|
98
|
+
}
|
99
|
+
|
100
|
+
// Based on http://inspirit.github.com/jsfeat/js/compatibility.js
|
101
|
+
SmoothieChart.AnimateCompatibility = (function() {
|
102
|
+
var lastTime = 0,
|
103
|
+
|
104
|
+
requestAnimationFrame = function(callback, element) {
|
105
|
+
var requestAnimationFrame =
|
106
|
+
window.requestAnimationFrame ||
|
107
|
+
window.webkitRequestAnimationFrame ||
|
108
|
+
window.mozRequestAnimationFrame ||
|
109
|
+
window.oRequestAnimationFrame ||
|
110
|
+
window.msRequestAnimationFrame ||
|
111
|
+
function(callback, element) {
|
112
|
+
var currTime = new Date().getTime();
|
113
|
+
var timeToCall = Math.max(0, 16 - (currTime - lastTime));
|
114
|
+
var id = window.setTimeout(function() {
|
115
|
+
callback(currTime + timeToCall);
|
116
|
+
}, timeToCall);
|
117
|
+
lastTime = currTime + timeToCall;
|
118
|
+
return id;
|
119
|
+
};
|
120
|
+
return requestAnimationFrame.call(window, callback, element);
|
121
|
+
},
|
122
|
+
|
123
|
+
cancelAnimationFrame = function(id) {
|
124
|
+
var cancelAnimationFrame =
|
125
|
+
window.cancelAnimationFrame ||
|
126
|
+
function(id) {
|
127
|
+
clearTimeout(id);
|
128
|
+
};
|
129
|
+
return cancelAnimationFrame.call(window, id);
|
130
|
+
};
|
131
|
+
|
132
|
+
return {
|
133
|
+
requestAnimationFrame: requestAnimationFrame,
|
134
|
+
cancelAnimationFrame: cancelAnimationFrame
|
135
|
+
};
|
136
|
+
})();
|
137
|
+
|
138
|
+
SmoothieChart.prototype.addTimeSeries = function(timeSeries, options) {
|
139
|
+
this.seriesSet.push({timeSeries: timeSeries, options: options || {}});
|
140
|
+
};
|
141
|
+
|
142
|
+
SmoothieChart.prototype.removeTimeSeries = function(timeSeries) {
|
143
|
+
this.seriesSet.splice(this.seriesSet.indexOf(timeSeries), 1);
|
144
|
+
};
|
145
|
+
|
146
|
+
SmoothieChart.prototype.streamTo = function(canvas, delay) {
|
147
|
+
this.canvas = canvas;
|
148
|
+
this.delay = delay;
|
149
|
+
this.start();
|
150
|
+
};
|
151
|
+
|
152
|
+
SmoothieChart.prototype.start = function() {
|
153
|
+
if (!this.frame) {
|
154
|
+
this.animate();
|
155
|
+
}
|
156
|
+
};
|
157
|
+
|
158
|
+
SmoothieChart.prototype.animate = function() {
|
159
|
+
this.frame = SmoothieChart.AnimateCompatibility.requestAnimationFrame(this.animate.bind(this));
|
160
|
+
this.render(this.canvas, new Date().getTime() - (this.delay || 0));
|
161
|
+
};
|
162
|
+
|
163
|
+
SmoothieChart.prototype.stop = function() {
|
164
|
+
if (this.frame) {
|
165
|
+
SmootheiChart.AnimateCompatibility.cancelAnimationFrame( this.frame );
|
166
|
+
delete this.frame;
|
167
|
+
}
|
168
|
+
};
|
169
|
+
|
170
|
+
// Sample timestamp formatting function
|
171
|
+
SmoothieChart.timeFormatter = function(dateObject) {
|
172
|
+
function pad2(number){return (number < 10 ? '0' : '') + number};
|
173
|
+
return pad2(dateObject.getHours())+':'+pad2(dateObject.getMinutes())+':'+pad2(dateObject.getSeconds());
|
174
|
+
};
|
175
|
+
|
176
|
+
SmoothieChart.prototype.render = function(canvas, time) {
|
177
|
+
var canvasContext = canvas.getContext("2d");
|
178
|
+
var options = this.options;
|
179
|
+
var dimensions = {top: 0, left: 0, width: canvas.clientWidth, height: canvas.clientHeight};
|
180
|
+
|
181
|
+
// Save the state of the canvas context, any transformations applied in this method
|
182
|
+
// will get removed from the stack at the end of this method when .restore() is called.
|
183
|
+
canvasContext.save();
|
184
|
+
|
185
|
+
// Round time down to pixel granularity, so motion appears smoother.
|
186
|
+
time = time - time % options.millisPerPixel;
|
187
|
+
|
188
|
+
// Move the origin.
|
189
|
+
canvasContext.translate(dimensions.left, dimensions.top);
|
190
|
+
|
191
|
+
// Create a clipped rectangle - anything we draw will be constrained to this rectangle.
|
192
|
+
// This prevents the occasional pixels from curves near the edges overrunning and creating
|
193
|
+
// screen cheese (that phrase should need no explanation).
|
194
|
+
canvasContext.beginPath();
|
195
|
+
canvasContext.rect(0, 0, dimensions.width, dimensions.height);
|
196
|
+
canvasContext.clip();
|
197
|
+
|
198
|
+
// Clear the working area.
|
199
|
+
canvasContext.save();
|
200
|
+
canvasContext.fillStyle = options.grid.fillStyle;
|
201
|
+
canvasContext.clearRect(0, 0, dimensions.width, dimensions.height);
|
202
|
+
canvasContext.fillRect(0, 0, dimensions.width, dimensions.height);
|
203
|
+
canvasContext.restore();
|
204
|
+
|
205
|
+
// Grid lines....
|
206
|
+
canvasContext.save();
|
207
|
+
canvasContext.lineWidth = options.grid.lineWidth || 1;
|
208
|
+
canvasContext.strokeStyle = options.grid.strokeStyle || '#ffffff';
|
209
|
+
// Vertical (time) dividers.
|
210
|
+
if (options.grid.millisPerLine > 0) {
|
211
|
+
for (var t = time - (time % options.grid.millisPerLine); t >= time - (dimensions.width * options.millisPerPixel); t -= options.grid.millisPerLine) {
|
212
|
+
canvasContext.beginPath();
|
213
|
+
var gx = Math.round(dimensions.width - ((time - t) / options.millisPerPixel));
|
214
|
+
if (options.grid.sharpLines)
|
215
|
+
gx -= 0.5;
|
216
|
+
canvasContext.moveTo(gx, 0);
|
217
|
+
canvasContext.lineTo(gx, dimensions.height);
|
218
|
+
canvasContext.stroke();
|
219
|
+
// To display timestamps along the bottom
|
220
|
+
// May have to adjust millisPerLine to display non-overlapping timestamps, depending on the canvas size
|
221
|
+
if (options.timestampFormatter){
|
222
|
+
var tx=new Date(t);
|
223
|
+
// Formats the timestamp based on user specified formatting function
|
224
|
+
// SmoothieChart.timeFormatter function above is one such formatting option
|
225
|
+
var ts = options.timestampFormatter(tx);
|
226
|
+
var txtwidth=(canvasContext.measureText(ts).width/2)+canvasContext.measureText(minValueString).width + 4;
|
227
|
+
if (gx<dimensions.width - txtwidth){
|
228
|
+
canvasContext.fillStyle = options.labels.fillStyle;
|
229
|
+
// Insert the time string so it doesn't overlap on the minimum value
|
230
|
+
canvasContext.fillText(ts, gx-(canvasContext.measureText(ts).width / 2), dimensions.height-2);
|
231
|
+
}
|
232
|
+
}
|
233
|
+
canvasContext.closePath();
|
234
|
+
}
|
235
|
+
}
|
236
|
+
|
237
|
+
// Horizontal (value) dividers.
|
238
|
+
for (var v = 1; v < options.grid.verticalSections; v++) {
|
239
|
+
var gy = Math.round(v * dimensions.height / options.grid.verticalSections);
|
240
|
+
if (options.grid.sharpLines)
|
241
|
+
gy -= 0.5;
|
242
|
+
canvasContext.beginPath();
|
243
|
+
canvasContext.moveTo(0, gy);
|
244
|
+
canvasContext.lineTo(dimensions.width, gy);
|
245
|
+
canvasContext.stroke();
|
246
|
+
canvasContext.closePath();
|
247
|
+
}
|
248
|
+
// Bounding rectangle.
|
249
|
+
canvasContext.beginPath();
|
250
|
+
canvasContext.strokeRect(0, 0, dimensions.width, dimensions.height);
|
251
|
+
canvasContext.closePath();
|
252
|
+
canvasContext.restore();
|
253
|
+
|
254
|
+
// Calculate the current scale of the chart, from all time series.
|
255
|
+
var maxValue = Number.NaN;
|
256
|
+
var minValue = Number.NaN;
|
257
|
+
|
258
|
+
for (var d = 0; d < this.seriesSet.length; d++) {
|
259
|
+
// TODO(ndunn): We could calculate / track these values as they stream in.
|
260
|
+
var timeSeries = this.seriesSet[d].timeSeries;
|
261
|
+
if (!isNaN(timeSeries.maxValue)) {
|
262
|
+
maxValue = !isNaN(maxValue) ? Math.max(maxValue, timeSeries.maxValue) : timeSeries.maxValue;
|
263
|
+
}
|
264
|
+
|
265
|
+
if (!isNaN(timeSeries.minValue)) {
|
266
|
+
minValue = !isNaN(minValue) ? Math.min(minValue, timeSeries.minValue) : timeSeries.minValue;
|
267
|
+
}
|
268
|
+
}
|
269
|
+
|
270
|
+
if (isNaN(maxValue) && isNaN(minValue)) {
|
271
|
+
canvasContext.restore(); // without this there is crash in Android browser
|
272
|
+
return;
|
273
|
+
}
|
274
|
+
|
275
|
+
// Scale the maxValue to add padding at the top if required
|
276
|
+
if (options.maxValue != null)
|
277
|
+
maxValue = options.maxValue;
|
278
|
+
else
|
279
|
+
maxValue = maxValue * options.maxValueScale;
|
280
|
+
// Set the minimum if we've specified one
|
281
|
+
if (options.minValue != null)
|
282
|
+
minValue = options.minValue;
|
283
|
+
var targetValueRange = maxValue - minValue;
|
284
|
+
this.currentValueRange += options.scaleSmoothing*(targetValueRange - this.currentValueRange);
|
285
|
+
this.currentVisMinValue += options.scaleSmoothing*(minValue - this.currentVisMinValue);
|
286
|
+
var valueRange = this.currentValueRange;
|
287
|
+
var visMinValue = this.currentVisMinValue;
|
288
|
+
|
289
|
+
// For each data set...
|
290
|
+
for (var d = 0; d < this.seriesSet.length; d++) {
|
291
|
+
canvasContext.save();
|
292
|
+
var timeSeries = this.seriesSet[d].timeSeries;
|
293
|
+
var dataSet = timeSeries.data;
|
294
|
+
var seriesOptions = this.seriesSet[d].options;
|
295
|
+
|
296
|
+
// Delete old data that's moved off the left of the chart.
|
297
|
+
// We must always keep the last expired data point as we need this to draw the
|
298
|
+
// line that comes into the chart, but any points prior to that can be removed.
|
299
|
+
while (dataSet.length >= options.maxDataSetLength && dataSet[1][0] < time - (dimensions.width * options.millisPerPixel)) {
|
300
|
+
dataSet.splice(0, 1);
|
301
|
+
}
|
302
|
+
|
303
|
+
// Set style for this dataSet.
|
304
|
+
canvasContext.lineWidth = seriesOptions.lineWidth || 1;
|
305
|
+
canvasContext.strokeStyle = seriesOptions.strokeStyle || '#ffffff';
|
306
|
+
// Draw the line...
|
307
|
+
canvasContext.beginPath();
|
308
|
+
// Retain lastX, lastY for calculating the control points of bezier curves.
|
309
|
+
var firstX = 0, lastX = 0, lastY = 0;
|
310
|
+
for (var i = 0; i < dataSet.length; i++) {
|
311
|
+
// TODO: Deal with dataSet.length < 2.
|
312
|
+
var x = Math.round(dimensions.width - ((time - dataSet[i][0]) / options.millisPerPixel));
|
313
|
+
var value = dataSet[i][1];
|
314
|
+
var offset = value - visMinValue;
|
315
|
+
var scaledValue = dimensions.height - (valueRange ? Math.round((offset / valueRange) * dimensions.height) : 0);
|
316
|
+
var y = Math.max(Math.min(scaledValue, dimensions.height - 1), 1); // Ensure line is always on chart.
|
317
|
+
|
318
|
+
if (i == 0) {
|
319
|
+
firstX = x;
|
320
|
+
canvasContext.moveTo(x, y);
|
321
|
+
}
|
322
|
+
// Great explanation of Bezier curves: http://en.wikipedia.org/wiki/Bezier_curve#Quadratic_curves
|
323
|
+
//
|
324
|
+
// Assuming A was the last point in the line plotted and B is the new point,
|
325
|
+
// we draw a curve with control points P and Q as below.
|
326
|
+
//
|
327
|
+
// A---P
|
328
|
+
// |
|
329
|
+
// |
|
330
|
+
// |
|
331
|
+
// Q---B
|
332
|
+
//
|
333
|
+
// Importantly, A and P are at the same y coordinate, as are B and Q. This is
|
334
|
+
// so adjacent curves appear to flow as one.
|
335
|
+
//
|
336
|
+
else {
|
337
|
+
switch (options.interpolation) {
|
338
|
+
case "line":
|
339
|
+
canvasContext.lineTo(x,y);
|
340
|
+
break;
|
341
|
+
case "bezier":
|
342
|
+
default:
|
343
|
+
canvasContext.bezierCurveTo( // startPoint (A) is implicit from last iteration of loop
|
344
|
+
Math.round((lastX + x) / 2), lastY, // controlPoint1 (P)
|
345
|
+
Math.round((lastX + x)) / 2, y, // controlPoint2 (Q)
|
346
|
+
x, y); // endPoint (B)
|
347
|
+
break;
|
348
|
+
}
|
349
|
+
}
|
350
|
+
|
351
|
+
lastX = x; lastY = y;
|
352
|
+
}
|
353
|
+
if (dataSet.length > 0 && seriesOptions.fillStyle) {
|
354
|
+
// Close up the fill region.
|
355
|
+
canvasContext.lineTo(dimensions.width + seriesOptions.lineWidth + 1, lastY);
|
356
|
+
canvasContext.lineTo(dimensions.width + seriesOptions.lineWidth + 1, dimensions.height + seriesOptions.lineWidth + 1);
|
357
|
+
canvasContext.lineTo(firstX, dimensions.height + seriesOptions.lineWidth);
|
358
|
+
canvasContext.fillStyle = seriesOptions.fillStyle;
|
359
|
+
canvasContext.fill();
|
360
|
+
}
|
361
|
+
canvasContext.stroke();
|
362
|
+
canvasContext.closePath();
|
363
|
+
canvasContext.restore();
|
364
|
+
}
|
365
|
+
|
366
|
+
// Draw the axis values on the chart.
|
367
|
+
if (!options.labels.disabled) {
|
368
|
+
canvasContext.fillStyle = options.labels.fillStyle;
|
369
|
+
var maxValueString = parseFloat(maxValue).toFixed(2);
|
370
|
+
var minValueString = parseFloat(minValue).toFixed(2);
|
371
|
+
canvasContext.fillText(maxValueString, dimensions.width - canvasContext.measureText(maxValueString).width - 2, 10);
|
372
|
+
canvasContext.fillText(minValueString, dimensions.width - canvasContext.measureText(minValueString).width - 2, dimensions.height - 2);
|
373
|
+
}
|
374
|
+
|
375
|
+
canvasContext.restore(); // See .save() above.
|
376
|
+
};
|
File without changes
|
@@ -49,12 +49,15 @@
|
|
49
49
|
});
|
50
50
|
}
|
51
51
|
|
52
|
+
var editTemplate = _.template(
|
53
|
+
"<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
|
+
)
|
58
|
+
|
52
59
|
Gauge.prototype.editForm = function() {
|
53
|
-
return
|
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)
|
60
|
+
return editTemplate(this);
|
58
61
|
}
|
59
62
|
|
60
63
|
Gauge.prototype.reflow = function() {
|