X-Git-Url: https://adrianiainlam.tk/git/?a=blobdiff_plain;f=dygraph-canvas.js;h=825a4c29c59385c7e0cb1ad238601cb262613855;hb=f34f95d1f2fe3adcc0bbe578368740afba513d9f;hp=c3bf8bceac4efcf1737aab8a77a367786218041a;hpb=fb63bf1b8b64385b98681417c4ee02b0fb0fd130;p=dygraphs.git diff --git a/dygraph-canvas.js b/dygraph-canvas.js index c3bf8bc..825a4c2 100644 --- a/dygraph-canvas.js +++ b/dygraph-canvas.js @@ -235,44 +235,39 @@ DygraphCanvasRenderer._getIteratorPredicate = function(connectSeparatedPoints) { 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 iter = Dygraph.createIterator(points, firstIndexInSet, setLength, + var points = this.layout.points[setIdx]; + var iter = Dygraph.createIterator(points, 0, points.length, 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) { @@ -282,29 +277,6 @@ DygraphCanvasRenderer.prototype._drawStyledLine = function( 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]; @@ -317,7 +289,7 @@ DygraphCanvasRenderer.prototype._drawPointsOnLine = function(ctx, pointsOnLine, DygraphCanvasRenderer.prototype._drawSeries = function( ctx, iter, strokeWidth, pointSize, drawPoints, drawGapPoints, - stepPlot, strategy) { + stepPlot, color) { var prevCanvasX = null; var prevCanvasY = null; @@ -327,44 +299,63 @@ DygraphCanvasRenderer.prototype._drawSeries = function( 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) { @@ -375,7 +366,7 @@ DygraphCanvasRenderer.prototype._drawSeries = function( } first = false; } - strategy.finish(); + ctx.stroke(); return pointsOnLine; }; @@ -428,11 +419,20 @@ DygraphCanvasRenderer.prototype._renderLineChart = function() { // 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. - var points = this.layout.points; - for (i = points.length; i--;) { - var 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; + } } // Draw any "fills", i.e. error bars or the filled area under a series. @@ -474,15 +474,13 @@ DygraphCanvasRenderer.prototype.drawErrorBars_ = function(points) { var newYs; - for (var i = 0; i < setCount; i++) { - var setName = setNames[i]; + for (var setIdx = 0; setIdx < setCount; setIdx++) { + var setName = setNames[setIdx]; 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, + var points = this.layout.points[setIdx]; + var iter = Dygraph.createIterator(points, 0, points.length, DygraphCanvasRenderer._getIteratorPredicate( this.attr_("connectSeparatedPoints"))); @@ -499,39 +497,36 @@ DygraphCanvasRenderer.prototype.drawErrorBars_ = function(points) { 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(); } @@ -555,18 +550,17 @@ DygraphCanvasRenderer.prototype.drawFillBars_ = function(points) { var currBaseline; // process sets in reverse order (needed for stacked graphs) - for (var i = setCount - 1; i >= 0; i--) { - var setName = setNames[i]; + 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 firstIndexInSet = this.layout.setPointsOffsets[i]; - var setLength = this.layout.setPointsLengths[i]; - var iter = Dygraph.createIterator(points, firstIndexInSet, setLength, + var points = this.layout.points[setIdx]; + var iter = Dygraph.createIterator(points, 0, points.length, DygraphCanvasRenderer._getIteratorPredicate( this.attr_("connectSeparatedPoints"))); @@ -583,62 +577,60 @@ DygraphCanvasRenderer.prototype.drawFillBars_ = function(points) { 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(); }