Use CSS for styling
authorDan Vanderkam <danvdk@gmail.com>
Wed, 28 Sep 2016 12:45:38 +0000 (08:45 -0400)
committerDan Vanderkam <danvdk@gmail.com>
Wed, 28 Sep 2016 12:45:38 +0000 (08:45 -0400)
13 files changed:
auto_tests/coverage.html
auto_tests/runner.html
auto_tests/tests/axis_labels.js
auto_tests/tests/pathological_cases.js
css/dygraph.css [new file with mode: 0644]
src/dygraph-default-attrs.js
src/dygraph-options-reference.js
src/dygraph-utils.js
src/dygraph.js
src/plugins/annotations.js
src/plugins/axes.js
src/plugins/chart-labels.js
src/plugins/legend.js

index 0457347..e8cd24c 100644 (file)
@@ -3,6 +3,7 @@
   <meta charset="utf-8">
   <title>dygraphs tests</title>
   <link rel="stylesheet" href="../node_modules/mocha/mocha.css" />
+  <link rel="stylesheet" href="../css/dygraph.css" />
 </head>
 <body>
   <div id="graph"></div>
index c3a3d87..2f48d5b 100644 (file)
@@ -3,6 +3,7 @@
   <meta charset="utf-8">
   <title>dygraphs tests</title>
   <link rel="stylesheet" href="../node_modules/mocha/mocha.css" />
+  <link rel="stylesheet" href="../css/dygraph.css" />
 </head>
 <body>
   <div id="graph"></div>
index c390392..5a631fa 100644 (file)
@@ -798,75 +798,6 @@ it('testAxisLabelFontSizeNull', function() {
   assertFontSize(document.querySelectorAll(".dygraph-axis-label-y"), "14px");
 });
 
-it('testAxisLabelColor', function() {
-  var graph = document.getElementById("graph");
-  var g = new Dygraph(graph, simpleData, {});
-
-  // Be sure we're dealing with a black default.
-  assert.equal("black", DEFAULT_ATTRS.axisLabelColor);
-
-  var assertColor = function(selector, expected) {
-    Util.assertStyleOfChildren(selector, "color", expected);
-  }
-
-  assertColor(document.querySelectorAll(".dygraph-axis-label-x"), "rgb(0, 0, 0)");
-  assertColor(document.querySelectorAll(".dygraph-axis-label-y"), "rgb(0, 0, 0)");
-
-  g.updateOptions({ axisLabelColor : "red"});
-  assertColor(document.querySelectorAll(".dygraph-axis-label-x"), "rgb(255, 0, 0)"); 
-  assertColor(document.querySelectorAll(".dygraph-axis-label-y"), "rgb(255, 0, 0)"); 
-
-  g.updateOptions({
-    axisLabelColor : null,
-    axes : { 
-      x : { axisLabelColor : "blue" },
-    }   
-  }); 
-
-  assertColor(document.querySelectorAll(".dygraph-axis-label-x"), "rgb(0, 0, 255)"); 
-  assertColor(document.querySelectorAll(".dygraph-axis-label-y"), "rgb(0, 0, 0)");
-
-  g.updateOptions({
-    axes : { 
-      y : { axisLabelColor : "green" },
-    }   
-  }); 
-
-  assertColor(document.querySelectorAll(".dygraph-axis-label-x"), "rgb(0, 0, 255)"); 
-  assertColor(document.querySelectorAll(".dygraph-axis-label-y"), "rgb(0, 128, 0)"); 
-
-  g.updateOptions({
-    series : { 
-      Y2 : { axis : "y2" } // copy y2 series to y2 axis.
-    },  
-    axes : { 
-      y2 : { axisLabelColor : "yellow" },
-    }   
-  }); 
-
-  assertColor(document.querySelectorAll(".dygraph-axis-label-x"), "rgb(0, 0, 255)"); 
-  assertColor(document.querySelectorAll(".dygraph-axis-label-y1"), "rgb(0, 128, 0)"); 
-  assertColor(document.querySelectorAll(".dygraph-axis-label-y2"), "rgb(255, 255, 0)"); 
-});
-
-it('testAxisLabelColorNull', function() {
-  var graph = document.getElementById("graph");
-  var g = new Dygraph(graph, simpleData,
-    {
-      axisLabelColor: null
-    });
-
-  var assertColor = function(selector, expected) {
-    Util.assertStyleOfChildren(selector, "color", expected);
-  }
-
-  // Be sure we're dealing with a 14-point default.
-  assert.equal(14, DEFAULT_ATTRS.axisLabelFontSize);
-
-  assertColor(document.querySelectorAll(".dygraph-axis-label-x"), "rgb(0, 0, 0)");
-  assertColor(document.querySelectorAll(".dygraph-axis-label-y"), "rgb(0, 0, 0)");
-});
-
 /*
  * This test shows that the label formatter overrides labelsKMB for all values.
  */
