* skip over points with missing yVals.
*/
DygraphCanvasRenderer._getIteratorPredicate = function(connectSeparatedPoints) {
- return connectSeparatedPoints ? DygraphCanvasRenderer._predicateThatSkipsEmptyPoints : null;
-}
+ 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(
- ctx, i, setName, color, strokeWidth, strokePattern, drawPoints,
+ ctx, setIdx, setName, color, strokeWidth, strokePattern, drawPoints,
drawPointCallback, pointSize) {
// TODO(konigsberg): Compute attributes outside this method call.
var stepPlot = this.attr_("stepPlot");
- var firstIndexInSet = this.layout.setPointsOffsets[i];
- var setLength = this.layout.setPointsLengths[i];
- var points = this.layout.points;
if (!Dygraph.isArrayLike(strokePattern)) {
strokePattern = null;
}
var drawGapPoints = this.dygraph_.attr_('drawGapEdgePoints', setName);
- ctx.save();
+ var points = this.layout.points[setIdx];
+ var iter = Dygraph.createIterator(points, 0, points.length,
+ DygraphCanvasRenderer._getIteratorPredicate(
+ this.attr_("connectSeparatedPoints")));
- var iter = Dygraph.createIterator(points, firstIndexInSet, setLength,
- DygraphCanvasRenderer._getIteratorPredicate(this.attr_("connectSeparatedPoints")));
+ var stroking = strokePattern && (strokePattern.length >= 2);
- var pointsOnLine;
- var strategy;
- if (!strokePattern || strokePattern.length <= 1) {
- strategy = trivialStrategy(ctx, color, strokeWidth);
- } else {
- strategy = nonTrivialStrategy(this, ctx, color, strokeWidth, strokePattern);
+ ctx.save();
+ if (stroking) {
+ ctx.installPattern(strokePattern);
}
- pointsOnLine = this._drawSeries(ctx, iter, strokeWidth, pointSize, drawPoints, drawGapPoints, stepPlot, strategy);
- this._drawPointsOnLine(ctx, pointsOnLine, drawPointCallback, setName, color, pointSize);
- ctx.restore();
-};
+ var pointsOnLine = this._drawSeries(ctx, iter, strokeWidth, pointSize, drawPoints, drawGapPoints, stepPlot, color);
+ this._drawPointsOnLine(ctx, pointsOnLine, drawPointCallback, setName, color, pointSize);
-var nonTrivialStrategy = function(renderer, ctx, color, strokeWidth, strokePattern) {
- return new function() {
- this.init = function() { };
- this.finish = function() { };
- this.startSegment = function() {
- ctx.beginPath();
- ctx.strokeStyle = color;
- ctx.lineWidth = strokeWidth;
- };
- this.endSegment = function() {
- ctx.stroke(); // should this include closePath?
- };
- this.drawLine = function(x1, y1, x2, y2) {
- renderer._dashedLine(ctx, x1, y1, x2, y2, strokePattern);
- };
- this.skipPixel = function(prevX, prevY, curX, curY) {
- // TODO(konigsberg): optimize with http://jsperf.com/math-round-vs-hack/6 ?
- return (Math.round(prevX) == Math.round(curX) &&
- Math.round(prevY) == Math.round(curY));
- };
- };
-};
+ if (stroking) {
+ ctx.uninstallPattern();
+ }
-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;
- };
- };
+ ctx.restore();
};
DygraphCanvasRenderer.prototype._drawPointsOnLine = function(ctx, pointsOnLine, drawPointCallback, setName, color, pointSize) {
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;
};
/**
* Actually draw the lines chart, including error bars.
- * TODO(danvk): split this into several smaller functions.
* @private
*/
DygraphCanvasRenderer.prototype._renderLineChart = function() {
- // TODO(danvk): use this.attr_ for many of these.
var ctx = this.elementContext;
- var fillAlpha = this.attr_('fillAlpha');
var errorBars = this.attr_("errorBars") || this.attr_("customBars");
var fillGraph = this.attr_("fillGraph");
- var stackedGraph = this.attr_("stackedGraph");
- var stepPlot = this.attr_("stepPlot");
- var points = this.layout.points;
- var pointsLength = points.length;
- var point, i, prevX, prevY, prevYs, color, setName, newYs, err_color, rgb, yscale, axis;
+ var i;
var setNames = this.layout.setNames;
var setCount = setNames.length;
// 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.
- for (i = pointsLength; i--;) {
- point = points[i];
- point.canvasx = this.area.w * point.x + this.area.x;
- point.canvasy = this.area.h * point.y + this.area.y;
+ // 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 sets = this.layout.points;
+ for (i = sets.length; i--;) {
+ var points = sets[i];
+ for (var j = points.length; j--;) {
+ var point = points[j];
+ point.canvasx = this.area.w * point.x + this.area.x;
+ point.canvasy = this.area.h * point.y + this.area.y;
+ }
}
- // create paths
+ // Draw any "fills", i.e. error bars or the filled area under a series.
+ // These must all be drawn before any lines, so that the main lines of a
+ // series are drawn on top.
if (errorBars) {
- ctx.save();
if (fillGraph) {
this.dygraph_.warn("Can't use fillGraph option with error bars");
}
- for (i = 0; i < setCount; i++) {
- setName = setNames[i];
- axis = this.dygraph_.axisPropertiesForSeries(setName);
- color = this.colors[setName];
-
- var firstIndexInSet = this.layout.setPointsOffsets[i];
- var setLength = this.layout.setPointsLengths[i];
-
- var iter = Dygraph.createIterator(points, firstIndexInSet, setLength,
- DygraphCanvasRenderer._getIteratorPredicate(this.attr_("connectSeparatedPoints")));
-
- // setup graphics context
- prevX = NaN;
- prevY = NaN;
- prevYs = [-1, -1];
- yscale = axis.yscale;
- // should be same color as the lines but only 15% opaque.
- rgb = new RGBColor(color);
- err_color = 'rgba(' + rgb.r + ',' + rgb.g + ',' + rgb.b + ',' +
- fillAlpha + ')';
- ctx.fillStyle = err_color;
- ctx.beginPath();
- while (iter.hasNext()) {
- point = iter.next();
- if (point.name == setName) { // TODO(klausw): this is always true
- 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) {
- 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();
- }
- prevYs = newYs;
- prevX = point.canvasx;
- }
- }
- ctx.fill();
- }
+ ctx.save();
+ this.drawErrorBars_(points);
ctx.restore();
} else if (fillGraph) {
ctx.save();
- var baseline = {}; // for stacked graphs: baseline for filling
- var currBaseline;
-
- // process sets in reverse order (needed for stacked graphs)
- for (i = setCount - 1; i >= 0; i--) {
- setName = setNames[i];
- color = this.colors[setName];
- axis = this.dygraph_.axisPropertiesForSeries(setName);
- var axisY = 1.0 + axis.minyval * axis.yscale;
- if (axisY < 0.0) axisY = 0.0;
- else if (axisY > 1.0) axisY = 1.0;
- axisY = this.area.h * axisY + this.area.y;
- var firstIndexInSet = this.layout.setPointsOffsets[i];
- var setLength = this.layout.setPointsLengths[i];
-
- var iter = Dygraph.createIterator(points, firstIndexInSet, setLength,
- DygraphCanvasRenderer._getIteratorPredicate(this.attr_("connectSeparatedPoints")));
-
- // setup graphics context
- prevX = NaN;
- prevYs = [-1, -1];
- yscale = axis.yscale;
- // should be same color as the lines but only 15% opaque.
- rgb = new RGBColor(color);
- err_color = 'rgba(' + rgb.r + ',' + rgb.g + ',' + rgb.b + ',' +
- fillAlpha + ')';
- ctx.fillStyle = err_color;
- ctx.beginPath();
- while(iter.hasNext()) {
- 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;
- } else {
- if(stepPlot) {
- lastY = currBaseline[0];
- } else {
- lastY = currBaseline;
- }
- }
- 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] ];
- }
- } else {
- baseline[point.canvasx] = point.canvasy;
- }
-
- } 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[1]);
- }
- } else {
- ctx.lineTo(point.canvasx, newYs[0]);
- ctx.lineTo(point.canvasx, newYs[1]);
- }
-
- ctx.lineTo(prevX, prevYs[1]);
- ctx.closePath();
- }
- prevYs = newYs;
- prevX = point.canvasx;
- }
- }
- ctx.fill();
- }
+ this.drawFillBars_(points);
ctx.restore();
}
};
/**
- * This does dashed lines onto a canvas for a given pattern. You must call
- * ctx.stroke() after to actually draw it, much line ctx.lineTo(). It remembers
- * the state of the line in regards to where we left off on drawing the pattern.
- * You can draw a dashed line in several function calls and the pattern will be
- * continous as long as you didn't call this function with a different pattern
- * in between.
- * @param ctx The canvas 2d context to draw on.
- * @param x The start of the line's x coordinate.
- * @param y The start of the line's y coordinate.
- * @param x2 The end of the line's x coordinate.
- * @param y2 The end of the line's y coordinate.
- * @param pattern The dash pattern to draw, an array of integers where even
- * index is drawn and odd index is not drawn (Ex. [10, 2, 5, 2], 10 is drawn 5
- * is drawn, 2 is the space between.). A null pattern, array of length one, or
- * empty array will do just a solid line.
+ * Draws the shaded error bars/confidence intervals for each series.
+ * This happens before the center lines are drawn, since the center lines
+ * need to be drawn on top of the error bars for all series.
+ *
* @private
*/
-DygraphCanvasRenderer.prototype._dashedLine = function(ctx, x, y, x2, y2, pattern) {
- // Original version http://stackoverflow.com/questions/4576724/dotted-stroke-in-canvas
- // Modified by Russell Valentine to keep line history and continue the pattern
- // where it left off.
- var dx, dy, len, rot, patternIndex, segment;
-
- // If we don't have a pattern or it is an empty array or of size one just
- // do a solid line.
- if (!pattern || pattern.length <= 1) {
- ctx.moveTo(x, y);
- ctx.lineTo(x2, y2);
- return;
- }
+DygraphCanvasRenderer.prototype.drawErrorBars_ = function(points) {
+ var ctx = this.elementContext;
+ var setNames = this.layout.setNames;
+ var setCount = setNames.length;
+ var fillAlpha = this.attr_('fillAlpha');
+ var stepPlot = this.attr_('stepPlot');
+
+ var newYs;
+
+ for (var i = 0; i < setCount; i++) {
+ var setName = setNames[i];
+ var axis = this.dygraph_.axisPropertiesForSeries(setName);
+ var color = this.colors[setName];
+
+ var firstIndexInSet = this.layout.setPointsOffsets[i];
+ var setLength = this.layout.setPointsLengths[i];
+
+ var iter = Dygraph.createIterator(points, firstIndexInSet, setLength,
+ DygraphCanvasRenderer._getIteratorPredicate(
+ this.attr_("connectSeparatedPoints")));
+
+ // setup graphics context
+ var prevX = NaN;
+ var prevY = NaN;
+ var prevYs = [-1, -1];
+ var yscale = axis.yscale;
+ // should be same color as the lines but only 15% opaque.
+ var rgb = new RGBColor(color);
+ var err_color =
+ 'rgba(' + rgb.r + ',' + rgb.g + ',' + rgb.b + ',' + fillAlpha + ')';
+ ctx.fillStyle = err_color;
+ ctx.beginPath();
+ while (iter.hasNext) {
+ var point = iter.next();
+ if (!Dygraph.isOK(point.y)) {
+ prevX = NaN;
+ continue;
+ }
- // If we have a different dash pattern than the last time this was called we
- // reset our dash history and start the pattern from the begging
- // regardless of state of the last pattern.
- if (!Dygraph.compareArrays(pattern, this._dashedLineToHistoryPattern)) {
- this._dashedLineToHistoryPattern = pattern;
- this._dashedLineToHistory = [0, 0];
+ 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) {
+ 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();
+ }
+ prevYs = newYs;
+ prevX = point.canvasx;
+ }
+ ctx.fill();
}
- ctx.save();
+};
- // Calculate transformation parameters
- dx = (x2-x);
- dy = (y2-y);
- len = Math.sqrt(dx*dx + dy*dy);
- rot = Math.atan2(dy, dx);
-
- // Set transformation
- ctx.translate(x, y);
- ctx.moveTo(0, 0);
- ctx.rotate(rot);
-
- // Set last pattern index we used for this pattern.
- patternIndex = this._dashedLineToHistory[0];
- x = 0;
- while (len > x) {
- // Get the length of the pattern segment we are dealing with.
- segment = pattern[patternIndex];
- // If our last draw didn't complete the pattern segment all the way we
- // will try to finish it. Otherwise we will try to do the whole segment.
- if (this._dashedLineToHistory[1]) {
- x += this._dashedLineToHistory[1];
- } else {
- x += segment;
- }
- if (x > len) {
- // We were unable to complete this pattern index all the way, keep
- // where we are the history so our next draw continues where we left off
- // in the pattern.
- this._dashedLineToHistory = [patternIndex, x-len];
- x = len;
- } else {
- // We completed this patternIndex, we put in the history that we are on
- // the beginning of the next segment.
- this._dashedLineToHistory = [(patternIndex+1)%pattern.length, 0];
- }
+/**
+ * Draws the shaded regions when "fillGraph" is set. Not to be confused with
+ * error bars.
+ *
+ * @private
+ */
+DygraphCanvasRenderer.prototype.drawFillBars_ = function(points) {
+ var ctx = this.elementContext;
+ var setNames = this.layout.setNames;
+ var setCount = setNames.length;
+ var fillAlpha = this.attr_('fillAlpha');
+ var stepPlot = this.attr_('stepPlot');
+ var stackedGraph = this.attr_("stackedGraph");
- // We do a line on a even pattern index and just move on a odd pattern index.
- // The move is the empty space in the dash.
- if(patternIndex % 2 === 0) {
- ctx.lineTo(x, 0);
- } else {
- ctx.moveTo(x, 0);
+ var baseline = {}; // for stacked graphs: baseline for filling
+ var currBaseline;
+
+ // process sets in reverse order (needed for stacked graphs)
+ for (var setIdx = setCount - 1; setIdx >= 0; setIdx--) {
+ var setName = setNames[setIdx];
+ var color = this.colors[setName];
+ var axis = this.dygraph_.axisPropertiesForSeries(setName);
+ var axisY = 1.0 + axis.minyval * axis.yscale;
+ if (axisY < 0.0) axisY = 0.0;
+ else if (axisY > 1.0) axisY = 1.0;
+ axisY = this.area.h * axisY + this.area.y;
+
+ var points = this.layout.points[setIdx];
+ var iter = Dygraph.createIterator(points, 0, points.length,
+ DygraphCanvasRenderer._getIteratorPredicate(
+ this.attr_("connectSeparatedPoints")));
+
+ // setup graphics context
+ var prevX = NaN;
+ var prevYs = [-1, -1];
+ var newYs;
+ var yscale = axis.yscale;
+ // should be same color as the lines but only 15% opaque.
+ var rgb = new RGBColor(color);
+ var err_color =
+ 'rgba(' + rgb.r + ',' + rgb.g + ',' + rgb.b + ',' + fillAlpha + ')';
+ ctx.fillStyle = err_color;
+ ctx.beginPath();
+ while(iter.hasNext) {
+ var point = iter.next();
+ 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 {
+ lastY = currBaseline;
+ }
+ }
+ 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] ];
+ }
+ } else {
+ baseline[point.canvasx] = point.canvasy;
+ }
+
+ } 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[1]);
+ }
+ } else {
+ ctx.lineTo(point.canvasx, newYs[0]);
+ ctx.lineTo(point.canvasx, newYs[1]);
+ }
+
+ ctx.lineTo(prevX, prevYs[1]);
+ ctx.closePath();
+ }
+ prevYs = newYs;
+ prevX = point.canvasx;
}
- // If we are not done, next loop process the next pattern segment, or the
- // first segment again if we are at the end of the pattern.
- patternIndex = (patternIndex+1) % pattern.length;
+ ctx.fill();
}
- ctx.restore();
};