2 javascript display of raw SMA webbox data
3 Copyright Andrew Tridgell 2010
4 Released under GNU GPL v3 or later
8 var is_chrome = (navigator.userAgent.toLowerCase().indexOf('chrome') != -1);
11 return the variables set after a '#' as a hash
13 function parse_hashvariables() {
15 var url = window.location.hash.slice(1);
16 var vars = url.split(';');
17 for (var i=0; i<vars.length; i++) {
18 var x = vars[i].split('=');
26 hashvars = parse_hashvariables();
29 rewrite the URL hash so you can bookmark particular dates
31 function rewrite_hashvars(vars) {
34 hash += '' + x + '=' + vars[x] + ';';
36 hash = hash.slice(0,hash.length-1);
37 window.location.hash = hash;
41 round a date back to midnight
43 function date_round(d) {
48 d2.setMilliseconds(0);
55 function canberraDate() {
57 return date_round(new Date(d.getTime() + (tz_difference*60*60*1000)));
63 pvdate = date_round(new Date());
66 tz_difference = 11 + (pvdate.getTimezoneOffset()/60);
70 /* marker for whether we are in a redraw with new data */
78 document.write("<h3><a STYLE='text-decoration:none' href=\"javascript:toggle_div('"+h+"')\"><img src='icons/icon_unhide_16.png' width='16' height='16' border='0' id='img-"+h+"'></a> "+h+"</h3>");
83 create a div for a graph
85 function graph_div(divname) {
89 '<td valign="top"><div id="' + divname + '" style="width:700px; height:350px;"></div></td>' +
90 '<td valign="top"> </td>' +
91 '<td valign="top"><div id="' + divname + ':labels"></div></td>' +
100 function hide_div(divname, hidden) {
101 var div = document.getElementById(divname);
103 div.style.display = "none";
105 div.style.display = "block";
109 /* unhide the loading div when busy */
112 function loading(busy) {
115 if (loading_counter == 1) {
116 started_loading=new Date();
117 hide_div("loading", false);
120 if (loading_counter > 0) {
122 if (loading_counter == 0) {
123 hide_div("loading", true);
125 var load_time = d.getTime() - started_loading.getTime();
126 writeDebug("Loading took: " + (load_time/1000));
133 /* a global call queue */
134 global_queue = new Array();
135 graph_queue = new Array();
140 function run_queue(q) {
142 var t_start = new Date();
144 var t_end = new Date();
147 var tdelay = (t_end.getTime() - t_start.getTime())/4;
151 setTimeout(function() { run_queue(q);}, tdelay);
157 queue a call. This is used to serialise long async operations in the
158 browser, so that you get less timeouts. It is especially needed on
159 IE, where the canvas widget is terribly slow.
161 function queue_call(q, callback, arg) {
162 q.push( { callback: callback, arg : arg });
164 setTimeout(function() { run_queue(q); }, 1);
168 function queue_global(callback, arg) {
169 queue_call(global_queue, callback, arg);
172 function queue_graph(callback, arg) {
173 queue_call(graph_queue, callback, arg);
178 date parser. Not completely general, but good enough
180 function parse_date(s, basedate) {
181 if (s.length == 5 && s[2] == ':') {
182 /* its a time since midnight */
183 var h = (+s.substring(0, 2));
184 var m = (+s.substring(3));
185 var d = basedate.getTime() + 1000*(h*60*60 + m*60);
188 if (s.length == 8 && s[2] == ':' && s[5] == ':') {
189 /* its a time since midnight */
190 var h = (+s.substring(0, 2));
191 var m = (+s.substring(3, 5));
192 var sec = (+s.substring(6));
193 var d = basedate.getTime() + 1000*(h*60*60 + m*60 + sec);
196 if (s.search("-") != -1) {
197 s = s.replace("-", "/", "g");
200 var x = s.split('/');
205 return date_round(d);
207 if (s.search("/") != -1) {
208 return date_round(new Date(s));
210 /* assume time in milliseconds since 1970 */
216 return a YYYY-MM-DD date string
218 function date_YMD(d) {
219 return '' + intLength(d.getFullYear(),4) + '-' + intLength(d.getMonth()+1,2) + '-' + intLength(d.getDate(),2);
223 parse the date portion of a filename which starts with YYYY-MM-DD after a directory
225 function filename_date(filename) {
226 var idx = filename.lastIndexOf("/");
228 filename = filename.substring(idx+1);
230 if (filename[4] == '-' && filename[7] == '-') {
231 /* looks like a date */
232 var d = date_round(new Date());
233 d.setYear(+filename.substring(0,4));
234 d.setMonth(filename.substring(5,7)-1);
235 d.setDate(filename.substring(8,10));
245 function parse_value(s) {
246 if (s.substring(0,1) == '"') {
247 s = unescape(s.substring(1,s.length-1));
253 var n = new Number(s);
260 /* keep a cache of loaded CSV files */
261 CSV_Cache = new Array();
265 load a CSV file, returning column names and data via a callback
267 function load_CSV(filename, callback) {
269 /* maybe its in the global cache? */
270 if (CSV_Cache[filename] !== undefined) {
272 if (CSV_Cache[filename].pending) {
273 /* its pending load by someone else. Add ourselves to the notify
274 queue so we are told when it is done */
275 CSV_Cache[filename].queue.push({filename:filename, callback:callback});
279 /* its ready in the cache - return it via a delayed callback */
280 if (CSV_Cache[filename].data == null) {
281 var d = { filename: CSV_Cache[filename].filename,
284 queue_global(callback, d);
286 var d = { filename: CSV_Cache[filename].filename,
287 labels: CSV_Cache[filename].labels.slice(0),
288 data: CSV_Cache[filename].data.slice(0) };
289 queue_global(callback, d);
294 /* mark this one pending */
295 CSV_Cache[filename] = { filename:filename, pending: true, queue: new Array()};
298 async callback when the CSV is loaded
300 function load_CSV_callback(caller) {
301 var data = new Array();
302 var labels = new Array();
304 if (filename.search(".csv") != -1) {
305 var csv = caller.r.responseText.split(/\n/g);
307 /* assume first line is column labels */
308 labels = csv[0].split(/,/g);
309 for (var i=0; i<labels.length; i++) {
310 labels[i] = labels[i].replace(" ", " ", "g");
313 /* the rest is data, we assume comma separation */
314 for (var i=1; i<csv.length; i++) {
315 var row = csv[i].split(/,/g);
316 if (row.length <= 1) {
319 data[i-1] = new Array();
320 data[i-1][0] = parse_date(row[0], caller.basedate);
321 for (var j=1; j<row.length; j++) {
322 data[i-1][j] = parse_value(row[j]);
326 var xml = caller.r.responseText.split(/\n/g);
329 for (var i=0; i < xml.length; i++) {
330 if (xml[i].search("<hist>") != -1) {
333 var row = xml[i].split("<");
336 if (row.length < 2) {
339 var rdata = new Array();
340 for (var j=1; j<row.length; j++) {
341 var v = row[j].split(">");
342 if (v[0].substring(0,1) == "/") {
343 var tag = v[0].substring(1);
344 if (prefix.substring(prefix.length-tag.length) == tag) {
345 prefix = prefix.substring(0, prefix.length-tag.length);
346 if (prefix.substring(prefix.length-1) == ".") {
347 prefix = prefix.substring(0, prefix.length-1);
351 } else if (v[1] == "") {
358 if (v[0] == "time") {
359 var dtime = parse_date(v[1], caller.basedate);
360 if (last_date != 0 && dtime < last_date) {
361 if (i < xml.length/2) {
363 the earlier points were from the last night
365 for (var ii=0; ii<data.length; ii++) {
366 data[ii][0] = data[ii][0] - 24*3600*1000;
368 writeDebug("subtraced day from: " + data.length);
370 /* we've gone past the end of the day */
375 dtime += 24*3600*1000;
380 } else if (v[1] != "") {
381 labels[num_labels+1] = prefix + "." + v[0];
382 rdata[num_labels+1] = parseFloat(v[1]);
386 data[data.length] = rdata;
390 /* save into the global cache */
391 CSV_Cache[caller.filename].labels = labels;
392 CSV_Cache[caller.filename].data = data;
394 /* give the caller a copy of the data (via slice()), as they may
396 var d = { filename: CSV_Cache[filename].filename,
397 labels: CSV_Cache[filename].labels.slice(0),
398 data: CSV_Cache[filename].data.slice(0) };
399 queue_global(caller.callback, d);
401 /* fire off any pending callbacks */
402 while (CSV_Cache[caller.filename].queue.length > 0) {
403 var qe = CSV_Cache[caller.filename].queue.shift();
404 var d = { filename: filename,
405 labels: CSV_Cache[filename].labels.slice(0),
406 data: CSV_Cache[filename].data.slice(0) };
407 queue_global(qe.callback, d);
409 CSV_Cache[caller.filename].pending = false;
410 CSV_Cache[caller.filename].queue = null;
413 /* make the async request for the file */
414 var caller = new Object();
415 caller.r = new XMLHttpRequest();
416 caller.callback = callback;
417 caller.filename = filename;
418 caller.basedate = filename_date(filename);
420 /* check the status when that returns */
421 caller.r.onreadystatechange = function() {
422 if (caller.r.readyState == 4) {
423 if (caller.r.status == 200) {
424 queue_global(load_CSV_callback, caller);
426 /* the load failed */
427 queue_global(caller.callback, { filename: filename, data: null, labels: null });
428 while (CSV_Cache[caller.filename].queue.length > 0) {
429 var qe = CSV_Cache[caller.filename].queue.shift();
430 var d = { filename: CSV_Cache[filename].filename,
433 queue_global(qe.callback, d);
435 CSV_Cache[caller.filename].pending = false;
436 CSV_Cache[caller.filename].queue = null;
437 CSV_Cache[caller.filename].data = null;
438 CSV_Cache[caller.filename].labels = null;
442 caller.r.open("GET",filename,true);
446 function array_equal(a1, a2) {
447 if (a1.length != a2.length) {
450 for (var i=0; i<a1.length; i++) {
451 if (a1[i] != a2[i]) {
459 combine two arrays that may have different labels
461 function combine_arrays(a1, l1, a2, l2) {
462 if (array_equal(l1, l2)) {
463 return a1.concat(a2);
465 /* we have two combine two arrays with different labels */
466 var map = new Array();
467 for (var i=0; i<l1.length; i++) {
468 map[i] = l2.indexOf(l1[i]);
471 for (var y=0; y<a2.length; y++) {
473 for (var x=0; x<l1.length; x++) {
477 r[x] = a2[y][map[x]];
486 load a comma separated list of CSV files, combining the data
488 function load_CSV_array(filenames, callback) {
489 var c = new Object();
490 c.filename = filenames;
491 c.files = filenames.split(',');
492 c.callback = callback;
493 c.data = new Array();
494 c.labels = new Array();
498 async callback when a CSV is loaded
500 function load_CSV_array_callback(d) {
502 var i = c.files.indexOf(d.filename);
504 c.labels[i] = d.labels;
505 if (c.count == c.files.length) {
506 var ret = { filename: c.filename, data: c.data[0], labels: c.labels[0]};
507 for (var i=1; i<c.files.length; i++) {
508 if (c.data[i] != null) {
509 if (ret.data == null) {
510 ret.data = c.data[i];
511 ret.labels = c.labels[i];
513 ret.data = combine_arrays(ret.data, ret.labels, c.data[i], c.labels[i]);
517 if (ret.data == null) {
518 hide_div("nodata", false);
520 hide_div("nodata", true);
522 queue_global(c.callback, ret);
526 for (var i=0; i<c.files.length; i++) {
527 load_CSV(c.files[i], load_CSV_array_callback);
532 format an integer with N digits by adding leading zeros
533 javascript is so lame ...
535 function intLength(v, N) {
537 while (r.length < N) {
545 return the list of CSV files for the inverters for date pvdate
547 function csv_files() {
548 var list = new Array();
549 var oneday = 24*60*60*1000;
550 var start_date = pvdate.getTime() - (period_days-1)*oneday;
551 if (start_date < first_data.getTime()) {
552 start_date = first_data.getTime();
554 for (var d=0; d<period_days; d++) {
555 var day = new Date(start_date + (d*oneday));
556 if (day.getTime() > pvdate.getTime()) {
559 for (var i=0; i<serialnums.length; i++) {
560 var f = CSV_directory + date_YMD(day) + "-WR5KA-08:" +
561 serialnums[i] + ".csv";
573 return the list of XML files for date pvdate
575 function xml_files() {
576 var list = new Array();
577 var oneday = 24*60*60*1000;
578 var start_date = pvdate.getTime() - (period_days-1)*oneday;
579 if (start_date < first_data.getTime()) {
580 start_date = first_data.getTime();
582 for (var d=0; d<period_days; d++) {
583 var day = new Date(start_date + (d*oneday));
584 if (day.getTime() > pvdate.getTime()) {
587 var f = XML_directory + date_YMD(day) + ".xml";
594 writeDebug("xml files: " + list);
601 return the position of v in an array or -1
603 function pos_in_array(a, v) {
604 for (var i=0; i<a.length; i++) {
613 see if v exists in array a
615 function in_array(a, v) {
616 return pos_in_array(a, v) != -1;
621 return a set of columns from a CSV file
623 function get_csv_data(filenames, columns, callback) {
624 var caller = new Object();
625 caller.d = new Array();
626 caller.columns = columns.slice(0);
627 caller.filenames = filenames.slice(0);
628 caller.callback = callback;
630 /* initially blank data - we can tell a load has completed when it
632 for (var i=0; i<caller.filenames.length; i++) {
633 caller.d[i] = { filename: caller.filenames[i], labels: null, data: null};
636 /* process one loaded CSV, mapping the data for
637 the requested columns */
638 function process_one_csv(d) {
639 var labels = new Array();
641 if (d.data == null) {
642 queue_global(caller.callback, d);
646 /* form the labels */
648 for (var i=0; i<caller.columns.length; i++) {
649 labels[i+1] = caller.columns[i];
652 /* get the column numbers */
653 var cnums = new Array();
655 for (var i=0; i<caller.columns.length; i++) {
656 cnums[i+1] = pos_in_array(d.labels, caller.columns[i]);
660 var data = new Array();
661 for (var i=0; i<d.data.length; i++) {
662 data[i] = new Array();
663 for (var j=0; j<cnums.length; j++) {
664 data[i][j] = d.data[i][cnums[j]];
670 for (var f=0; f<caller.filenames.length; f++) {
671 if (d.filename == caller.d[f].filename) {
672 caller.d[f].labels = labels;
673 caller.d[f].data = data;
677 /* see if all the files are now loaded */
678 for (var f=0; f<caller.filenames.length; f++) {
679 if (caller.d[f].data == null) {
684 /* they are all loaded - make the callback */
685 queue_global(caller.callback, caller.d);
688 /* start the loading */
689 for (var i=0; i<caller.filenames.length; i++) {
690 load_CSV_array(caller.filenames[i], process_one_csv);
696 apply a function to a set of data, giving it a new label
698 function apply_function(d, func, label) {
702 for (var i=0; i<d.data.length; i++) {
704 d.data[i] = r.slice(0,1);
705 d.data[i][1] = func(r.slice(1))
707 d.labels = d.labels.slice(0,1);
712 /* currently displayed graphs, indexed by divname */
713 global_graphs = new Array();
716 find a graph by divname
718 function graph_find(divname) {
719 for (var i=0; i<global_graphs.length; i++) {
720 var g = global_graphs[i];
721 if (g.divname == divname) {
728 function nameAnnotation(ann) {
729 return "(" + ann.series + ", " + ann.xval + ")";
735 try to save an annotation via annotation.cgi
737 function save_annotation(ann) {
738 var r = new XMLHttpRequest();
740 "cgi/annotation.cgi?series="+escape(ann.series)+"&xval="+ann.xval+"&text="+escape(ann.text), true);
744 function round_annotations() {
745 for (var i=0; i<annotations.length; i++) {
746 annotations[i].xval = round_time(annotations[i].xval, defaultAttrs.averaging);
751 load annotations from annotations.csv
753 function load_annotations(g) {
754 function callback(d) {
755 var anns_by_name = new Array();
757 for (var i=0; i<d.data.length; i++) {
758 var xval = d.data[i][0] + (tz_difference*60*60*1000);
759 xval = round_time(xval, defaultAttrs.averaging);
760 if (xval.valueOf() < pvdate.valueOf() ||
761 xval.valueOf() >= (pvdate.valueOf() + (24*60*60*1000))) {
765 xval: xval.valueOf(),
766 series: d.data[i][1],
768 text: decodeURIComponent(d.data[i][2])
770 var a = anns_by_name[nameAnnotation(ann)];
771 if (a == undefined) {
772 anns_by_name[nameAnnotation(ann)] = annotations.length;
773 annotations.push(ann);
775 annotations[a] = ann;
776 if (ann.text == '') {
777 annotations.splice(a,1);
781 for (var i=0; i<global_graphs.length; i++) {
782 var g = global_graphs[i];
783 g.setAnnotations(annotations);
787 load_CSV("../CSV/annotations.csv", callback);
790 function annotation_highlight(ann, point, dg, event) {
791 saveBg = ann.div.style.backgroundColor;
792 ann.div.style.backgroundColor = '#ddd';
795 function annotation_unhighlight(ann, point, dg, event) {
796 ann.div.style.backgroundColor = saveBg;
800 handle annotation updates
802 function annotation_click(ann, point, dg, event) {
803 ann.text = prompt("Enter annotation", ann.text);
804 if (ann.text == null) {
807 for (var i=0; i<annotations.length; i++) {
808 if (annotations[i].xval == ann.xval && annotations[i].series == ann.series) {
809 annotations[i].text = ann.text;
810 if (ann.text == '' || ann.text == null) {
812 writeDebug("removing annnotation");
813 annotations.splice(i,1);
818 for (var i=0; i<global_graphs.length; i++) {
819 var g = global_graphs[i];
820 if (g.series_names.indexOf(ann.series) != -1) {
821 g.setAnnotations(annotations);
824 save_annotation(ann);
828 add a new annotation to one graph
830 function annotation_add_graph(g, p, ann) {
831 var anns = g.annotations();
834 if (ann.text == '') {
835 var idx = anns.indexOf(p);
840 p.annotation.text = ann.text;
845 g.setAnnotations(anns);
851 function annotation_add(event, p) {
854 xval: p.xval - (tz_difference*60*60*1000),
856 text: prompt("Enter annotation", ""),
858 if (ann.text == '' || ann.text == null) {
861 for (var i=0; i<global_graphs.length; i++) {
862 var g = global_graphs[i];
863 if (g.series_names.indexOf(p.name) != -1) {
864 annotation_add_graph(g, p, ann);
868 save_annotation(ann);
872 /* default dygraph attributes */
878 annotationMouseOverHandler: annotation_highlight,
879 annotationMouseOutHandler: annotation_unhighlight,
880 annotationClickHandler: annotation_click,
881 pointClickCallback: annotation_add
885 round to averaged time
887 function round_time(t, n) {
888 var t2 = t / (60*1000);
889 t2 = Math.round((t2/n)-0.5);
895 average some data over time
897 function average_data(data, n) {
898 var ret = new Array();
900 var counts = new Array();
901 for (y=0; y<data.length; y++) {
902 var t = round_time(data[y][0], n);
903 if (ret.length > 0 && t.getTime() > ret[ret.length-1][0].getTime() + (6*60*60*1000)) {
904 /* there is a big gap - insert a missing value */
905 var t0 = ret[ret.length-1][0];
906 var tavg = Math.round((t0.getTime()+t.getTime())/2);
907 var t2 = new Date(tavg);
909 ret[y2] = new Array();
911 counts[y2] = new Array();
912 for (var x=1; x<ret[y2-1].length; x++) {
918 if (ret.length > 0 && t.getTime() == ret[ret.length-1][0].getTime()) {
919 var y2 = ret.length-1;
920 for (var x=1; x<data[y].length; x++) {
921 if (data[y][x] != null) {
922 ret[y2][x] += data[y][x];
927 counts[y2] = new Array();
930 for (var x=1; x<ret[y2].length; x++) {
931 if (ret[y2][x] != null) {
937 for (y2=0; y2<ret.length; y2++) {
938 for (var x=1; x<ret[y2].length; x++) {
939 if (ret[y2][x] != null) {
940 ret[y2][x] /= counts[y2][x];
948 graph results from a set of CSV files:
949 - apply func1 to the name columns within each file
950 - apply func2 between the files
952 function graph_csv_files_func(divname, filenames, columns, func1, func2, attrs) {
953 /* load the csv files */
954 var caller = new Object();
955 caller.divname = divname;
956 caller.filenames = filenames.slice(0);
957 caller.columns = columns.slice(0);
958 caller.func1 = func1;
959 caller.func2 = func2;
960 caller.attrs = attrs;
962 if (attrs.series_base != undefined) {
963 caller.colname = attrs.series_base;
964 } else if (columns.length == 1) {
965 caller.colname = columns[0]
967 caller.colname = divname;
970 /* called when all the data is loaded and we're ready to apply the
971 functions and graph */
972 function loaded_callback(d) {
974 if (d[0] == undefined) {
979 for (var i=0; i<caller.filenames.length; i++) {
980 apply_function(d[i], caller.func1, caller.colname);
983 /* work out the y offsets to align the times */
984 var yoffsets = new Array();
985 for (var i=0; i<caller.filenames.length; i++) {
989 if (caller.attrs.missingValue !== undefined) {
990 missingValue = attrs.missingValue;
996 var data = d[0].data;
997 for (var y=0; y<data.length; y++) {
998 if (data[y][1] == missingValue || data[y][1] == null) {
1001 for (var f=1; f<caller.filenames.length; f++) {
1002 var y2 = y + yoffsets[f];
1003 if (y2 >= d[f].data.length) {
1004 y2 = d[f].data.length-1;
1009 while (y2 > 0 && d[f].data[y2][0] > data[y][0]) {
1012 while (y2 < (d[f].data.length-1) && d[f].data[y2][0] < data[y][0]) {
1015 yoffsets[f] = y2 - y;
1016 if (d[f].data[y2][0] != data[y][0] || d[f].data[y2][1] == missingValue || d[f].data[y2][1] == null) {
1017 data[y][f+1] = null;
1019 data[y][f+1] = d[f].data[y2][1];
1024 labels = new Array();
1025 if (caller.colname.constructor == Array) {
1026 labels = caller.colname.slice(0);
1028 labels[0] = d[0].labels[0];
1029 for (var i=0; i<caller.filenames.length; i++) {
1030 labels[i+1] = caller.colname + (i+1);
1034 var d2 = { labels: labels, data: data };
1035 apply_function(d2, caller.func2, caller.colname);
1037 /* add the labels to the given graph attributes */
1038 caller.attrs.labels = d2.labels;
1040 for (a in defaultAttrs) {
1041 if (caller.attrs[a] == undefined) {
1042 caller.attrs[a] = defaultAttrs[a];
1046 caller.attrs['labelsDiv'] = divname + ":labels";
1048 /* we need to create a new one, as otherwise we can't remove
1050 for (var i=0; i<global_graphs.length; i++) {
1051 var g = global_graphs[i];
1052 if (g.divname == divname) {
1053 global_graphs.splice(i, 1);
1059 var max_points = 900;
1063 if (auto_averaging) {
1064 if (d2.data != null && (d2.data.length/defaultAttrs.averaging) > max_points) {
1065 var averaging_times = [ 1, 2, 5, 10, 15, 20, 30, 60, 120, 240, 480 ];
1067 var num_minutes = (d2.data[d2.data.length-1][0] - d2.data[0][0]) / (60*1000);
1068 for (var i=0; i<averaging_times.length-1; i++) {
1069 if (num_minutes / averaging_times[i] <= max_points) {
1073 set_averaging(averaging_times[i]);
1074 round_annotations();
1079 if (attrs.averaging == false) {
1080 avg_data = d2.data.slice(0);
1081 for (var y=0; y<avg_data.length; y++) {
1082 avg_data[y][0] = new Date(avg_data[y][0]);
1085 avg_data = average_data(d2.data, defaultAttrs.averaging);
1088 if (attrs.maxtime != undefined) {
1089 var start = new Date() - (attrs.maxtime * 60 * 1000);
1091 for (y=avg_data.length-1; y>0; y--) {
1092 if (avg_data[y][0] < start) {
1096 avg_data = avg_data.slice(y);
1099 /* create a new dygraph */
1100 if (hashvars['nograph'] != '1') {
1101 g = new Dygraph(document.getElementById(divname), avg_data, caller.attrs);
1102 g.series_names = caller.attrs.labels;
1103 g.divname = divname;
1104 g.setAnnotations(annotations);
1105 global_graphs.push(g);
1112 /* fire off a request to load the data */
1117 function graph_callback(caller) {
1118 get_csv_data(caller.filenames, caller.columns, loaded_callback);
1121 queue_graph(graph_callback, caller);
1125 function product(v) {
1127 for (var i=1; i<v.length; i++) {
1135 for (var i=0; i<v.length; i++) {
1147 graph one column from a set of CSV files
1149 function graph_csv_files(divname, filenames, column, attrs) {
1150 return graph_csv_files_func(divname, filenames, [column], null, null, attrs);
1154 graph one column from a set of CSV files as a sum over multiple files
1156 function graph_sum_csv_files(divname, filenames, column, attrs) {
1157 return graph_csv_files_func(divname, filenames, [column], null, sum, attrs);
1161 show all the live data graphs
1163 function show_graphs() {
1164 hide_div("nodata", true);
1166 pvdate_base = pvdate.getTime();
1168 graph_csv_files_func("Recent Data (W)",
1170 [ "msg.ch1.watts", "msg.ch2.watts", "msg.ch3.watts" ],
1172 { includeZero: true,
1175 series_base: [ 'Time', 'Chan1', 'Chan2', 'Chan3' ]});
1178 return pow = v[0] + v[1] + v[2];
1181 graph_csv_files_func("Total (W)",
1183 [ "msg.ch1.watts", "msg.ch2.watts", "msg.ch3.watts" ],
1185 { includeZero: true,
1186 series_base: 'Total'});
1188 graph_csv_files("Temperature (C)",
1191 { includeZero: false });
1193 graph_csv_files_func("Channels (W)",
1195 [ "msg.ch1.watts", "msg.ch2.watts", "msg.ch3.watts" ],
1197 { includeZero: true,
1198 series_base: [ 'Time', 'Chan1', 'Chan2', 'Chan3' ]});
1205 called when the user selects a date
1207 function set_date(e) {
1208 var dp = datePickerController.getDatePicker("pvdate");
1209 pvdate = date_round(dp.date);
1210 hashvars['date'] = date_YMD(pvdate);
1211 rewrite_hashvars(hashvars);
1212 writeDebug("redrawing for: " + pvdate);
1213 annotations = new Array();
1218 setup the datepicker widget
1220 function setup_datepicker() {
1221 document.getElementById("pvdate").value =
1222 intLength(pvdate.getDate(),2) + "/" + intLength(pvdate.getMonth()+1, 2) + "/" + pvdate.getFullYear();
1223 datePickerController.addEvent(document.getElementById("pvdate"), "change", set_date);
1228 called to reload every few minutes
1230 function reload_timer() {
1231 /* flush the old CSV cache */
1232 CSV_Cache = new Array();
1233 writeDebug("reloading on timer");
1234 if (loading_counter == 0) {
1237 setup_reload_timer();
1241 setup for automatic reloads
1243 function setup_reload_timer() {
1244 setTimeout(reload_timer, 300000);
1249 toggle display of a div
1251 function toggle_div(divname)
1253 var div = document.getElementById(divname);
1254 var img = document.getElementById("img-" + divname);
1255 var current_display = div.style.display;
1256 var old_src = img.getAttribute("src");
1257 if (current_display != "none") {
1258 div.style.display = "none";
1259 img.setAttribute("src", old_src.replace("_unhide", "_hide"));
1261 div.style.display = "block";
1262 img.setAttribute("src", old_src.replace("_hide", "_unhide"));
1267 change display period
1269 function change_period(p) {
1271 if (period_days != p) {
1282 function change_averaging() {
1283 var v = +document.getElementById('averaging').value;
1284 defaultAttrs.averaging = v;
1292 function set_averaging(v) {
1293 var a = document.getElementById('averaging');
1295 defaultAttrs.averaging = v;