X-Git-Url: https://adrianiainlam.tk/git/?a=blobdiff_plain;ds=sidebyside;f=dygraph-canvas.js;h=720ab80b0f674bfe8ae53562492350c4ee1dcaab;hb=c06d8ec4ed15aeaf6bc2be27542972d73b9c0680;hp=6cbe102709aadcf669da51761866dbcacdd60b4b;hpb=9f6db80e6619a74aad92c42073a3c49d3dd63cb6;p=dygraphs.git diff --git a/dygraph-canvas.js b/dygraph-canvas.js index 6cbe102..720ab80 100644 --- a/dygraph-canvas.js +++ b/dygraph-canvas.js @@ -235,10 +235,12 @@ 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; +}; /** * @@ -257,69 +259,25 @@ 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 pointsOnLine; - var strategy; - if (!strokePattern || strokePattern.length <= 1) { - strategy = trivialStrategy(ctx, color, strokeWidth); - } else { - strategy = nonTrivialStrategy(this, ctx, color, strokeWidth, strokePattern); + var stroking = strokePattern && (strokePattern.length >= 2); + + 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) { @@ -334,7 +292,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; @@ -344,44 +302,56 @@ 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; + + for (var i = iter.start_; i < iter.end_; i++) { + point = iter.array_[i]; + if (iter.predicate_) { + while (i < iter.end_ && !iter.predicate_(iter.array_, i)) { + i++; + } + if (i == iter.end_) break; + point = iter.array_[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(); + ctx.moveTo(prevCanvasX, prevCanvasY); + ctx.lineTo(point.canvasx, point.canvasy); } } if (drawPoints || isIsolated) { @@ -392,7 +362,7 @@ DygraphCanvasRenderer.prototype._drawSeries = function( } first = false; } - strategy.finish(); + ctx.stroke(); return pointsOnLine; }; @@ -516,39 +486,37 @@ 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 + // 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(); } @@ -600,154 +568,61 @@ 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(); } }; - -/** - * 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. - * @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; - } - - // 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]; - } - 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]; - } - - // 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); - } - // 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.restore(); -};