return connectSeparatedPoints
? DygraphCanvasRenderer._predicateThatSkipsEmptyPoints
: null;
-}
+};
DygraphCanvasRenderer._predicateThatSkipsEmptyPoints =
- function(array, idx) { return array[idx].yval !== null; }
+ function(array, idx) {
+ return array[idx].yval !== null;
+};
/**
- *
* @private
*/
DygraphCanvasRenderer.prototype._drawStyledLine = function(
}
var drawGapPoints = this.dygraph_.attr_('drawGapEdgePoints', setName);
- ctx.save();
-
var iter = Dygraph.createIterator(points, firstIndexInSet, setLength,
DygraphCanvasRenderer._getIteratorPredicate(
this.attr_("connectSeparatedPoints")));
var stroking = strokePattern && (strokePattern.length >= 2);
- var pointsOnLine;
- var strategy;
+ ctx.save();
if (stroking) {
ctx.installPattern(strokePattern);
}
- strategy = trivialStrategy(ctx, color, strokeWidth);
- pointsOnLine = this._drawSeries(ctx, iter, strokeWidth, pointSize, drawPoints, drawGapPoints, stepPlot, strategy);
+ var pointsOnLine = this._drawSeries(ctx, iter, strokeWidth, pointSize, drawPoints, drawGapPoints, stepPlot, color);
this._drawPointsOnLine(ctx, pointsOnLine, drawPointCallback, setName, color, pointSize);
if (stroking) {
ctx.restore();
};
-var trivialStrategy = function(ctx, color, strokeWidth) {
- return new function() {
- this.init = function() {
- ctx.beginPath();
- ctx.strokeStyle = color;
- ctx.lineWidth = strokeWidth;
- };
- this.finish = function() {
- ctx.stroke(); // should this include closePath?
- };
- this.startSegment = function() { };
- this.endSegment = function() { };
- this.drawLine = function(x1, y1, x2, y2) {
- ctx.moveTo(x1, y1);
- ctx.lineTo(x2, y2);
- };
- // don't skip pixels.
- this.skipPixel = function() {
- return false;
- };
- };
-};
-
DygraphCanvasRenderer.prototype._drawPointsOnLine = function(ctx, pointsOnLine, drawPointCallback, setName, color, pointSize) {
for (var idx = 0; idx < pointsOnLine.length; idx++) {
var cb = pointsOnLine[idx];
DygraphCanvasRenderer.prototype._drawSeries = function(
ctx, iter, strokeWidth, pointSize, drawPoints, drawGapPoints,
- stepPlot, strategy) {
+ stepPlot, color) {
var prevCanvasX = null;
var prevCanvasY = null;
var pointsOnLine = []; // Array of [canvasx, canvasy] pairs.
var first = true; // the first cycle through the while loop
- strategy.init();
+ ctx.beginPath();
+ ctx.strokeStyle = color;
+ ctx.lineWidth = strokeWidth;
+
+ // NOTE: we break the iterator's encapsulation here for about a 25% speedup.
+ var arr = iter.array_;
+ var limit = iter.end_;
+ var predicate = iter.predicate_;
+
+ for (var i = iter.start_; i < limit; i++) {
+ point = arr[i];
+ if (predicate) {
+ while (i < limit && !predicate(arr, i)) {
+ i++;
+ }
+ if (i == limit) break;
+ point = arr[i];
+ }
- while(iter.hasNext) {
- point = iter.next();
if (point.canvasy === null || point.canvasy != point.canvasy) {
if (stepPlot && prevCanvasX !== null) {
// Draw a horizontal line to the start of the missing data
- strategy.startSegment();
- strategy.drawLine(prevX, prevY, point.canvasx, prevY);
- strategy.endSegment();
+ ctx.moveTo(prevX, prevY);
+ ctx.lineTo(point.canvasx, prevY);
}
prevCanvasX = prevCanvasY = null;
} else {
- nextCanvasY = iter.hasNext ? iter.peek.canvasy : null;
- // TODO: we calculate isNullOrNaN for this point, and the next, and then,
- // when we iterate, test for isNullOrNaN again. Why bother?
- var isNextCanvasYNullOrNaN = nextCanvasY === null || nextCanvasY != nextCanvasY;
- isIsolated = (!prevCanvasX && isNextCanvasYNullOrNaN);
- if (drawGapPoints) {
- // Also consider a point to be "isolated" if it's adjacent to a
- // null point, excluding the graph edges.
- if ((!first && !prevCanvasX) ||
- (iter.hasNext && isNextCanvasYNullOrNaN)) {
- isIsolated = true;
+ isIsolated = false;
+ if (drawGapPoints || !prevCanvasX) {
+ iter.nextIdx_ = i;
+ var peek = iter.next();
+ nextCanvasY = iter.hasNext ? iter.peek.canvasy : null;
+
+ var isNextCanvasYNullOrNaN = nextCanvasY === null ||
+ nextCanvasY != nextCanvasY;
+ isIsolated = (!prevCanvasX && isNextCanvasYNullOrNaN);
+ if (drawGapPoints) {
+ // Also consider a point to be "isolated" if it's adjacent to a
+ // null point, excluding the graph edges.
+ if ((!first && !prevCanvasX) ||
+ (iter.hasNext && isNextCanvasYNullOrNaN)) {
+ isIsolated = true;
+ }
}
}
+
if (prevCanvasX !== null) {
- if (strategy.skipPixel(prevCanvasX, prevCanvasY, point.canvasx, point.canvasy)) {
- continue;
- }
if (strokeWidth) {
- strategy.startSegment();
if (stepPlot) {
- strategy.drawLine(prevCanvasX, prevCanvasY, point.canvasx, prevCanvasY);
+ ctx.moveTo(prevCanvasX, prevCanvasY);
+ ctx.lineTo(point.canvasx, prevCanvasY);
prevCanvasX = point.canvasx;
}
- strategy.drawLine(prevCanvasX, prevCanvasY, point.canvasx, point.canvasy);
- strategy.endSegment();
+
+ // TODO(danvk): this moveTo is rarely necessary
+ ctx.moveTo(prevCanvasX, prevCanvasY);
+ ctx.lineTo(point.canvasx, point.canvasy);
}
}
if (drawPoints || isIsolated) {
}
first = false;
}
- strategy.finish();
+ ctx.stroke();
return pointsOnLine;
};
// TODO(bhs): this loop is a hot-spot for high-point-count charts. These
// transformations can be pushed into the canvas via linear transformation
// matrices.
+ // NOTE(danvk): this is trickier than it sounds at first. The transformation
+ // needs to be done before the .moveTo() and .lineTo() calls, but must be
+ // undone before the .stroke() call to ensure that the stroke width is
+ // unaffected. An alternative is to reduce the stroke width in the
+ // transformed coordinate space, but you can't specify different values for
+ // each dimension (as you can with .scale()). The speedup here is ~12%.
var points = this.layout.points;
for (i = points.length; i--;) {
var point = points[i];
ctx.beginPath();
while (iter.hasNext) {
var point = iter.next();
- if (point.name == setName) { // TODO(klausw): this is always true
- if (!Dygraph.isOK(point.y)) {
- prevX = NaN;
- continue;
- }
+ if (!Dygraph.isOK(point.y)) {
+ prevX = NaN;
+ continue;
+ }
- // TODO(danvk): here
+ if (stepPlot) {
+ newYs = [ point.y_bottom, point.y_top ];
+ prevY = point.y;
+ } else {
+ newYs = [ point.y_bottom, point.y_top ];
+ }
+ newYs[0] = this.area.h * newYs[0] + this.area.y;
+ newYs[1] = this.area.h * newYs[1] + this.area.y;
+ if (!isNaN(prevX)) {
if (stepPlot) {
- newYs = [ point.y_bottom, point.y_top ];
- prevY = point.y;
+ ctx.moveTo(prevX, newYs[0]);
} else {
- newYs = [ point.y_bottom, point.y_top ];
+ ctx.moveTo(prevX, prevYs[0]);
}
- newYs[0] = this.area.h * newYs[0] + this.area.y;
- newYs[1] = this.area.h * newYs[1] + this.area.y;
- if (!isNaN(prevX)) {
- if (stepPlot) {
- ctx.moveTo(prevX, newYs[0]);
- } else {
- ctx.moveTo(prevX, prevYs[0]);
- }
- ctx.lineTo(point.canvasx, newYs[0]);
- ctx.lineTo(point.canvasx, newYs[1]);
- if (stepPlot) {
- ctx.lineTo(prevX, newYs[1]);
- } else {
- ctx.lineTo(prevX, prevYs[1]);
- }
- ctx.closePath();
+ ctx.lineTo(point.canvasx, newYs[0]);
+ ctx.lineTo(point.canvasx, newYs[1]);
+ if (stepPlot) {
+ ctx.lineTo(prevX, newYs[1]);
+ } else {
+ ctx.lineTo(prevX, prevYs[1]);
}
- prevYs = newYs;
- prevX = point.canvasx;
+ ctx.closePath();
}
+ prevYs = newYs;
+ prevX = point.canvasx;
}
ctx.fill();
}
ctx.beginPath();
while(iter.hasNext) {
var point = iter.next();
- if (point.name == setName) { // TODO(klausw): this is always true
- if (!Dygraph.isOK(point.y)) {
- prevX = NaN;
- continue;
- }
- if (stackedGraph) {
- currBaseline = baseline[point.canvasx];
- var lastY;
- if (currBaseline === undefined) {
- lastY = axisY;
+ if (!Dygraph.isOK(point.y)) {
+ prevX = NaN;
+ continue;
+ }
+ if (stackedGraph) {
+ currBaseline = baseline[point.canvasx];
+ var lastY;
+ if (currBaseline === undefined) {
+ lastY = axisY;
+ } else {
+ if(stepPlot) {
+ lastY = currBaseline[0];
} else {
- if(stepPlot) {
- lastY = currBaseline[0];
- } else {
- lastY = currBaseline;
- }
+ lastY = currBaseline;
}
- newYs = [ point.canvasy, lastY ];
+ }
+ newYs = [ point.canvasy, lastY ];
- if(stepPlot) {
- // Step plots must keep track of the top and bottom of
- // the baseline at each point.
- if(prevYs[0] === -1) {
- baseline[point.canvasx] = [ point.canvasy, axisY ];
- } else {
- baseline[point.canvasx] = [ point.canvasy, prevYs[0] ];
- }
+ if(stepPlot) {
+ // Step plots must keep track of the top and bottom of
+ // the baseline at each point.
+ if(prevYs[0] === -1) {
+ baseline[point.canvasx] = [ point.canvasy, axisY ];
} else {
- baseline[point.canvasx] = point.canvasy;
+ baseline[point.canvasx] = [ point.canvasy, prevYs[0] ];
}
-
} else {
- newYs = [ point.canvasy, axisY ];
+ baseline[point.canvasx] = point.canvasy;
}
- if (!isNaN(prevX)) {
- ctx.moveTo(prevX, prevYs[0]);
- if (stepPlot) {
- ctx.lineTo(point.canvasx, prevYs[0]);
- if(currBaseline) {
- // Draw to the bottom of the baseline
- ctx.lineTo(point.canvasx, currBaseline[1]);
- } else {
- ctx.lineTo(point.canvasx, newYs[1]);
- }
+ } else {
+ newYs = [ point.canvasy, axisY ];
+ }
+ if (!isNaN(prevX)) {
+ ctx.moveTo(prevX, prevYs[0]);
+
+ if (stepPlot) {
+ ctx.lineTo(point.canvasx, prevYs[0]);
+ if(currBaseline) {
+ // Draw to the bottom of the baseline
+ ctx.lineTo(point.canvasx, currBaseline[1]);
} else {
- ctx.lineTo(point.canvasx, newYs[0]);
ctx.lineTo(point.canvasx, newYs[1]);
}
-
- ctx.lineTo(prevX, prevYs[1]);
- ctx.closePath();
+ } else {
+ ctx.lineTo(point.canvasx, newYs[0]);
+ ctx.lineTo(point.canvasx, newYs[1]);
}
- prevYs = newYs;
- prevX = point.canvasx;
+
+ ctx.lineTo(prevX, prevYs[1]);
+ ctx.closePath();
}
+ prevYs = newYs;
+ prevX = point.canvasx;
}
ctx.fill();
}