return [left, right];
};
+/**
+ * Returns the currently-visible y-range. This can be affected by zooming,
+ * panning or a call to updateOptions.
+ * Returns a two-element array: [bottom, top].
+ */
+Dygraph.prototype.yAxisRange = function() {
+ return this.displayedYRange_;
+};
+
+/**
+ * Convert from data coordinates to canvas/div X/Y coordinates.
+ * Returns a two-element array: [X, Y]
+ */
+Dygraph.prototype.toDomCoords = function(x, y) {
+ var ret = [null, null];
+ var area = this.plotter_.area;
+ if (x !== null) {
+ var xRange = this.xAxisRange();
+ ret[0] = area.x + (x - xRange[0]) / (xRange[1] - xRange[0]) * area.w;
+ }
+
+ if (y !== null) {
+ var yRange = this.yAxisRange();
+ ret[1] = area.y + (yRange[1] - y) / (yRange[1] - yRange[0]) * area.h;
+ }
+
+ return ret;
+};
+
+/**
+ * Convert from canvas/div coords to data coordinates.
+ * Returns a two-element array: [X, Y]
+ */
+Dygraph.prototype.toDataCoords = function(x, y) {
+ var ret = [null, null];
+ var area = this.plotter_.area;
+ if (x !== null) {
+ var xRange = this.xAxisRange();
+ ret[0] = xRange[0] + (x - area.x) / area.w * (xRange[1] - xRange[0]);
+ }
+
+ if (y !== null) {
+ var yRange = this.yAxisRange();
+ ret[1] = yRange[0] + (area.h - y) / area.h * (yRange[1] - yRange[0]);
+ }
+
+ return ret;
+};
+
Dygraph.addEvent = function(el, evt, fn) {
var normed_fn = function(e) {
if (!e) var e = window.event;
var stacked_datasets = [];
// Loop over all fields in the dataset
-
for (var i = 1; i < data[0].length; i++) {
if (!this.visibility()[i - 1]) continue;
series = this.rollingAverage(series, this.rollPeriod_);
// Prune down to the desired range, if necessary (for zooming)
+ // Because there can be lines going to points outside of the visible area,
+ // we actually prune to visible points, plus one on either side.
var bars = this.attr_("errorBars") || this.attr_("customBars");
if (this.dateWindow_) {
var low = this.dateWindow_[0];
var high= this.dateWindow_[1];
var pruned = [];
+ // TODO(danvk): do binary search instead of linear search.
+ // TODO(danvk): pass firstIdx and lastIdx directly to the renderer.
+ var firstIdx = null, lastIdx = null;
for (var k = 0; k < series.length; k++) {
- // if (series[k][0] >= low && series[k][0] <= high) {
- pruned.push(series[k]);
- // }
+ if (series[k][0] >= low && firstIdx === null) {
+ firstIdx = k;
+ }
+ if (series[k][0] <= high) {
+ lastIdx = k;
+ }
+ }
+ if (firstIdx === null) firstIdx = 0;
+ if (firstIdx > 0) firstIdx--;
+ if (lastIdx === null) lastIdx = series.length - 1;
+ if (lastIdx < series.length - 1) lastIdx++;
+ for (var k = firstIdx; k <= lastIdx; k++) {
+ pruned.push(series[k]);
}
series = pruned;
}
// set explicitly by the user.
if (this.valueRange_ != null) {
this.addYTicks_(this.valueRange_[0], this.valueRange_[1]);
+ this.displayedYRange_ = this.valueRange_;
} else {
// This affects the calculation of span, below.
if (this.attr_("includeZero") && minY > 0) {
}
this.addYTicks_(minAxisY, maxAxisY);
+ this.displayedYRange_ = [minAxisY, maxAxisY];
}
this.addXTicks_();
return null;
}
if (parsedData[i][0] == null
- || typeof(parsedData[i][0].getTime) != 'function') {
- this.error("x value in row " << (1 + i) << " is not a Date");
+ || typeof(parsedData[i][0].getTime) != 'function'
+ || isNaN(parsedData[i][0].getTime())) {
+ this.error("x value in row " + (1 + i) + " is not a Date");
return null;
}
parsedData[i][0] = parsedData[i][0].getTime();