- ctx.save();
- if (strokeWidth && !stepPlot && (!strokePattern || strokePattern.length <= 1)) {
- this._drawTrivialLine(ctx, points, setLength, firstIndexInSet, setName, color, strokeWidth, drawPointCallback, pointSize, drawPoints, drawGapPoints);
- } else {
- this._drawNonTrivialLine(ctx, points, setLength, firstIndexInSet, setName, color, strokeWidth, strokePattern, drawPointCallback, pointSize, drawPoints, drawGapPoints, stepPlot);
- }
- ctx.restore();
-};
-
-DygraphCanvasRenderer.prototype._drawNonTrivialLine = function(
- ctx, points, setLength, firstIndexInSet, setName, color, strokeWidth, strokePattern, drawPointCallback, pointSize, drawPoints, drawGapPoints, stepPlot) {
- var prevX = null;
- var prevY = null;
- var nextY = null;
- var point, nextPoint;
- var pointsOnLine = []; // Array of [canvasx, canvasy] pairs.
- var next = DygraphCanvasRenderer.makeNextPointStep_(
- this.attr_('connectSeparatedPoints'), points, firstIndexInSet,
- firstIndexInSet + setLength);
- for (var j = 0; j < setLength; j = next(j)) {
- point = points[firstIndexInSet + j];
- nextY = (next(j) < setLength) ?
- points[firstIndexInSet + next(j)].canvasy : null;
- if (DygraphCanvasRenderer.isNullOrNaN_(point.canvasy)) {
- if (stepPlot && prevX !== null) {
- // Draw a horizontal line to the start of the missing data
- ctx.beginPath();
- ctx.strokeStyle = color;
- ctx.lineWidth = this.attr_('strokeWidth');
- this._dashedLine(ctx, prevX, prevY, point.canvasx, prevY, strokePattern);
- ctx.stroke();
- }
- // this will make us move to the next point, not draw a line to it.
- prevX = prevY = null;
- } else {
- // A point is "isolated" if it is non-null but both the previous
- // and next points are null.
- var isIsolated = (!prevX && DygraphCanvasRenderer.isNullOrNaN_(nextY));
- if (drawGapPoints) {
- // Also consider a point to be is "isolated" if it's adjacent to a
- // null point, excluding the graph edges.
- if ((j > 0 && !prevX) ||
- (next(j) < setLength && DygraphCanvasRenderer.isNullOrNaN_(nextY))) {
- isIsolated = true;
- }
- }
- if (prevX === null) {
- prevX = point.canvasx;
- prevY = point.canvasy;
- } else {
- // Skip over points that will be drawn in the same pixel.
- if (Math.round(prevX) == Math.round(point.canvasx) &&
- Math.round(prevY) == Math.round(point.canvasy)) {
- continue;
- }
- // TODO(antrob): skip over points that lie on a line that is already
- // going to be drawn. There is no need to have more than 2
- // consecutive points that are collinear.
- if (strokeWidth) {
- ctx.beginPath();
- ctx.strokeStyle = color;
- ctx.lineWidth = strokeWidth;
- if (stepPlot) {
- this._dashedLine(ctx, prevX, prevY, point.canvasx, prevY, strokePattern);
- prevX = point.canvasx;
- }
- this._dashedLine(ctx, prevX, prevY, point.canvasx, point.canvasy, strokePattern);
- prevX = point.canvasx;
- prevY = point.canvasy;
- ctx.stroke();
+/**
+ * Proxy for CanvasRenderingContext2D which drops moveTo/lineTo calls which are
+ * superfluous. It accumulates all movements which haven't changed the x-value
+ * and only applies the two with the most extreme y-values.
+ *
+ * Calls to lineTo/moveTo must have non-decreasing x-values.
+ */
+DygraphCanvasRenderer._fastCanvasProxy = function(context) {
+ var pendingActions = []; // array of [type, x, y] tuples
+ var lastRoundedX = null;
+
+ var LINE_TO = 1,
+ MOVE_TO = 2;
+
+ var actionCount = 0; // number of moveTos and lineTos passed to context.
+
+ // Drop superfluous motions
+ // Assumes all pendingActions have the same (rounded) x-value.
+ var compressActions = function(opt_losslessOnly) {
+ if (pendingActions.length <= 1) return;
+
+ // Lossless compression: drop inconsequential moveTos.
+ for (var i = pendingActions.length - 1; i > 0; i--) {
+ var action = pendingActions[i];
+ if (action[0] == MOVE_TO) {
+ var prevAction = pendingActions[i - 1];
+ if (prevAction[1] == action[1] && prevAction[2] == action[2]) {
+ pendingActions.splice(i, 1);