X-Git-Url: https://adrianiainlam.tk/git/?a=blobdiff_plain;f=dygraph.js;h=0748d2642b90e484b5340d1dae6c8a75b2925141;hb=014a810304b884645ca4841440c44127b1bbc9c7;hp=ff9fbfaa91694db51a2e446d629ee23078e72500;hpb=94ea5744c9541f9c91ee2d150d06024e430c967e;p=dygraphs.git
diff --git a/dygraph.js b/dygraph.js
index ff9fbfa..0748d26 100644
--- a/dygraph.js
+++ b/dygraph.js
@@ -397,6 +397,7 @@ Dygraph.prototype.__init__ = function(div, file, attrs) {
Dygraph.updateDeep(this.attrs_, Dygraph.DEFAULT_ATTRS);
this.boundaryIds_ = [];
+ this.setIndexByName_ = {};
// Create the containing DIV and other interactive elements
this.createInterface_();
@@ -451,14 +452,14 @@ Dygraph.prototype.attr_ = function(name, seriesName) {
Dygraph.OPTIONS_REFERENCE[name] = true;
}
//
- if (seriesName &&
+ if (this.user_attrs_ !== null && seriesName &&
typeof(this.user_attrs_[seriesName]) != 'undefined' &&
this.user_attrs_[seriesName] !== null &&
typeof(this.user_attrs_[seriesName][name]) != 'undefined') {
return this.user_attrs_[seriesName][name];
- } else if (typeof(this.user_attrs_[name]) != 'undefined') {
+ } else if (this.user_attrs_ !== null && typeof(this.user_attrs_[name]) != 'undefined') {
return this.user_attrs_[name];
- } else if (typeof(this.attrs_[name]) != 'undefined') {
+ } else if (this.attrs_ !== null && typeof(this.attrs_[name]) != 'undefined') {
return this.attrs_[name];
} else {
return null;
@@ -830,21 +831,27 @@ Dygraph.prototype.createInterface_ = function() {
}
var dygraph = this;
- Dygraph.addEvent(this.mouseEventElement_, 'mousemove', function(e) {
- dygraph.mouseMove_(e);
- });
- Dygraph.addEvent(this.mouseEventElement_, 'mouseout', function(e) {
- dygraph.mouseOut_(e);
- });
+
+ this.mouseMoveHandler = function(e) {
+ dygraph.mouseMove_(e);
+ };
+ Dygraph.addEvent(this.mouseEventElement_, 'mousemove', this.mouseMoveHandler);
+
+ this.mouseOutHandler = function(e) {
+ dygraph.mouseOut_(e);
+ };
+ Dygraph.addEvent(this.mouseEventElement_, 'mouseout', this.mouseOutHandler);
this.createStatusMessage_();
this.createDragInterface_();
+ this.resizeHandler = function(e) {
+ dygraph.resize();
+ };
+
// Update when the window is resized.
// TODO(danvk): drop frames depending on complexity of the chart.
- Dygraph.addEvent(window, 'resize', function(e) {
- dygraph.resize();
- });
+ Dygraph.addEvent(window, 'resize', this.resizeHandler);
};
/**
@@ -859,6 +866,10 @@ Dygraph.prototype.destroy = function() {
node.removeChild(node.firstChild);
}
};
+
+ // remove mouse event handlers
+ Dygraph.removeEvent(this.mouseEventElement_, 'mouseout', this.mouseOutHandler);
+ Dygraph.removeEvent(this.mouseEventElement_, 'mousemove', this.mouseMoveHandler);
removeRecursive(this.maindiv_);
var nullOut = function(obj) {
@@ -868,7 +879,9 @@ Dygraph.prototype.destroy = function() {
}
}
};
-
+ // remove event handlers
+ Dygraph.removeEvent(window,'resize',this.resizeHandler);
+ this.resizeHandler = null;
// These may not all be necessary, but it can't hurt...
nullOut(this.layout_);
nullOut(this.plotter_);
@@ -1327,7 +1340,7 @@ Dygraph.prototype.doUnzoom_ = function() {
}
for (var i = 0; i < this.axes_.length; i++) {
- if (this.axes_[i].valueWindow !== null) {
+ if (typeof(this.axes_[i].valueWindow) !== 'undefined' && this.axes_[i].valueWindow !== null) {
dirty = true;
dirtyY = true;
}
@@ -1379,7 +1392,8 @@ Dygraph.prototype.doUnzoom_ = function() {
newValueRanges = [];
for (i = 0; i < this.axes_.length; i++) {
- newValueRanges.push(this.axes_[i].extremeRange);
+ var axis = this.axes_[i];
+ newValueRanges.push(axis.valueRange != null ? axis.valueRange : axis.extremeRange);
}
}
@@ -1526,29 +1540,113 @@ Dygraph.prototype.mouseMove_ = function(event) {
Dygraph.prototype.idxToRow_ = function(idx) {
if (idx < 0) return -1;
- for (var i in this.layout_.datasets) {
- if (idx < this.layout_.datasets[i].length) {
- return this.boundaryIds_[0][0]+idx;
+ // 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;
}
- idx -= this.layout_.datasets[i].length;
+ }
+ 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 = "
";
+ } 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 += "";
+ }
+ }
+ }
+ 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) {
+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;
+ var html, sepLines, i, c, dash, strokePattern;
if (typeof(x) === 'undefined') {
if (this.attr_('legend') != 'always') return '';
@@ -1559,8 +1657,10 @@ Dygraph.prototype.generateLegendHTML_ = function(x, sel_points) {
if (!this.visibility()[i - 1]) continue;
c = this.plotter_.colors[labels[i]];
if (html !== '') html += (sepLines ? '
' : ' ');
- html += "—" + labels[i] +
- "";
+ strokePattern = this.attr_("strokePattern", labels[i]);
+ dash = this.generateLegendDashHTML_(strokePattern, c, oneEmWidth);
+ html += "" + dash +
+ " " + labels[i] + "";
}
return html;
}
@@ -1603,8 +1703,14 @@ Dygraph.prototype.generateLegendHTML_ = function(x, sel_points) {
* x-value. Should have properties like 'name', 'yval' and 'canvasy'.
*/
Dygraph.prototype.setLegendHTML_ = function(x, sel_points) {
- var html = this.generateLegendHTML_(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 {
@@ -1683,8 +1789,9 @@ Dygraph.prototype.setSelection = function(row) {
}
if (row !== false && row >= 0) {
- for (var i in this.layout_.datasets) {
- if (row < this.layout_.datasets[i].length) {
+ for (var setIdx = 0; setIdx < this.layout_.datasets.length; ++setIdx) {
+ var set = this.layout_.datasets[setIdx];
+ if (row < set.length) {
var point = this.layout_.points[pos+row];
if (this.attr_("stackedGraph")) {
@@ -1693,7 +1800,7 @@ Dygraph.prototype.setSelection = function(row) {
this.selPoints_.push(point);
}
- pos += this.layout_.datasets[i].length;
+ pos += set.length;
}
}
@@ -2019,9 +2126,15 @@ Dygraph.prototype.drawGraph_ = function(clearSelection) {
var extremes = packed[1];
this.boundaryIds_ = packed[2];
+ this.setIndexByName_ = {};
+ var labels = this.attr_("labels");
+ if (labels.length > 0) {
+ this.setIndexByName_[labels[0]] = 0;
+ }
for (var i = 1; i < datasets.length; i++) {
+ this.setIndexByName_[labels[i]] = i;
if (!this.visibility()[i - 1]) continue;
- this.layout_.addDataset(this.attr_("labels")[i], datasets[i]);
+ this.layout_.addDataset(labels[i], datasets[i]);
}
this.computeYAxisRanges_(extremes);
@@ -2051,10 +2164,10 @@ Dygraph.prototype.renderGraph_ = function(is_initial_draw, clearSelection) {
this.canvas_.getContext('2d').clearRect(0, 0, this.canvas_.width,
this.canvas_.height);
- if (is_initial_draw) {
- // Generate a static legend before any particular point is selected.
- this.setLegendHTML_();
- } else {
+ // Generate a static legend before any particular point is selected.
+ this.setLegendHTML_();
+
+ if (!is_initial_draw) {
if (clearSelection) {
if (typeof(this.selPoints_) !== 'undefined' && this.selPoints_.length) {
// We should select the point nearest the page x/y here, but it's easier
@@ -2090,7 +2203,7 @@ Dygraph.prototype.renderGraph_ = function(is_initial_draw, clearSelection) {
Dygraph.prototype.computeYAxes_ = function() {
// Preserve valueWindow settings if they exist, and if the user hasn't
// specified a new valueRange.
- var i, valueWindows, seriesName, axis, index;
+ var i, valueWindows, seriesName, axis, index, opts, v;
if (this.axes_ !== undefined && this.user_attrs_.hasOwnProperty("valueRange") === false) {
valueWindows = [];
for (index = 0; index < this.axes_.length; index++) {
@@ -2122,7 +2235,7 @@ Dygraph.prototype.computeYAxes_ = function() {
// Copy global axis options over to the first axis.
for (i = 0; i < axisOptions.length; i++) {
var k = axisOptions[i];
- var v = this.attr_(k);
+ v = this.attr_(k);
if (v) this.axes_[0][k] = v;
}
@@ -2136,7 +2249,7 @@ Dygraph.prototype.computeYAxes_ = function() {
}
if (typeof(axis) == 'object') {
// Add a new axis, making a copy of its per-axis options.
- var opts = {};
+ opts = {};
Dygraph.update(opts, this.axes_[0]);
Dygraph.update(opts, { valueRange: null }); // shouldn't inherit this.
var yAxisId = this.axes_.length;
@@ -2170,6 +2283,22 @@ Dygraph.prototype.computeYAxes_ = function() {
this.axes_[index].valueWindow = valueWindows[index];
}
}
+
+ // New axes options
+ for (axis = 0; axis < this.axes_.length; axis++) {
+ if (axis === 0) {
+ opts = this.optionsViewForAxis_('y' + (axis ? '2' : ''));
+ v = opts("valueRange");
+ if (v) this.axes_[axis].valueRange = v;
+ } else { // To keep old behavior
+ var axes = this.user_attrs_.axes;
+ if (axes && axes.y2) {
+ v = axes.y2.valueRange;
+ if (v) this.axes_[axis].valueRange = v;
+ }
+ }
+ }
+
};
/**
@@ -2654,9 +2783,9 @@ Dygraph.prototype.parseCSV_ = function(data) {
this.parseFloat_(vals[1], i, line),
this.parseFloat_(vals[2], i, line) ];
} else {
- this.warning('When using customBars, values must be either blank ' +
- 'or "low;center;high" tuples (got "' + val +
- '" on line ' + (1+i));
+ this.warn('When using customBars, values must be either blank ' +
+ 'or "low;center;high" tuples (got "' + val +
+ '" on line ' + (1+i));
}
}
}
@@ -2774,6 +2903,19 @@ Dygraph.prototype.parseArray_ = function(data) {
* @private
*/
Dygraph.prototype.parseDataTable_ = function(data) {
+ var shortTextForAnnotationNum = function(num) {
+ // converts [0-9]+ [A-Z][a-z]*
+ // example: 0=A, 1=B, 25=Z, 26=Aa, 27=Ab
+ // and continues like.. Ba Bb .. Za .. Zz..Aaa...Zzz Aaaa Zzzz
+ var shortText = String.fromCharCode(65 /* A */ + num % 26);
+ num = Math.floor(num / 26);
+ while ( num > 0 ) {
+ shortText = String.fromCharCode(65 /* A */ + (num - 1) % 26 ) + shortText.toLowerCase();
+ num = Math.floor((num - 1) / 26);
+ }
+ return shortText;
+ }
+
var cols = data.getNumberOfColumns();
var rows = data.getNumberOfRows();
@@ -2855,7 +2997,7 @@ Dygraph.prototype.parseDataTable_ = function(data) {
var ann = {};
ann.series = data.getColumnLabel(col);
ann.xval = row[0];
- ann.shortText = String.fromCharCode(65 /* A */ + annotations.length);
+ ann.shortText = shortTextForAnnotationNum(annotations.length);
ann.text = '';
for (var k = 0; k < annotationCols[col].length; k++) {
if (k) ann.text += "\n";
@@ -2897,21 +3039,25 @@ Dygraph.prototype.parseDataTable_ = function(data) {
* @private
*/
Dygraph.prototype.start_ = function() {
- if (typeof this.file_ == 'function') {
- // CSV string. Pretend we got it via XHR.
- this.loadedEvent_(this.file_());
- } else if (Dygraph.isArrayLike(this.file_)) {
- this.rawData_ = this.parseArray_(this.file_);
+ var data = this.file_;
+
+ // Functions can return references of all other types.
+ if (typeof data == 'function') {
+ data = data();
+ }
+
+ if (Dygraph.isArrayLike(data)) {
+ this.rawData_ = this.parseArray_(data);
this.predraw_();
- } else if (typeof this.file_ == 'object' &&
- typeof this.file_.getColumnRange == 'function') {
+ } else if (typeof data == 'object' &&
+ typeof data.getColumnRange == 'function') {
// must be a DataTable from gviz.
- this.parseDataTable_(this.file_);
+ this.parseDataTable_(data);
this.predraw_();
- } else if (typeof this.file_ == 'string') {
+ } else if (typeof data == 'string') {
// Heuristic: a newline means it's CSV data. Otherwise it's an URL.
- if (this.file_.indexOf('\n') >= 0) {
- this.loadedEvent_(this.file_);
+ if (data.indexOf('\n') >= 0) {
+ this.loadedEvent_(data);
} else {
var req = new XMLHttpRequest();
var caller = this;
@@ -2924,11 +3070,11 @@ Dygraph.prototype.start_ = function() {
}
};
- req.open("GET", this.file_, true);
+ req.open("GET", data, true);
req.send(null);
}
} else {
- this.error("Unknown data format: " + (typeof this.file_));
+ this.error("Unknown data format: " + (typeof data));
}
};
@@ -3158,15 +3304,19 @@ Dygraph.prototype.annotations = function() {
};
/**
+ * Get the list of label names for this graph. The first column is the
+ * x-axis, so the data series names start at index 1.
+ */
+Dygraph.prototype.getLabels = function(name) {
+ return this.attr_("labels").slice();
+};
+
+/**
* Get the index of a series (column) given its name. The first column is the
* x-axis, so the data series start with index 1.
*/
Dygraph.prototype.indexFromSetName = function(name) {
- var labels = this.attr_("labels");
- for (var i = 0; i < labels.length; i++) {
- if (labels[i] == name) return i;
- }
- return null;
+ return this.setIndexByName_[name];
};
/**