+ * Transforms layout_.points index into data row number.
+ * @param int layout_.points index
+ * @return int row number, or -1 if none could be found.
+ * @private
+ */
+Dygraph.prototype.idxToRow_ = function(idx) {
+ if (idx < 0) return -1;
+
+ // make sure that you get the boundaryIds record which is also defined (see bug #236)
+ var boundaryIdx = -1;
+ for (var i = 0; i < this.boundaryIds_.length; i++) {
+ if (this.boundaryIds_[i] !== undefined) {
+ boundaryIdx = i;
+ break;
+ }
+ }
+ if (boundaryIdx < 0) return -1;
+ for (var setIdx = 0; setIdx < this.layout_.datasets.length; ++setIdx) {
+ var set = this.layout_.datasets[setIdx];
+ if (idx < set.length) {
+ return this.boundaryIds_[boundaryIdx][0] + idx;
+ }
+ idx -= set.length;
+ }
+ return -1;
+};
+
+/**
+ * @private
+ * Generates legend html dash for any stroke pattern. It will try to scale the
+ * pattern to fit in 1em width. Or if small enough repeat the partern for 1em
+ * width.
+ * @param strokePattern The pattern
+ * @param color The color of the series.
+ * @param oneEmWidth The width in pixels of 1em in the legend.
+ */
+Dygraph.prototype.generateLegendDashHTML_ = function(strokePattern, color, oneEmWidth) {
+ var dash = "";
+ var i, j, paddingLeft, marginRight;
+ var strokePixelLength = 0, segmentLoop = 0;
+ var normalizedPattern = [];
+ var loop;
+ // IE 7,8 fail at these divs, so they get boring legend, have not tested 9.
+ var isIE = (/MSIE/.test(navigator.userAgent) && !window.opera);
+ if(isIE) {
+ return "—";
+ }
+ if (!strokePattern || strokePattern.length <= 1) {
+ // Solid line
+ dash = "<div style=\"display: inline-block; position: relative; " +
+ "bottom: .5ex; padding-left: 1em; height: 1px; " +
+ "border-bottom: 2px solid " + color + ";\"></div>";
+ } else {
+ // Compute the length of the pixels including the first segment twice,
+ // since we repeat it.
+ for (i = 0; i <= strokePattern.length; i++) {
+ strokePixelLength += strokePattern[i%strokePattern.length];
+ }
+
+ // See if we can loop the pattern by itself at least twice.
+ loop = Math.floor(oneEmWidth/(strokePixelLength-strokePattern[0]));
+ if (loop > 1) {
+ // This pattern fits at least two times, no scaling just convert to em;
+ for (i = 0; i < strokePattern.length; i++) {
+ normalizedPattern[i] = strokePattern[i]/oneEmWidth;
+ }
+ // Since we are repeating the pattern, we don't worry about repeating the
+ // first segment in one draw.
+ segmentLoop = normalizedPattern.length;
+ } else {
+ // If the pattern doesn't fit in the legend we scale it to fit.
+ loop = 1;
+ for (i = 0; i < strokePattern.length; i++) {
+ normalizedPattern[i] = strokePattern[i]/strokePixelLength;
+ }
+ // For the scaled patterns we do redraw the first segment.
+ segmentLoop = normalizedPattern.length+1;
+ }
+ // Now make the pattern.
+ for (j = 0; j < loop; j++) {
+ for (i = 0; i < segmentLoop; i+=2) {
+ // The padding is the drawn segment.
+ paddingLeft = normalizedPattern[i%normalizedPattern.length];
+ if (i < strokePattern.length) {
+ // The margin is the space segment.
+ marginRight = normalizedPattern[(i+1)%normalizedPattern.length];
+ } else {
+ // 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>";
+ }
+ }
+ }
+ return dash;
+};
+
+/**
+ * @private
+ * Generates HTML for the legend which is displayed when hovering over the
+ * chart. If no selected points are specified, a default legend is returned
+ * (this may just be the empty string).
+ * @param { Number } [x] The x-value of the selected points.
+ * @param { [Object] } [sel_points] List of selected points for the given
+ * x-value. Should have properties like 'name', 'yval' and 'canvasy'.
+ * @param { Number } [oneEmWidth] The pixel width for 1em in the legend.
+ */
+Dygraph.prototype.generateLegendHTML_ = function(x, sel_points, oneEmWidth) {
+ // If no points are selected, we display a default legend. Traditionally,
+ // this has been blank. But a better default would be a conventional legend,
+ // which provides essential information for a non-interactive chart.
+ var html, sepLines, i, c, dash, strokePattern;
+ if (typeof(x) === 'undefined') {
+ if (this.attr_('legend') != 'always') return '';
+
+ sepLines = this.attr_('labelsSeparateLines');
+ var labels = this.attr_('labels');
+ html = '';
+ for (i = 1; i < labels.length; i++) {
+ if (!this.visibility()[i - 1]) continue;
+ c = this.plotter_.colors[labels[i]];
+ if (html !== '') html += (sepLines ? '<br/>' : ' ');
+ strokePattern = this.attr_("strokePattern", labels[i]);
+ dash = this.generateLegendDashHTML_(strokePattern, c, oneEmWidth);
+ html += "<span style='font-weight: bold; color: " + c + ";'>" + dash +
+ " " + labels[i] + "</span>";
+ }
+ return html;
+ }
+
+ var xOptView = this.optionsViewForAxis_('x');
+ var xvf = xOptView('valueFormatter');
+ html = xvf(x, xOptView, this.attr_('labels')[0], this) + ":";
+
+ var yOptViews = [];
+ var num_axes = this.numAxes();
+ for (i = 0; i < num_axes; i++) {
+ yOptViews[i] = this.optionsViewForAxis_('y' + (i ? 1 + i : ''));
+ }
+ var showZeros = this.attr_("labelsShowZeroValues");
+ sepLines = this.attr_("labelsSeparateLines");
+ for (i = 0; i < this.selPoints_.length; i++) {
+ var pt = this.selPoints_[i];
+ if (pt.yval === 0 && !showZeros) continue;
+ if (!Dygraph.isOK(pt.canvasy)) continue;
+ if (sepLines) html += "<br/>";
+
+ var yOptView = yOptViews[this.seriesToAxisMap_[pt.name]];
+ var fmtFunc = yOptView('valueFormatter');
+ c = this.plotter_.colors[pt.name];
+ var yval = fmtFunc(pt.yval, yOptView, pt.name, this);
+
+ // TODO(danvk): use a template string here and make it an attribute.
+ html += " <b><span style='color: " + c + ";'>" + pt.name +
+ "</span></b>:" + yval;
+ }
+ return html;
+};
+
+/**
+ * @private
+ * Displays information about the selected points in the legend. If there is no
+ * selection, the legend will be cleared.
+ * @param { Number } [x] The x-value of the selected points.
+ * @param { [Object] } [sel_points] List of selected points for the given
+ * x-value. Should have properties like 'name', 'yval' and 'canvasy'.
+ */
+Dygraph.prototype.setLegendHTML_ = function(x, sel_points) {
+ var labelsDiv = this.attr_("labelsDiv");
+ var sizeSpan = document.createElement('span');
+ // Calculates the width of 1em in pixels for the legend.
+ sizeSpan.setAttribute('style', 'margin: 0; padding: 0 0 0 1em; border: 0;');
+ labelsDiv.appendChild(sizeSpan);
+ var oneEmWidth=sizeSpan.offsetWidth;
+
+ var html = this.generateLegendHTML_(x, sel_points, oneEmWidth);
+ if (labelsDiv !== null) {
+ labelsDiv.innerHTML = html;
+ } else {
+ if (typeof(this.shown_legend_error_) == 'undefined') {
+ this.error('labelsDiv is set to something nonexistent; legend will not be shown.');
+ this.shown_legend_error_ = true;
+ }
+ }
+};
+
+/**