Initial implementation
authorDan Vanderkam <dan@dygraphs.com>
Sat, 26 Mar 2011 21:18:39 +0000 (17:18 -0400)
committerDan Vanderkam <dan@dygraphs.com>
Sat, 26 Mar 2011 21:18:39 +0000 (17:18 -0400)
dygraph-canvas.js
dygraph.js
tests/century-scale.html
tests/demo.html
tests/noise.html
tests/reverse-y-axis.html

index 2f8821e..6ac556a 100644 (file)
@@ -7,6 +7,24 @@
  * - grid overlays
  * - error bars
  * - dygraphs attribute system
+ *
+ * High level overview of classes:
+ *
+ * - DygraphLayout
+ *     This contains all the data to be charted.
+ *     It uses data coordinates, but also records the chart range (in data
+ *     coordinates) and hence is able to calculate percentage positions ('In
+ *     this view, Point A lies 25% down the x-axis.')
+ *     Two things that it does not do are:
+ *     1. Record pixel coordinates for anything.
+ *     2. (oddly) determine anything about the layout of chart elements.
+ *     The naming is a vestige of Dygraph's original PlotKit roots.
+ *
+ * - DygraphCanvasRenderer
+ *     This class determines the charting area (in pixel coordinates), maps the
+ *     percentage coordinates in the DygraphLayout to pixels and draws them.
+ *     It's also responsible for creating chart DOM elements, i.e. annotations,
+ *     tick mark labels, the title and the x/y-axis labels.
  */
 
 /**
@@ -301,6 +319,7 @@ DygraphCanvasRenderer = function(dygraph, element, layout, options) {
   this.xlabels = new Array();
   this.ylabels = new Array();
   this.annotations = new Array();
+  this.chartLabels = {};
 
   // TODO(danvk): consider all axes in this computation.
   this.area = {
@@ -321,6 +340,22 @@ DygraphCanvasRenderer = function(dygraph, element, layout, options) {
                         "to use " + this.dygraph_.numAxes() + ")");
   }
 
+  // Add space for chart labels: title, xlabel and ylabel.
+  if (this.attr_('title')) {
+    // TODO(danvk): make this a parameter
+    this.area.h -= this.attr_('titleHeight');
+    this.area.y += this.attr_('titleHeight');
+  }
+  if (this.attr_('xlabel')) {
+    // TODO(danvk): make this a parameter
+    this.area.h -= this.attr_('xLabelHeight');
+  }
+  if (this.attr_('ylabel')) {
+    var yLabelWidth = 16;
+    this.area.x += this.attr_('yLabelWidth');
+    this.area.w -= this.attr_('yLabelWidth');
+  }
+
   this.container.style.position = "relative";
   this.container.style.width = this.width + "px";
 
@@ -374,9 +409,15 @@ DygraphCanvasRenderer.prototype.clear = function() {
     var el = this.annotations[i];
     if (el.parentNode) el.parentNode.removeChild(el);
   }
+  for (var k in this.chartLabels) {
+    if (!this.chartLabels.hasOwnProperty(k)) continue;
+    var el = this.chartLabels[k];
+    if (el.parentNode) el.parentNode.removeChild(el);
+  }
   this.xlabels = new Array();
   this.ylabels = new Array();
   this.annotations = new Array();
+  this.chartLabels = {};
 };
 
 
@@ -452,6 +493,7 @@ DygraphCanvasRenderer.prototype.render = function() {
   // Do the ordinary rendering, as before
   this._renderLineChart();
   this._renderAxis();
+  this._renderChartLabels(); 
   this._renderAnnotations();
 };
 
@@ -518,7 +560,7 @@ DygraphCanvasRenderer.prototype._renderAxis = function() {
           label.style.top = top + "px";
         }
         if (tick[0] == 0) {
-          label.style.left = "0px";
+          label.style.left = (this.area.x - this.options.yAxisLabelWidth - this.options.axisTickSize) + "px";
           label.style.textAlign = "right";
         } else if (tick[0] == 1) {
           label.style.left = (this.area.x + this.area.w +
@@ -575,7 +617,7 @@ DygraphCanvasRenderer.prototype._renderAxis = function() {
 
         var label = makeDiv(tick[1]);
         label.style.textAlign = "center";
-        label.style.bottom = "0px";
+        label.style.top = (y + this.options.axisTickSize) + 'px';
 
         var left = (x - this.options.axisLabelWidth/2);
         if (left + this.options.axisLabelWidth > this.width) {
@@ -605,6 +647,80 @@ DygraphCanvasRenderer.prototype._renderAxis = function() {
 };
 
 
+DygraphCanvasRenderer.prototype._renderChartLabels = function() {
+  // Generate divs for the chart title, xlabel and ylabel.
+  // Space for these divs has already been taken away from the charting area in
+  // the DygraphCanvasRenderer constructor.
+  if (this.attr_('title')) {
+    var div = document.createElement("div");
+    div.style.position = 'absolute';
+    div.style.top = '0px';
+    div.style.left = this.area.x + 'px';
+    div.style.width = this.area.w + 'px';
+    div.style.height = this.attr_('titleHeight') + 'px';
+    div.style.textAlign = 'center';
+    div.style.fontSize = this.attr_('titleHeight') + 'px';
+    div.style.fontWeight = 'bold';
+    // div.style.border = '1px solid black';
+    div.innerHTML = this.attr_('title');
+    this.container.appendChild(div);
+    this.chartLabels.title = div;
+  }
+
+  if (this.attr_('xlabel')) {
+    var div = document.createElement("div");
+    div.style.position = 'absolute';
+    div.style.bottom = 0;  // TODO(danvk): this is lazy. Calculate style.top.
+    div.style.left = this.area.x + 'px';
+    div.style.width = this.area.w + 'px';
+    div.style.height = this.attr_('xLabelHeight') + 'px';
+    div.style.textAlign = 'center';
+    div.style.fontSize = this.attr_('xLabelHeight') + 'px';
+    // div.style.border = '1px solid black';
+    div.innerHTML = this.attr_('xlabel');
+    this.container.appendChild(div);
+    this.chartLabels.xlabel = div;
+  }
+
+  if (this.attr_('ylabel')) {
+    var box = {
+      left: 0,
+      top: this.area.y,
+      width: this.attr_('yLabelWidth'),
+      height: this.area.h
+    };
+    var div = document.createElement("div");
+    div.style.position = 'absolute';
+    div.style.left = box.left;
+    div.style.top = box.top + 'px';
+    div.style.width = box.width + 'px';
+    div.style.height = box.height + 'px';
+    div.style.fontSize = this.attr_('xLabelHeight') + 'px';
+    // div.style.border = '1px solid black';
+
+    var inner_div = document.createElement("div");
+    inner_div.style.position = 'absolute';
+    // inner_div.style.border = '1px solid red';
+    inner_div.style.width = box.height + 'px';
+    inner_div.style.height = box.width + 'px';
+    inner_div.style.top = (box.height / 2 - box.width / 2) + 'px';
+    inner_div.style.left = (box.width / 2 - box.height / 2) + 'px';
+    inner_div.style.textAlign = 'center';
+    inner_div.style.transform = 'rotate(-90deg)';        // HTML5
+    inner_div.style.WebkitTransform = 'rotate(-90deg)';  // Safari/Chrome
+    inner_div.style.MozTransform = 'rotate(-90deg)';     // Firefox
+    inner_div.style.OTransform = 'rotate(-90deg)';       // Opera
+    inner_div.style.filter =
+     'progid:DXImageTransform.Microsoft.BasicImage(rotation=3)';
+    inner_div.innerHTML = this.attr_('ylabel');
+
+    div.appendChild(inner_div);
+    this.container.appendChild(div);
+    this.chartLabels.ylabel = div;
+  }
+};
+
+
 DygraphCanvasRenderer.prototype._renderAnnotations = function() {
   var annotationStyle = {
     "position": "absolute",
index 26aff3a..03ee85d 100644 (file)
@@ -193,6 +193,11 @@ Dygraph.DEFAULT_ATTRS = {
   stepPlot: false,
   avoidMinZero: false,
 
+  // Sizes of the various chart labels.
+  titleHeight: 16,
+  xLabelHeight: 16,
+  yLabelWidth: 16,
+
   interactionModel: null  // will be set to Dygraph.defaultInteractionModel.
 };
 
@@ -914,8 +919,9 @@ Dygraph.prototype.createStatusMessage_ = function() {
 };
 
 /**
- * Position the labels div so that its right edge is flush with the right edge
- * of the charting area.
+ * Position the labels div so that:
+ * - its right edge is flush with the right edge of the charting area
+ * - its top edge is flush with the top edge of the charting area
  */
 Dygraph.prototype.positionLabelsDiv_ = function() {
   // Don't touch a user-specified labelsDiv.
@@ -924,6 +930,7 @@ Dygraph.prototype.positionLabelsDiv_ = function() {
   var area = this.plotter_.area;
   var div = this.attr_("labelsDiv");
   div.style.left = area.x + area.w - this.attr_("labelsDivWidth") - 1 + "px";
+  div.style.top = area.y + "px";
 };
 
 /**
index ae4eaef..271a652 100644 (file)
@@ -36,7 +36,6 @@
                          "rgb(255,100,100)",
                          "#00DD55",
                          "rgba(50,50,200,0.4)"],
-                padding: {left: 40, right: 30, top: 15, bottom: 15},
                 width: 480,
                 height: 320
               }
index 2a2ea20..5859a23 100644 (file)
@@ -36,9 +36,7 @@
                 }
                 return r;
               },
-              null,
               {
-                rollPeriod: 1,
                 labelsDiv: document.getElementById('status'),
                 labelsSeparateLines: true,
                 labelsKMB: true,
                          "rgb(255,100,100)",
                          "#00DD55",
                          "rgba(50,50,200,0.4)"],
-                padding: {left: 40, right: 30, top: 15, bottom: 15},
-                width: 480,
-                height: 320
+                width: 640,
+                height: 480,
+                title: 'Interesting Shapes',
+                xlabel: 'Date',
+                ylabel: 'Count'
               }
           );
     </script>
index 902de67..6c5b566 100644 (file)
@@ -13,9 +13,8 @@
     <script type="text/javascript" src="data.js"></script>
   </head>
   <body>
-    <p>7-day rollup:</p>
     <div id="div_g" style="width:600px; height:300px;"></div>
-    <p>14-Day Rollup:</p>
+    <br/>
     <div id="div_g30" style="width:600px; height:300px;"></div>
 
     <script type="text/javascript">
             NoisyData, {
               rollPeriod: 7,
               errorBars: true,
-              legend: 'always'
+              legend: 'always',
+              ylabel: 'Percent Error',
+              yAxisLabelWidth: 25,
+              title: 'Error Rates over Time (7-day rollup)'
             }
           );
       g30 = new Dygraph(
               errorBars: true,
               legend: 'always',
               labelsDivStyles: {
-                'text-align': 'right',
+                'textAlign': 'right',
               },
-              labelsDivWidth: 170
+              labelsDivWidth: 170,
+              ylabel: 'Percent Error',
+              yAxisLabelWidth: 25,
+              title: 'Error Rates over Time (14-day rollup)'
             }
           );
     </script>
index 2328a41..5d0869e 100644 (file)
@@ -37,7 +37,6 @@
                 }
                 return r;
               },
-              null,
               {
                 rollPeriod: 1,
                 labelsDiv: document.getElementById('status'),
@@ -47,7 +46,6 @@
                          "rgb(255,100,100)",
                          "#00DD55",
                          "rgba(50,50,200,0.4)"],
-                padding: {left: 40, right: 30, top: 15, bottom: 15},
                 width: 480,
                 height: 320,
                 valueRange: [3000, 0]  // Reverse y-axis