added annotations
authorAndrew Tridgell <tridge@samba.org>
Tue, 12 Oct 2010 09:41:29 +0000 (20:41 +1100)
committerAndrew Tridgell <tridge@samba.org>
Tue, 12 Oct 2010 09:41:29 +0000 (20:41 +1100)
live/annotation.cgi [new file with mode: 0755]
live/graphs.js
live/index.html

diff --git a/live/annotation.cgi b/live/annotation.cgi
new file mode 100755 (executable)
index 0000000..4fb6fff
--- /dev/null
@@ -0,0 +1,35 @@
+#!/usr/bin/env python
+
+import cgi, sys, os, urllib
+
+print('''Content-type: text/html
+
+<html>
+<head><title>annotations server - for automated use only</title></head>
+<body>go away</body>
+''')
+
+allowed_addresses = [ "192.168.2.15", "127.0.0.1" ]
+
+addr = cgi.escape(os.environ["REMOTE_ADDR"])
+
+if not addr in allowed_addresses:
+    print("no access for :", addr)
+    sys.exit(0)
+
+
+form = cgi.FieldStorage()
+series = form.getvalue("series")
+xval = form.getvalue("xval")
+text = form.getvalue("text")
+
+if (series is None or xval is None or text is None):
+    print("Bad form data")
+    sys.exit(1)
+
+series = series.replace(',','%2C')
+text   = text.replace(',','%2C')
+
+f = open("/var/www/solar.tridgell.net/html/CSV/annotations.csv", mode='a')
+f.write('%u,"%s","%s"\n' % (long(xval), cgi.escape(series), cgi.escape(text)))
+f.close()
index 156e0cc20481948af2230f4d389084e19d0226b4..9175fae8e6a19177c909d5f41d3012366f6fcf06 100644 (file)
@@ -16,6 +16,9 @@ midnight.setMilliseconds(0);
 
 pvdate = new Date(midnight);
 
+/* marker for whether we are in a redraw with new data */
+in_redraw = false;
+
 /*
   show a HTML heading
  */
