this.dateWindow_ = attrs.dateWindow || null;
this.valueRange_ = attrs.valueRange || null;
this.wilsonInterval_ = attrs.wilsonInterval || true;
- this.customBars_ = attrs.customBars || false;
// Clear the div. This ensure that, if multiple dygraphs are passed the same
// div, then only one will be drawn.
// Create the PlotKit grapher
// TODO(danvk): why does the Layout need its own set of options?
this.layoutOptions_ = { 'errorBars': (this.attr_("errorBars") ||
- this.customBars_),
+ this.attr_("customBars")),
'xOriginIsZero': false };
MochiKit.Base.update(this.layoutOptions_, this.attrs_);
MochiKit.Base.update(this.layoutOptions_, this.user_attrs_);
- this.layout_ = new DygraphLayout(this.layoutOptions_);
+ this.layout_ = new DygraphLayout(this, this.layoutOptions_);
// TODO(danvk): why does the Renderer need its own set of options?
this.renderOptions_ = { colorScheme: this.colors_,
axisLineWidth: Dygraph.AXIS_LINE_WIDTH };
MochiKit.Base.update(this.renderOptions_, this.attrs_);
MochiKit.Base.update(this.renderOptions_, this.user_attrs_);
- this.plotter_ = new DygraphCanvasRenderer(this.hidden_, this.layout_,
+ this.plotter_ = new DygraphCanvasRenderer(this,
+ this.hidden_, this.layout_,
this.renderOptions_);
this.createStatusMessage_();
MochiKit.Base.update(this.layoutOptions_, this.attrs_);
}
+// The following functions are from quirksmode.org
+// http://www.quirksmode.org/js/findpos.html
+Dygraph.findPosX = function(obj) {
+ var curleft = 0;
+ if (obj.offsetParent) {
+ while (obj.offsetParent) {
+ curleft += obj.offsetLeft;
+ obj = obj.offsetParent;
+ }
+ }
+ else if (obj.x)
+ curleft += obj.x;
+ return curleft;
+};
+
+Dygraph.findPosY = function(obj) {
+ var curtop = 0;
+ if (obj.offsetParent) {
+ while (obj.offsetParent) {
+ curtop += obj.offsetTop;
+ obj = obj.offsetParent;
+ }
+ }
+ else if (obj.y)
+ curtop += obj.y;
+ return curtop;
+};
+
/**
* Create the div that contains information on the selected point(s)
* This goes in the top right of the canvas, unless an external div has already
// Track the beginning of drag events
connect(this.hidden_, 'onmousedown', function(event) {
mouseDown = true;
- px = PlotKit.Base.findPosX(self.canvas_);
- py = PlotKit.Base.findPosY(self.canvas_);
+ px = Dygraph.findPosX(self.canvas_);
+ py = Dygraph.findPosY(self.canvas_);
dragStartX = getX(event);
dragStartY = getY(event);
});
* @private
*/
Dygraph.prototype.mouseMove_ = function(event) {
- var canvasx = event.mouse().page.x - PlotKit.Base.findPosX(this.hidden_);
+ var canvasx = event.mouse().page.x - Dygraph.findPosX(this.hidden_);
var points = this.layout_.points;
var lastx = -1;
ctx.clearRect(px - circleSize - 1, 0, 2 * circleSize + 2, this.height_);
}
+ var isOK = function(x) { return x && !isNaN(x); };
+
if (selPoints.length > 0) {
var canvasx = selPoints[0].canvasx;
var replace = this.attr_('xValueFormatter')(lastx, this) + ":";
var clen = this.colors_.length;
for (var i = 0; i < selPoints.length; i++) {
+ if (!isOK(selPoints[i].canvasy)) continue;
if (this.attr_("labelsSeparateLines")) {
replace += "<br/>";
}
// Draw colored circles over the center of each selected point
ctx.save()
for (var i = 0; i < selPoints.length; i++) {
+ if (!isOK(selPoints[i%clen].canvasy)) continue;
ctx.beginPath();
ctx.fillStyle = this.colors_[i%clen].toRGBString();
ctx.arc(canvasx, selPoints[i%clen].canvasy, circleSize, 0, 360, false);
yTicks: ticks } );
};
+// Computes the range of the data series (including confidence intervals).
+// series is either [ [x1, y1], [x2, y2], ... ] or
+// [ [x1, [y1, dev_low, dev_high]], [x2, [y2, dev_low, dev_high]], ...
+// Returns [low, high]
+Dygraph.prototype.extremeValues_ = function(series) {
+ var minY = null, maxY = null;
+
+ var bars = this.attr_("errorBars") || this.attr_("customBars");
+ if (bars) {
+ // With custom bars, maxY is the max of the high values.
+ for (var j = 0; j < series.length; j++) {
+ var y = series[j][1][0];
+ if (!y) continue;
+ var low = y - series[j][1][1];
+ var high = y + series[j][1][2];
+ if (low > y) low = y; // this can happen with custom bars,
+ if (high < y) high = y; // e.g. in tests/custom-bars.html
+ if (maxY == null || high > maxY) {
+ maxY = high;
+ }
+ if (minY == null || low < minY) {
+ minY = low;
+ }
+ }
+ } else {
+ for (var j = 0; j < series.length; j++) {
+ var y = series[j][1];
+ if (!y) continue;
+ if (maxY == null || y > maxY) {
+ maxY = y;
+ }
+ if (minY == null || y < minY) {
+ minY = y;
+ }
+ }
+ }
+
+ return [minY, maxY];
+};
+
/**
* Update the graph with new data. Data is in the format
* [ [date1, val1, val2, ...], [date2, val1, val2, ...] if errorBars=false
var minY = null, maxY = null;
this.layout_.removeAllDatasets();
this.setColors_();
+ this.attrs_['pointSize'] = 0.5 * this.attr_('highlightCircleSize');
// Loop over all fields in the dataset
for (var i = 1; i < data[0].length; i++) {
series = this.rollingAverage(series, this.rollPeriod_);
// Prune down to the desired range, if necessary (for zooming)
- var bars = this.attr_("errorBars") || this.customBars_;
+ var bars = this.attr_("errorBars") || this.attr_("customBars");
if (this.dateWindow_) {
var low = this.dateWindow_[0];
var high= this.dateWindow_[1];
for (var k = 0; k < series.length; k++) {
if (series[k][0] >= low && series[k][0] <= high) {
pruned.push(series[k]);
- var y = bars ? series[k][1][0] : series[k][1];
- if (maxY == null || y > maxY) maxY = y;
- if (minY == null || y < minY) minY = y;
}
}
series = pruned;
- } else {
- if (!this.customBars_) {
- for (var j = 0; j < series.length; j++) {
- var y = bars ? series[j][1][0] : series[j][1];
- if (maxY == null || y > maxY) {
- maxY = bars ? y + series[j][1][1] : y;
- }
- if (minY == null || y < minY) {
- minY = bars ? y + series[j][1][1] : y;
- }
- }
- } else {
- // With custom bars, maxY is the max of the high values.
- for (var j = 0; j < series.length; j++) {
- var y = series[j][1][0];
- var high = series[j][1][2];
- if (high > y) y = high;
- if (maxY == null || y > maxY) {
- maxY = y;
- }
- if (minY == null || y < minY) {
- minY = y;
- }
- }
- }
}
+ var extremes = this.extremeValues_(series);
+ var thisMinY = extremes[0];
+ var thisMaxY = extremes[1];
+ if (!minY || thisMinY < minY) minY = thisMinY;
+ if (!maxY || thisMaxY > maxY) maxY = thisMaxY;
+
if (bars) {
var vals = [];
for (var j=0; j<series.length; j++)
rollingData[i] = [date, mult * value];
}
}
- } else if (this.customBars_) {
+ } else if (this.attr_("customBars")) {
var low = 0;
var mid = 0;
var high = 0;
// there is not enough data to roll over the full number of days
var num_init_points = Math.min(rollPeriod - 1, originalData.length - 2);
if (!this.attr_("errorBars")){
- for (var i = 0; i < num_init_points; i++) {
- var sum = 0;
- for (var j = 0; j < i + 1; j++)
- sum += originalData[j][1];
- rollingData[i] = [originalData[i][0], sum / (i + 1)];
+ if (rollPeriod == 1) {
+ return originalData;
}
- // Calculate the rolling average for the remaining points
- for (var i = Math.min(rollPeriod - 1, originalData.length - 2);
- i < originalData.length;
- i++) {
+
+ for (var i = 0; i < originalData.length; i++) {
var sum = 0;
- for (var j = i - rollPeriod + 1; j < i + 1; j++)
+ var num_ok = 0;
+ for (var j = Math.max(0, i - rollPeriod + 1); j < i + 1; j++) {
+ var y = originalData[j][1];
+ if (!y || isNaN(y)) continue;
+ num_ok++;
sum += originalData[j][1];
- rollingData[i] = [originalData[i][0], sum / rollPeriod];
+ }
+ if (num_ok) {
+ rollingData[i] = [originalData[i][0], sum / num_ok];
+ } else {
+ rollingData[i] = [originalData[i][0], null];
+ }
}
+
} else {
- for (var i = 0; i < num_init_points; i++) {
+ for (var i = 0; i < originalData.length; i++) {
var sum = 0;
var variance = 0;
- for (var j = 0; j < i + 1; j++) {
+ var num_ok = 0;
+ for (var j = Math.max(0, i - rollPeriod + 1); j < i + 1; j++) {
+ var y = originalData[j][1][0];
+ if (!y || isNaN(y)) continue;
+ num_ok++;
sum += originalData[j][1][0];
variance += Math.pow(originalData[j][1][1], 2);
}
- var stddev = Math.sqrt(variance)/(i+1);
- rollingData[i] = [originalData[i][0],
- [sum/(i+1), sigma * stddev, sigma * stddev]];
- }
- // Calculate the rolling average for the remaining points
- for (var i = Math.min(rollPeriod - 1, originalData.length - 2);
- i < originalData.length;
- i++) {
- var sum = 0;
- var variance = 0;
- for (var j = i - rollPeriod + 1; j < i + 1; j++) {
- sum += originalData[j][1][0];
- variance += Math.pow(originalData[j][1][1], 2);
+ if (num_ok) {
+ var stddev = Math.sqrt(variance) / num_ok;
+ rollingData[i] = [originalData[i][0],
+ [sum / num_ok, sigma * stddev, sigma * stddev]];
+ } else {
+ rollingData[i] = [originalData[i][0], [null, null, null]];
}
- var stddev = Math.sqrt(variance) / rollPeriod;
- rollingData[i] = [originalData[i][0],
- [sum / rollPeriod, sigma * stddev, sigma * stddev]];
}
}
}
for (var j = 1; j < inFields.length; j += 2)
fields[(j + 1) / 2] = [parseFloat(inFields[j]),
parseFloat(inFields[j + 1])];
- } else if (this.customBars_) {
+ } else if (this.attr_("customBars")) {
// Bars are a low;center;high tuple
for (var j = 1; j < inFields.length; j++) {
var vals = inFields[j].split(";");
} else {
row.push(data.getValue(i, 0));
}
- var any_data = false;
for (var j = 1; j < cols; j++) {
row.push(data.getValue(i, j));
- if (data.getValue(i, j)) any_data = true;
}
- if (any_data) ret.push(row);
+ ret.push(row);
}
return ret;
}
*/
Dygraph.prototype.updateOptions = function(attrs) {
// TODO(danvk): this is a mess. Rethink this function.
- if (attrs.customBars) {
- this.customBars_ = attrs.customBars;
- }
if (attrs.rollPeriod) {
this.rollPeriod_ = attrs.rollPeriod;
}