index 0bcab0b..0fab200 100644 (file)
@@ -90,7 +90,6 @@ it('testCombinations', function() {
       var opts = {
         width: 300,
         height: 150,
-        labelsDivWidth: 100,
         pointSize: 10
       };
       for (var key in base) {
diff --git a/css/dygraph.css b/css/dygraph.css
new file mode 100644 (file)
index 0000000..75d5868
--- /dev/null
@@ -0,0 +1,117 @@
+/**
+ * Default styles for the dygraphs charting library.
+ */
+
+.dygraph-legend {
+  position: absolute;
+  font-size: 14px;
+  z-index: 10;
+  width: 250px;  /* labelsDivWidth */
+  /*
+  dygraphs determines these based on the presence of chart labels.
+  It might make more sense to create a wrapper div around the chart proper.
+  top: 0px;
+  right: 2px;
+  */
+  background: white;
+  line-height: normal;
+  text-align: left;
+  overflow: hidden;
+}
+
+/* styles for a solid line in the legend */
+.dygraph-legend-line {
+  display: inline-block;
+  position: relative;
+  bottom: .5ex;
+  padding-left: 1em;
+  height: 1px;
+  border-bottom-width: 2px;
+  border-bottom-style: solid;
+  /* border-bottom-color is set based on the series color */
+}
+
+/* styles for a dashed line in the legend, e.g. when strokePattern is set */
+.dygraph-legend-dash {
+  display: inline-block;
+  position: relative;
+  bottom: .5ex;
+  height: 1px;
+  border-bottom-width: 2px;
+  border-bottom-style: solid;
+  /* border-bottom-color is set based on the series color */
+  /* margin-right is set based on the stroke pattern */
+  /* padding-left is set based on the stroke pattern */
+}
+
+.dygraph-roller {
+  position: absolute;
+  z-index: 10;
+}
+
+/* This class is shared by all annotations, including those with icons */
+.dygraph-annotation {
+  position: absolute;
+  z-index: 10;
+  overflow: hidden;
+}
+
+/* This class only applies to annotations without icons */
+/* Old class name: .dygraphDefaultAnnotation */
+.dygraph-default-annotation {
+  border: 1px solid black;
+  background-color: white;
+  text-align: center;
+}
+
+.dygraph-axis-label {
+  /* position: absolute; */
+  /* font-size: 14px; */
+  z-index: 10;
+  line-height: normal;
+  overflow: hidden;
+  color: black;  /* replaces old axisLabelColor option */
+}
+
+.dygraph-axis-label-x {
+}
+
+.dygraph-axis-label-y {
+}
+
+.dygraph-axis-label-y2 {
+}
+
+.dygraph-title {
+  font-weight: bold;
+  z-index: 10;
+  text-align: center;
+  /* font-size: based on titleHeight option */
+}
+
+.dygraph-xlabel {
+  text-align: center;
+  /* font-size: based on xLabelHeight option */
+}
+
+/* For y-axis label */
+.dygraph-label-rotate-left {
+  text-align: center;
+  /* See http://caniuse.com/#feat=transforms2d */
+  transform: rotate(90deg);
+  -webkit-transform: rotate(90deg);
+  -moz-transform: rotate(90deg);
+  -o-transform: rotate(90deg);
+  -ms-transform: rotate(90deg);
+}
+
+/* For y2-axis label */
+.dygraph-label-rotate-right {
+  text-align: center;
+  /* See http://caniuse.com/#feat=transforms2d */
+  transform: rotate(-90deg);
+  -webkit-transform: rotate(-90deg);
+  -moz-transform: rotate(-90deg);
+  -o-transform: rotate(-90deg);
+  -ms-transform: rotate(-90deg);
+}
index 5b7cb32..36cbd3e 100644 (file)
@@ -12,10 +12,6 @@ var DEFAULT_ATTRS = {
   highlightSeriesBackgroundAlpha: 0.5,
   highlightSeriesBackgroundColor: 'rgb(255, 255, 255)',
 
-  labelsDivWidth: 250,
-  labelsDivStyles: {
-    // TODO(danvk): move defaults from createStatusMessage_ here.
-  },
   labelsSeparateLines: false,
   labelsShowZeroValues: true,
   labelsKMB: false,
@@ -67,7 +63,6 @@ var DEFAULT_ATTRS = {
   axisLineColor: "black",
   axisLineWidth: 0.3,
   gridLineWidth: 0.3,
-  axisLabelColor: "black",
   axisLabelWidth: 50,
   gridLineColor: "rgb(128,128,128)",
 
index 4b1a23b..73615c2 100644 (file)
@@ -41,12 +41,6 @@ OPTIONS_REFERENCE =  // <JSON>
     "type": "integer",
     "description": "The size of the dot to draw on each point in pixels (see drawPoints). A dot is always drawn when a point is \"isolated\", i.e. there is a missing point on either side of it. This also controls the size of those dots."
   },
-  "labelsDivStyles": {
-    "default": "null",
-    "labels": ["Legend"],
-    "type": "{}",
-    "description": "Additional styles to apply to the currently-highlighted points div. For example, { 'fontWeight': 'bold' } will make the labels bold. In general, it is better to use CSS to style the .dygraph-legend class than to use this property."
-  },
   "drawPoints": {
     "default": "false",
     "labels": ["Data Line display"],
@@ -439,12 +433,6 @@ OPTIONS_REFERENCE =  // <JSON>
     "example": "[10, 110]",
     "description": "Explicitly set the vertical range of the graph to [low, high]. This may be set on a per-axis basis to define each y-axis separately. If either limit is unspecified, it will be calculated automatically (e.g. [null, 30] to automatically calculate just the lower bound)"
   },
-  "labelsDivWidth": {
-    "default": "250",
-    "labels": ["Legend"],
-    "type": "integer",
-    "description": "Width (in pixels) of the div which shows information on the currently-highlighted points."
-  },
   "colorSaturation": {
     "default": "1.0",
     "labels": ["Data Series Colors"],
@@ -695,12 +683,6 @@ OPTIONS_REFERENCE =  // <JSON>
     "type": "float (0.0 - 1.0)",
     "description" : "Error bars (or custom bars) for each series are drawn in the same color as the series, but with partial transparency. This sets the transparency. A value of 0.0 means that the error bars will not be drawn, whereas a value of 1.0 means that the error bars will be as dark as the line for the series itself. This can be used to produce chart lines whose thickness varies at each point."
   },
-  "axisLabelColor": {
-    "default": "black",
-    "labels": ["Axis display"],
-    "type": "string",
-    "description" : "Color for x- and y-axis labels. This is a CSS color string."
-  },
   "axisLabelWidth": {
     "default": "50 (y-axis), 60 (x-axis)",
     "labels": ["Axis display", "Chart labels"],
index 79215d4..8e972a6 100644 (file)
@@ -837,7 +837,6 @@ var pixelSafeOptions = {
   'annotationDblClickHandler': true,
   'annotationMouseOutHandler': true,
   'annotationMouseOverHandler': true,
-  'axisLabelColor': true,
   'axisLineColor': true,
   'axisLineWidth': true,
   'clickCallback': true,
@@ -855,8 +854,6 @@ var pixelSafeOptions = {
   'interactionModel': true,
   'isZoomedIgnoreProgrammaticZoom': true,
   'labelsDiv': true,
-  'labelsDivStyles': true,
-  'labelsDivWidth': true,
   'labelsKMB': true,
   'labelsKMG2': true,
   'labelsSeparateLines': true,
index 3ffc2e6..ce9f110 100644 (file)
@@ -1029,32 +1029,28 @@ Dygraph.prototype.getPropertiesForSeries = function(series_name) {
  */
 Dygraph.prototype.createRollInterface_ = function() {
   // Create a roller if one doesn't exist already.
-  if (!this.roller_) {
-    this.roller_ = document.createElement("input");
-    this.roller_.type = "text";
-    this.roller_.style.display = "none";
-    this.graphDiv.appendChild(this.roller_);
+  var roller = this.roller_;
+  if (!roller) {
+    this.roller_ = roller = document.createElement("input");
+    roller.type = "text";
+    roller.style.display = "none";
+    roller.className = 'dygraph-roller';
+    this.graphDiv.appendChild(roller);
   }
 
   var display = this.getBooleanOption('showRoller') ? 'block' : 'none';
 
-  var area = this.plotter_.area;
-  var textAttr = { "position": "absolute",
-                   "zIndex": 10,
+  var area = this.getArea();
+  var textAttr = {
                    "top": (area.y + area.h - 25) + "px",
                    "left": (area.x + 1) + "px",
                    "display": display
-                  };
-  this.roller_.size = "2";
-  this.roller_.value = this.rollPeriod_;
-  for (var name in textAttr) {
-    if (textAttr.hasOwnProperty(name)) {
-      this.roller_.style[name] = textAttr[name];
-    }
-  }
+                 };
+  roller.size = "2";
+  roller.value = this.rollPeriod_;
+  utils.update(roller.style, textAttr);
 
-  var dygraph = this;
-  this.roller_.onchange = function() { dygraph.adjustRoll(dygraph.roller_.value); };
+  roller.onchange = () => this.adjustRoll(roller.value);
 };
 
 /**
@@ -3340,7 +3336,6 @@ Dygraph.prototype.size = function() {
  */
 Dygraph.prototype.setAnnotations = function(ann, suppressDraw) {
   // Only add the annotation CSS rule once we know it will be used.
-  Dygraph.addAnnotationRule();
   this.annotations_ = ann;
   if (!this.layout_) {
     console.warn("Tried to setAnnotations before dygraph was ready. " +
@@ -3431,48 +3426,6 @@ Dygraph.prototype.ready = function(callback) {
 };
 
 /**
- * @private
- * Adds a default style for the annotation CSS classes to the document. This is
- * only executed when annotations are actually used. It is designed to only be
- * called once -- all calls after the first will return immediately.
- */
-Dygraph.addAnnotationRule = function() {
-  // TODO(danvk): move this function into plugins/annotations.js?
-  if (Dygraph.addedAnnotationCSS) return;
-
-  var rule = "border: 1px solid black; " +
-             "background-color: white; " +
-             "text-align: center;";
-
-  var styleSheetElement = document.createElement("style");
-  styleSheetElement.type = "text/css";
-  document.getElementsByTagName("head")[0].appendChild(styleSheetElement);
-
-  // Find the first style sheet that we can access.
-  // We may not add a rule to a style sheet from another domain for security
-  // reasons. This sometimes comes up when using gviz, since the Google gviz JS
-  // adds its own style sheets from google.com.
-  for (var i = 0; i < document.styleSheets.length; i++) {
-    if (document.styleSheets[i].disabled) continue;
-    var mysheet = document.styleSheets[i];
-    try {
-      if (mysheet.insertRule) {  // Firefox
-        var idx = mysheet.cssRules ? mysheet.cssRules.length : 0;
-        mysheet.insertRule(".dygraphDefaultAnnotation { " + rule + " }", idx);
-      } else if (mysheet.addRule) {  // IE
-        mysheet.addRule(".dygraphDefaultAnnotation", rule);
-      }
-      Dygraph.addedAnnotationCSS = true;
-      return;
-    } catch(err) {
-      // Was likely a security exception.
-    }
-  }
-
-  console.warn("Unable to add default annotation CSS rule; display may be off.");
-};
-
-/**
  * Add an event handler. This event handler is kept until the graph is
  * destroyed with a call to graph.destroy().
  *
index 46d0891..87153c8 100644 (file)
@@ -56,12 +56,6 @@ annotations.prototype.didDrawChart = function(e) {
   if (!points || points.length === 0) return;
 
   var containerDiv = e.canvas.parentNode;
-  var annotationStyle = {
-    "position": "absolute",
-    "fontSize": g.getOption('axisLabelFontSize') + "px",
-    "zIndex": 10,
-    "overflow": "hidden"
-  };
 
   var bindEvt = function(eventName, classEventName, pt) {
     return function(annotation_event) {
@@ -75,7 +69,7 @@ annotations.prototype.didDrawChart = function(e) {
   };
 
   // Add the annotations one-by-one.
-  var area = e.dygraph.plotter_.area;
+  var area = e.dygraph.getArea();
 
   // x-coord to sum of previous annotation's heights (used for stacking).
   var xToUsedHeight = {};
@@ -93,18 +87,18 @@ annotations.prototype.didDrawChart = function(e) {
       tick_height = a.tickHeight;
     }
 
+    // TODO: deprecate axisLabelFontSize in favor of CSS
     var div = document.createElement("div");
-    for (var name in annotationStyle) {
-      if (annotationStyle.hasOwnProperty(name)) {
-        div.style[name] = annotationStyle[name];
-      }
-    }
+    div.style['fontSize'] = g.getOption('axisLabelFontSize') + "px";
+    var className = 'dygraph-annotation';
     if (!a.hasOwnProperty('icon')) {
-      div.className = "dygraphDefaultAnnotation";
+      // camelCase class names are deprecated.
+      className += ' dygraphDefaultAnnotation dygraph-default-annotation';
     }
     if (a.hasOwnProperty('cssClass')) {
-      div.className += " " + a.cssClass;
+      className += " " + a.cssClass;
     }
+    div.className = className;
 
     var width = a.hasOwnProperty('width') ? a.width : 16;
     var height = a.hasOwnProperty('height') ? a.height : 16;
index f93f36f..2d9e31f 100644 (file)
@@ -19,6 +19,8 @@ Options left to make axis-friendly.
   ('xAxisHeight')
 */
 
+import * as utils from '../dygraph-utils';
+
 /**
  * Draws the axes. This includes the labels on the x- and y-axes, as well
  * as the tick marks on the axes.
@@ -115,19 +117,14 @@ axes.prototype.willDrawChart = function(e) {
     return {
       position: 'absolute',
       fontSize: g.getOptionForAxis('axisLabelFontSize', axis) + 'px',
-      zIndex: 10,
-      color: g.getOptionForAxis('axisLabelColor', axis),
       width: g.getOptionForAxis('axisLabelWidth', axis) + 'px',
-      // height: g.getOptionForAxis('axisLabelFontSize', 'x') + 2 + "px",
-      lineHeight: 'normal',  // Something other than "normal" line-height screws up label positioning.
-      overflow: 'hidden'
     };
   };
 
   var labelStyles = {
-    x : makeLabelStyle('x'),
-    y : makeLabelStyle('y'),
-    y2 : makeLabelStyle('y2')
+    x: makeLabelStyle('x'),
+    y: makeLabelStyle('y'),
+    y2: makeLabelStyle('y2')
   };
 
   var makeDiv = function(txt, axis, prec_axis) {
@@ -139,11 +136,8 @@ axes.prototype.willDrawChart = function(e) {
      */
     var div = document.createElement('div');
     var labelStyle = labelStyles[prec_axis == 'y2' ? 'y2' : axis];
-    for (var name in labelStyle) {
-      if (labelStyle.hasOwnProperty(name)) {
-        div.style[name] = labelStyle[name];
-      }
-    }
+    utils.update(div.style, labelStyle);
+    // TODO: combine outer & inner divs
     var inner_div = document.createElement('div');
     inner_div.className = 'dygraph-axis-label' +
                           ' dygraph-axis-label-' + axis +
@@ -202,6 +196,7 @@ axes.prototype.willDrawChart = function(e) {
         } else {
           label.style.top = top + 'px';
         }
+        // TODO: replace these with css classes?
         if (tick.axis === 0) {
           label.style.left = (area.x - getAxisOption('axisLabelWidth') - getAxisOption('axisTickSize')) + 'px';
           label.style.textAlign = 'right';
index 5fbbdc5..2d09fc8 100644 (file)
@@ -79,16 +79,8 @@ var createRotatedDiv = function(g, box, axis, classes, html) {
   inner_div.style.height = box.w + 'px';
   inner_div.style.top = (box.h / 2 - box.w / 2) + 'px';
   inner_div.style.left = (box.w / 2 - box.h / 2) + 'px';
-  inner_div.style.textAlign = 'center';
-
-  // CSS rotation is an HTML5 feature which is not standardized. Hence every
-  // browser has its own name for the CSS style.
-  var val = 'rotate(' + (axis == 1 ? '-' : '') + '90deg)';
-  inner_div.style.transform = val;        // HTML5
-  inner_div.style.WebkitTransform = val;  // Safari/Chrome
-  inner_div.style.MozTransform = val;     // Firefox
-  inner_div.style.OTransform = val;       // Opera
-  inner_div.style.msTransform = val;      // IE9
+  // TODO: combine inner_div and class_div.
+  inner_div.className = 'dygraph-label-rotate-' + (axis == 1 ? 'right' : 'left');
 
   var class_div = document.createElement("div");
   class_div.className = classes;
@@ -108,10 +100,7 @@ chart_labels.prototype.layout = function(e) {
     // QUESTION: should this return an absolutely-positioned div instead?
     var title_rect = e.reserveSpaceTop(g.getOption('titleHeight'));
     this.title_div_ = createDivInRect(title_rect);
-    this.title_div_.style.textAlign = 'center';
     this.title_div_.style.fontSize = (g.getOption('titleHeight') - 8) + 'px';
-    this.title_div_.style.fontWeight = 'bold';
-    this.title_div_.style.zIndex = 10;
 
     var class_div = document.createElement("div");
     class_div.className = 'dygraph-label dygraph-title';
@@ -123,7 +112,6 @@ chart_labels.prototype.layout = function(e) {
   if (g.getOption('xlabel')) {
     var x_rect = e.reserveSpaceBottom(g.getOption('xLabelHeight'));
     this.xlabel_div_ = createDivInRect(x_rect);
-    this.xlabel_div_.style.textAlign = 'center';
     this.xlabel_div_.style.fontSize = (g.getOption('xLabelHeight') - 2) + 'px';
 
     var class_div = document.createElement("div");
index 96e68f4..8b91841 100644 (file)
@@ -35,9 +35,6 @@ Legend.prototype.toString = function() {
   return "Legend Plugin";
 };
 
-// (defined below)
-var generateLegendDashHTML;
-
 /**
  * This is called during the dygraph constructor, after options have been set
  * but before the data is available.
@@ -52,7 +49,6 @@ var generateLegendDashHTML;
  */
 Legend.prototype.activate = function(g) {
   var div;
-  var divWidth = g.getOption('labelsDivWidth');
 
   var userLabelsDiv = g.getOption('labelsDiv');
   if (userLabelsDiv && null !== userLabelsDiv) {
@@ -62,35 +58,8 @@ Legend.prototype.activate = function(g) {
       div = userLabelsDiv;
     }
   } else {
-    // Default legend styles. These can be overridden in CSS by adding
-    // "!important" after your rule, e.g. "left: 30px !important;"
-    var messagestyle = {
-      "position": "absolute",
-      "fontSize": "14px",
-      "zIndex": 10,
-      "width": divWidth + "px",
-      "top": "0px",
-      "left": (g.size().width - divWidth - 2) + "px",
-      "background": "white",
-      "lineHeight": "normal",
-      "textAlign": "left",
-      "overflow": "hidden"};
-
-    // TODO(danvk): get rid of labelsDivStyles? CSS is better.
-    utils.update(messagestyle, g.getOption('labelsDivStyles'));
     div = document.createElement("div");
     div.className = "dygraph-legend";
-    for (var name in messagestyle) {
-      if (!messagestyle.hasOwnProperty(name)) continue;
-
-      try {
-        div.style[name] = messagestyle[name];
-      } catch (e) {
-        console.warn("You are using unsupported css properties for your " +
-            "browser in labelsDivStyles");
-      }
-    }
-
     // TODO(danvk): come up with a cleaner way to expose this.
     g.graphDiv.appendChild(div);
     this.is_generated_div_ = true;
@@ -136,7 +105,7 @@ Legend.prototype.select = function(e) {
   if (legendMode === 'follow') {
     // create floating legend div
     var area = e.dygraph.plotter_.area;
-    var labelsDivWidth = e.dygraph.getOption('labelsDivWidth');
+    var labelsDivWidth = this.legend_div.offsetWidth;
     var yAxisLabelWidth = e.dygraph.getOptionForAxis('axisLabelWidth', 'y');
     // determine floating [left, top] coordinates of the legend div
     // within the plotter_ area
@@ -195,10 +164,9 @@ Legend.prototype.predraw = function(e) {
   // TODO(danvk): only use real APIs for this.
   e.dygraph.graphDiv.appendChild(this.legend_div_);
   var area = e.dygraph.getArea();
-  var labelsDivWidth = e.dygraph.getOption("labelsDivWidth");
+  var labelsDivWidth = this.legend_div_.offsetWidth;
   this.legend_div_.style.left = area.x + area.w - labelsDivWidth - 1 + "px";
   this.legend_div_.style.top = area.y + "px";
-  this.legend_div_.style.width = labelsDivWidth + "px";
 };
 
 /**
@@ -341,12 +309,10 @@ Legend.defaultFormatter = function(data) {
  * @private
  */
 // TODO(danvk): cache the results of this
-generateLegendDashHTML = function(strokePattern, color, oneEmWidth) {
+function generateLegendDashHTML(strokePattern, color, oneEmWidth) {
   // Easy, common case: a solid line
   if (!strokePattern || strokePattern.length <= 1) {
-    return "<div style=\"display: inline-block; position: relative; " +
-    "bottom: .5ex; padding-left: 1em; height: 1px; " +
-    "border-bottom: 2px solid " + color + ";\"></div>";
+    return `<div class="dygraph-legend-line" style="border-bottom-color: ${color};"></div>`;
   }
 
   var i, j, paddingLeft, marginRight;
@@ -393,10 +359,7 @@ generateLegendDashHTML = function(strokePattern, color, oneEmWidth) {
         // The repeated first segment has no right margin.
         marginRight = 0;
       }
-      dash += "<div style=\"display: inline-block; position: relative; " +
-        "bottom: .5ex; margin-right: " + marginRight + "em; padding-left: " +
-        paddingLeft + "em; height: 1px; border-bottom: 2px solid " + color +
-        ";\"></div>";
+      dash += `<div class="dygraph-legend-dash" style="margin-right: ${marginRight}em; padding-left: ${paddingLeft}em;"></div>`;
     }
   }
   return dash;