@@ -45,10 +48,8 @@ function graph_div(divname) {
 function hide_div(divname, hidden) {
   var div = document.getElementById(divname);
   if (hidden) {
-    writeDebug("hiding " + divname);
     div.style.display = "none";
   } else {
-    writeDebug("unhiding " + divname);
     div.style.display = "block";
   }
 }
@@ -125,9 +126,29 @@ function parse_date(s) {
   if (s.search("-") != -1) {
     s = s.replace("-", "/", "g");
   }
-  return new Date(s);
+  if (s.search("/") != -1) {
+    return new Date(s);
+  }
+  /* assume time in milliseconds since 1970 */
+  return (+s);
 };
 
+/*
+  parse a CSV value
+ */
+function parse_value(s) {
+  if (s.substring(0,1) == '"') {
+    s = unescape(s.substring(1,s.length-1));
+    writeDebug("s="+s);
+    return s;
+  }
+  var n = new Number(s);
+  if (isNaN(n)) {
+    return s;
+  }
+  return n;
+}
+
 /* keep a cache of loaded CSV files */
 CSV_Cache = new Array();
 
@@ -143,7 +164,6 @@ function load_CSV(filename, callback) {
     if (CSV_Cache[filename].pending) {
       /* its pending load by someone else. Add ourselves to the notify
         queue so we are told when it is done */
-      writeDebug("waiting on: " + filename);
       CSV_Cache[filename].queue.push({filename:filename, callback:callback});
       return;
     }
@@ -191,7 +211,7 @@ function load_CSV(filename, callback) {
       data[i-1] = new Array();
       data[i-1][0] = parse_date(row[0]);
       for (var j=1; j<row.length; j++) {
-       data[i-1][j] = parseFloat(row[j]);
+       data[i-1][j] = parse_value(row[j]);
       }
     }
     
@@ -230,10 +250,8 @@ function load_CSV(filename, callback) {
       if (caller.r.status == 200) {
        load_CSV_callback(caller);
        hide_div("nodata", true);
-       writeDebug("got file: " + caller.filename);
       } else {
        /* the load failed */
-       writeDebug("missed file: " + caller.filename);
        hide_div("nodata", false);
        queue_call(caller.callback, { filename: filename, data: null, labels: null });
        while (CSV_Cache[caller.filename].queue.length > 0) {
@@ -272,7 +290,6 @@ function intLength(v, N) {
  */
 function days_csv_files() {
   var list = new Array();
-  writeDebug(pvdate);
   for (var i=0; i<serialnums.length; i++) {
     list[i] = CSV_directory + 
       pvdate.getFullYear() + "-" + 
@@ -400,14 +417,155 @@ function apply_function(d, func, label) {
 /* currently displayed graphs, indexed by divname */
 global_graphs = new Array();
 
+/*
+  find a graph by divname
+ */
+function graph_find(divname) {
+  for (var i=0; i<global_graphs.length; i++) {
+    var g = global_graphs[i];
+    if (g.divname == divname) {
+      return g;
+    }
+  }
+  return null;
+}
+
+function nameAnnotation(ann) {
+  return "(" + ann.series + ", " + ann.xval + ")";
+}
+
+annotations = [];
+
+/*
+  try to save an annotation via annotation.cgi
+ */
+function save_annotation(ann) {
+  var r = new XMLHttpRequest();
+  r.open("GET", 
+        "annotation.cgi?series="+escape(ann.series)+"&xval="+ann.xval+"&text="+escape(ann.text), true);
+  r.send(null);  
+}
+
+/*
+  load annotations from annotations.csv
+ */
+function load_annotations(g) {
+  function callback(d) {
+    var anns_by_name = new Array();
+    annotations = []
+    for (var i=0; i<d.data.length; i++) {
+      var ann = {
+      xval: d.data[i][0],
+      series: d.data[i][1],
+      shortText: '!',
+      text: decodeURIComponent(d.data[i][2])
+      };
+      var a = anns_by_name[nameAnnotation(ann)];
+      if (a == undefined) {
+       anns_by_name[nameAnnotation(ann)] = i;
+       annotations.push(ann);
+      } else {
+       annotations[a] = ann;
+      }
+    }
+    for (var i=0; i<global_graphs.length; i++) {
+      var g = global_graphs[i];
+      g.setAnnotations(annotations);
+    }
+  }
+
+  load_CSV("../CSV/annotations.csv", callback);
+}
+
+function annotation_highlight(ann, point, dg, event) {
+  saveBg = ann.div.style.backgroundColor;
+  ann.div.style.backgroundColor = '#ddd';
+}
+
+function annotation_unhighlight(ann, point, dg, event) {
+  ann.div.style.backgroundColor = saveBg;
+}
+
+/*
+  handle annotation updates
+ */
+function annotation_click(ann, point, dg, event) {
+  ann.text = prompt("Enter annotation", ann.text);
+  for (var i=0; i<annotations.length; i++) {
+    if (annotations[i].xval == ann.xval && annotations[i].series == ann.series) {
+      annotations[i].text = ann.text;
+      if (ann.text == '') {
+       annotations.splice(i,1);
+       i--;
+      }
+    }
+  }
+  for (var i=0; i<global_graphs.length; i++) {
+    var g = global_graphs[i];
+    if (g.series_names.indexOf(ann.series) != -1) {
+      g.setAnnotations(annotations);
+    }
+  }
+  save_annotation(ann);
+}
+
+/*
+  add a new annotation to one graph
+ */
+function annotation_add_graph(g, p, ann) {
+  var anns = g.annotations();
+  if (p.annotation) {
+    /* its an update */
+    if (ann.text == '') {
+      var idx = anns.indexOf(p);
+      if (idx != -1) {
+       anns.splice(idx,1);
+      }
+    } else {
+      p.annotation.text = ann.text;
+    }
+  } else {
+    writeDebugObject(ann);
+    writeDebug(ann.xval);
+    anns.push(ann);
+  }
+  g.setAnnotations(anns);
+}
+
+/*
+  add a new annotation
+ */
+function annotation_add(event, p) {
+  var ann = {
+  series: p.name,
+  xval: p.xval,
+  shortText: '!',
+  text: prompt("Enter annotation", ""),
+  };
+  for (var i=0; i<global_graphs.length; i++) {
+    var g = global_graphs[i];
+    if (g.series_names.indexOf(p.name) != -1) {
+      annotation_add_graph(g, p, ann);
+    }
+  }
+
+  save_annotation(ann);
+}
+
+
 /* default dygraph attributes */
 defaultAttrs = {
  width: 700,
  height: 350,
  rollPeriod: 1,
  strokeWidth: 1,
- showRoller: true
-}
+ showRoller: true,
+ annotationMouseOverHandler: annotation_highlight,
+ annotationMouseOutHandler: annotation_unhighlight,
+ annotationClickHandler: annotation_click,
+ pointClickCallback: annotation_add
+};
+
 
 /*
   graph results from a set of CSV files:
@@ -502,9 +660,10 @@ function graph_csv_files_func(divname, filenames, columns, func1, func2, attrs)
 
     caller.attrs['labelsDiv'] = divname + ":labels";
 
-    if (global_graphs[divname] !== undefined) {
+    var g = graph_find(divname);
+
+    if (g != null) {
       /* we already have the graph, just update the data */
-      var g = global_graphs[divname];
       for (var i=0; i<d2.data.length; i++) {
        /* the rawData_ attribute doesn't take dates */
        d2.data[i][0] = d2.data[i][0].valueOf();
@@ -513,7 +672,11 @@ function graph_csv_files_func(divname, filenames, columns, func1, func2, attrs)
       g.drawGraph_(g.rawData_);
     } else {
       /* create a new dygraph */
-      global_graphs[divname] = new Dygraph(document.getElementById(divname), d2.data, caller.attrs);
+      g = new Dygraph(document.getElementById(divname), d2.data, caller.attrs);
+      g.series_names = caller.attrs.labels;
+      g.divname = divname;
+      g.setAnnotations(annotations);
+      global_graphs.push(g);
     }
 
     loading(false);
@@ -559,9 +722,6 @@ function graph_sum_csv_files(divname, filenames, column, attrs) {
   return graph_csv_files_func(divname, filenames, [column], null, sum, attrs);
 }
 
-/* marker for whether we are in a redraw with new data */
-in_redraw = false;
-
 /*
   show all the live data graphs
  */
@@ -650,6 +810,8 @@ function show_graphs() {
                    avoidMinZero: true,
                    valueRange: [0, 12] });
 
+  load_annotations();
+
   in_redraw = true;
 }
 
index 3a2883042ef5a7eeee4c3c583f35976af832725a..83357d6b2379f4e298b9b2a63c670194ac92aa62 100755 (executable)
@@ -10,7 +10,6 @@
 <script type="text/javascript">is_IE = true;</script>
 <script type="text/javascript" src="lib/excanvas.js"></script>
 <![endif]-->
-<script type="text/javascript" src="lib/strftime.js"></script>
 <script type="text/javascript" src="lib/rgbcolor.js"></script>
 <script type="text/javascript" src="lib/dygraph-canvas.js"></script>
 <script type="text/javascript" src="lib/dygraph.js"></script>