4 var callLuciRealtimeStats
= rpc
.declare({
6 method
: 'getRealtimeStats',
7 params
: [ 'mode', 'device' ],
14 Math
.log2
= Math
.log2
|| function(x
) { return Math
.log(x
) * Math
.LOG2E
; };
16 return L
.view
.extend({
19 this.loadSVG(L
.resource('load.svg'))
23 updateGraph: function(svg
, lines
, cb
) {
24 var G
= svg
.firstElementChild
;
26 var view
= document
.querySelector('#view');
28 var width
= view
.offsetWidth
- 2;
32 var data_wanted
= Math
.floor(width
/ step
);
37 for (var i
= 0; i
< lines
.length
; i
++)
47 /* prefill datasets */
48 for (var i
= 0; i
< data_values
.length
; i
++)
49 for (var j
= 0; j
< data_wanted
; j
++)
50 data_values
[i
][j
] = 0;
52 /* plot horizontal time interval lines */
53 for (var i
= width
% (step
* 60); i
< width
; i
+= step
* 60) {
54 var line
= document
.createElementNS('http://www.w3.org/2000/svg', 'line');
55 line
.setAttribute('x1', i
);
56 line
.setAttribute('y1', 0);
57 line
.setAttribute('x2', i
);
58 line
.setAttribute('y2', '100%');
59 line
.setAttribute('style', 'stroke:black;stroke-width:0.1');
61 var text
= document
.createElementNS('http://www.w3.org/2000/svg', 'text');
62 text
.setAttribute('x', i
+ 5);
63 text
.setAttribute('y', 15);
64 text
.setAttribute('style', 'fill:#eee; font-size:9pt; font-family:sans-serif; text-shadow:1px 1px 1px #000');
65 text
.appendChild(document
.createTextNode(Math
.round((width
- i
) / step
/ 60) + 'm'));
71 info
.interval
= pollInterval
;
72 info
.timeframe
= data_wanted
/ 60;
88 pollData: function() {
89 L
.Poll
.add(L
.bind(function() {
92 for (var i
= 0; i
< graphPolls
.length
; i
++) {
93 var ctx
= graphPolls
[i
];
94 tasks
.push(L
.resolveDefault(callLuciRealtimeStats('load'), []));
97 return Promise
.all(tasks
).then(L
.bind(function(datasets
) {
98 for (var gi
= 0; gi
< graphPolls
.length
; gi
++) {
99 var ctx
= graphPolls
[gi
],
106 var data_wanted
= Math
.floor(ctx
.width
/ ctx
.step
);
107 var last_timestamp
= NaN
;
109 for (var i
= 0, di
= 0; di
< lines
.length
; di
++) {
110 if (lines
[di
] == null)
113 var multiply
= (lines
[di
].multiply
!= null) ? lines
[di
].multiply
: 1,
114 offset
= (lines
[di
].offset
!= null) ? lines
[di
].offset
: 0;
116 for (var j
= ctx
.timestamp
? 0 : 1; j
< data
.length
; j
++) {
117 /* skip overlapping entries */
118 if (data
[j
][0] <= ctx
.timestamp
)
123 last_timestamp
= data
[j
][0];
126 info
.line_current
[i
] = data
[j
][di
+ 1] * multiply
;
127 info
.line_current
[i
] -= Math
.min(info
.line_current
[i
], offset
);
128 values
[i
].push(info
.line_current
[i
]);
134 /* cut off outdated entries */
135 ctx
.fill
= Math
.min(ctx
.fill
, data_wanted
);
137 for (var i
= 0; i
< values
.length
; i
++) {
138 var len
= values
[i
].length
;
139 values
[i
] = values
[i
].slice(len
- data_wanted
, len
);
141 /* find peaks, averages */
142 info
.line_peak
[i
] = NaN
;
143 info
.line_average
[i
] = 0;
145 for (var j
= 0; j
< values
[i
].length
; j
++) {
146 info
.line_peak
[i
] = isNaN(info
.line_peak
[i
]) ? values
[i
][j
] : Math
.max(info
.line_peak
[i
], values
[i
][j
]);
147 info
.line_average
[i
] += values
[i
][j
];
150 info
.line_average
[i
] = info
.line_average
[i
] / ctx
.fill
;
153 info
.peak
= Math
.max
.apply(Math
, info
.line_peak
);
155 /* remember current timestamp, calculate horizontal scale */
156 if (!isNaN(last_timestamp
))
157 ctx
.timestamp
= last_timestamp
;
159 var size
= Math
.floor(Math
.log2(info
.peak
)),
160 div
= Math
.pow(2, size
- (size
% 10)),
161 mult
= info
.peak
/ div
,
162 mult
= (mult
< 5) ? 2 : ((mult
< 50) ? 10 : ((mult
< 500) ? 100 : 1000));
164 info
.peak
= info
.peak
+ (mult
* div
) - (info
.peak
% (mult
* div
));
166 data_scale
= ctx
.height
/ info
.peak
;
169 for (var i
= 0, di
= 0; di
< lines
.length
; di
++) {
170 if (lines
[di
] == null)
173 var el
= ctx
.svg
.firstElementChild
.getElementById(lines
[di
].line
),
174 pt
= '0,' + ctx
.height
,
180 for (var j
= 0; j
< values
[i
].length
; j
++) {
181 var x
= j
* ctx
.step
;
183 y
= ctx
.height
- Math
.floor(values
[i
][j
] * data_scale
);
184 //y -= Math.floor(y % (1 / data_scale));
186 pt
+= ' ' + x
+ ',' + y
;
189 pt
+= ' ' + ctx
.width
+ ',' + y
+ ' ' + ctx
.width
+ ',' + ctx
.height
;
191 el
.setAttribute('points', pt
);
196 info
.label_25
= 0.25 * info
.peak
;
197 info
.label_50
= 0.50 * info
.peak
;
198 info
.label_75
= 0.75 * info
.peak
;
200 if (typeof(ctx
.cb
) == 'function')
201 ctx
.cb(ctx
.svg
, info
);
204 }, this), pollInterval
);
207 loadSVG: function(src
) {
208 return L
.Request
.get(src
).then(function(response
) {
210 throw new Error(response
.statusText
);
213 'style': 'width:100%;height:300px;border:1px solid #000;background:#fff'
214 }, E(response
.text()));
218 render: function(data
) {
223 E('div', { 'class': 'right' }, E('small', { 'id': 'scale' }, '-')),
226 E('div', { 'class': 'table', 'style': 'width:100%;table-layout:fixed' }, [
227 E('div', { 'class': 'tr' }, [
228 E('div', { 'class': 'td right top' }, E('strong', { 'style': 'border-bottom:2px solid #f00' }, [ _('1 Minute Load:') ])),
229 E('div', { 'class': 'td', 'id': 'lb_load01_cur' }, [ '0.00' ]),
231 E('div', { 'class': 'td right top' }, E('strong', {}, [ _('Average:') ])),
232 E('div', { 'class': 'td', 'id': 'lb_load01_avg' }, [ '0.00' ]),
234 E('div', { 'class': 'td right top' }, E('strong', {}, [ _('Peak:') ])),
235 E('div', { 'class': 'td', 'id': 'lb_load01_peak' }, [ '0.00' ])
237 E('div', { 'class': 'tr' }, [
238 E('div', { 'class': 'td right top' }, E('strong', { 'style': 'border-bottom:2px solid #f60' }, [ _('5 Minute Load:') ])),
239 E('div', { 'class': 'td', 'id': 'lb_load05_cur' }, [ '0.00' ]),
241 E('div', { 'class': 'td right top' }, E('strong', {}, [ _('Average:') ])),
242 E('div', { 'class': 'td', 'id': 'lb_load05_avg' }, [ '0.00' ]),
244 E('div', { 'class': 'td right top' }, E('strong', {}, [ _('Peak:') ])),
245 E('div', { 'class': 'td', 'id': 'lb_load05_peak' }, [ '0.00' ])
247 E('div', { 'class': 'tr' }, [
248 E('div', { 'class': 'td right top' }, E('strong', { 'style': 'border-bottom:2px solid #fa0' }, [ _('15 Minute Load:') ])),
249 E('div', { 'class': 'td', 'id': 'lb_load15_cur' }, [ '0.00' ]),
251 E('div', { 'class': 'td right top' }, E('strong', {}, [ _('Average:') ])),
252 E('div', { 'class': 'td', 'id': 'lb_load15_avg' }, [ '0.00' ]),
254 E('div', { 'class': 'td right top' }, E('strong', {}, [ _('Peak:') ])),
255 E('div', { 'class': 'td', 'id': 'lb_load15_peak' }, [ '0.00' ])
260 this.updateGraph(svg
, [ { line
: 'load01' }, { line
: 'load05' }, { line
: 'load15' } ], function(svg
, info
) {
261 var G
= svg
.firstElementChild
, tab
= svg
.parentNode
;
263 G
.getElementById('label_25').firstChild
.data
= '%.2f'.format(info
.label_25
/ 100);
264 G
.getElementById('label_50').firstChild
.data
= '%.2f'.format(info
.label_50
/ 100);
265 G
.getElementById('label_75').firstChild
.data
= '%.2f'.format(info
.label_75
/ 100);
267 tab
.querySelector('#scale').firstChild
.data
= _('(%d minute window, %d second interval)').format(info
.timeframe
, info
.interval
);
269 tab
.querySelector('#lb_load01_cur').firstChild
.data
= '%.2f'.format(info
.line_current
[0] / 100);
270 tab
.querySelector('#lb_load01_avg').firstChild
.data
= '%.2f'.format(info
.line_average
[0] / 100);
271 tab
.querySelector('#lb_load01_peak').firstChild
.data
= '%.2f'.format(info
.line_peak
[0] / 100);
273 tab
.querySelector('#lb_load05_cur').firstChild
.data
= '%.2f'.format(info
.line_current
[1] / 100);
274 tab
.querySelector('#lb_load05_avg').firstChild
.data
= '%.2f'.format(info
.line_average
[1] / 100);
275 tab
.querySelector('#lb_load05_peak').firstChild
.data
= '%.2f'.format(info
.line_peak
[1] / 100);
277 tab
.querySelector('#lb_load15_cur').firstChild
.data
= '%.2f'.format(info
.line_current
[2] / 100);
278 tab
.querySelector('#lb_load15_avg').firstChild
.data
= '%.2f'.format(info
.line_average
[2] / 100);
279 tab
.querySelector('#lb_load15_peak').firstChild
.data
= '%.2f'.format(info
.line_peak
[2] / 100);
287 handleSaveApply
: null,