* @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);
- 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")));
// 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];
- point.canvasx = this.area.w * point.x + this.area.x;
- point.canvasy = this.area.h * point.y + this.area.y;
+ 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.
this.setNames = [];
this.annotations = [];
this.yAxes_ = null;
+ this.points = null;
// TODO(danvk): it's odd that xTicks_ and yTicks_ are inputs, but xticks and
// yticks are outputs. Clean this up.
};
DygraphLayout.prototype._evaluateLineCharts = function() {
- // An array to keep track of how many points will be drawn for each set.
- // This will allow for the canvas renderer to not have to check every point
- // for every data set since the points are added in order of the sets in
- // datasets.
- this.setPointsLengths = [];
- this.setPointsOffsets = [];
-
var connectSeparated = this.attr_('connectSeparatedPoints');
+
+ // series index -> point index in series -> |point| structure
+ this.points = new Array(this.datasets.length);
+
// TODO(bhs): these loops are a hot-spot for high-point-count charts. In fact,
// on chrome+linux, they are 6 times more expensive than iterating through the
// points and drawing the lines. The brunt of the cost comes from allocating
// the |point| structures.
- var i = 0;
- var setIdx;
-
- // Preallocating the size of points reduces reallocations, and therefore,
- // calls to collect garbage.
- var totalPoints = 0;
- for (setIdx = 0; setIdx < this.datasets.length; ++setIdx) {
- totalPoints += this.datasets[setIdx].length;
- }
- this.points = new Array(totalPoints);
-
- for (setIdx = 0; setIdx < this.datasets.length; ++setIdx) {
- this.setPointsOffsets.push(i);
+ for (var setIdx = 0; setIdx < this.datasets.length; setIdx++) {
var dataset = this.datasets[setIdx];
var setName = this.setNames[setIdx];
var axis = this.dygraph_.axisPropertiesForSeries(setName);
+ // Preallocating the size of points reduces reallocations, and therefore,
+ // calls to collect garbage.
+ var seriesPoints = new Array(dataset.length);
+
for (var j = 0; j < dataset.length; j++) {
var item = dataset[j];
var xValue = DygraphLayout.parseFloat_(item[0]);
// Range from 0-1 where 0 represents top and 1 represents bottom
var yNormal = DygraphLayout._calcYNormal(axis, yValue);
+ // TODO(danvk): drop the point in this case, don't null it.
+ // The nulls create complexity in DygraphCanvasRenderer._drawSeries.
if (connectSeparated && item[1] === null) {
yValue = null;
}
- this.points[i] = {
- // TODO(danvk): here
+ seriesPoints[j] = {
x: xNormal,
y: yNormal,
xval: xValue,
yval: yValue,
- name: setName
+ name: setName // TODO(danvk): is this really necessary?
};
- i++;
}
- this.setPointsLengths.push(i - this.setPointsOffsets[setIdx]);
+
+ this.points[setIdx] = seriesPoints;
}
};
// Copy over the error terms
var i = 0; // index in this.points
for (var setIdx = 0; setIdx < this.datasets.length; ++setIdx) {
+ var points = this.points[setIdx];
var j = 0;
var dataset = this.datasets[setIdx];
var setName = this.setNames[setIdx];
var xv = DygraphLayout.parseFloat_(item[0]);
var yv = DygraphLayout.parseFloat_(item[1]);
- if (xv == this.points[i].xval &&
- yv == this.points[i].yval) {
+ if (xv == points[j].xval &&
+ yv == points[j].yval) {
var errorMinus = DygraphLayout.parseFloat_(item[2]);
var errorPlus = DygraphLayout.parseFloat_(item[3]);
var yv_minus = yv - errorMinus;
var yv_plus = yv + errorPlus;
- this.points[i].y_top = DygraphLayout._calcYNormal(axis, yv_minus);
- this.points[i].y_bottom = DygraphLayout._calcYNormal(axis, yv_plus);
+ points[j].y_top = DygraphLayout._calcYNormal(axis, yv_minus);
+ points[j].y_bottom = DygraphLayout._calcYNormal(axis, yv_plus);
}
}
}
}
// TODO(antrob): loop through annotations not points.
- for (i = 0; i < this.points.length; i++) {
- var p = this.points[i];
- var k = p.xval + "," + p.name;
- if (k in annotations) {
- p.annotation = annotations[k];
- this.annotated_points.push(p);
+ for (var setIdx = 0; setIdx < this.points.length; setIdx++) {
+ var points = this.points[setIdx];
+ for (i = 0; i < points.length; i++) {
+ var p = points[i];
+ var k = p.xval + "," + p.name;
+ if (k in annotations) {
+ p.annotation = annotations[k];
+ this.annotated_points.push(p);
+ }
}
}
};
* Return a copy of the point at the indicated index, with its yval unstacked.
* @param int index of point in layout_.points
*/
-DygraphLayout.prototype.unstackPointAtIndex = function(idx) {
- var point = this.points[idx];
+DygraphLayout.prototype.unstackPointAtIndex = function(setIdx, row) {
+ var point = this.points[setIdx][row];
// If the point is missing, no unstacking is necessary
if (!point.yval) {
return point;
// The unstacked yval is equal to the current yval minus the yval of the
// next point at the same xval.
- for (var i = idx+1; i < this.points.length; i++) {
- if ((this.points[i].xval == point.xval) && this.points[i].yval) {
- unstackedPoint.yval -= this.points[i].yval;
+ var points = this.points[setIdx];
+ for (var i = row + 1; i < points.length; i++) {
+ if ((points[i].xval == point.xval) && points[i].yval) {
+ unstackedPoint.yval -= points[i].yval;
break;
}
}
*/
Dygraph.prototype.findClosestRow = function(domX) {
var minDistX = Infinity;
- var idx = -1;
- var points = this.layout_.points;
- var l = points.length;
- for (var i = 0; i < l; i++) {
- var point = points[i];
- if (!Dygraph.isValidPoint(point, true)) continue;
- var dist = Math.abs(point.canvasx - domX);
- if (dist < minDistX) {
- minDistX = dist;
- idx = i;
+ var pointIdx = -1, setIdx = -1;
+ var sets = this.layout_.points;
+ for (var i = 0; i < sets.length; i++) {
+ var points = sets[i];
+ var len = points.length;
+ for (var j = 0; j < len; j++) {
+ var point = points[j];
+ if (!Dygraph.isValidPoint(point, true)) continue;
+ var dist = Math.abs(point.canvasx - domX);
+ if (dist < minDistX) {
+ minDistX = dist;
+ setIdx = i;
+ pointIdx = j;
+ }
}
}
- return this.idxToRow_(idx);
+
+ // TODO(danvk): remove this function; it's trivial and has only one use.
+ return this.idxToRow_(setIdx, pointIdx);
};
/**
Dygraph.prototype.findClosestPoint = function(domX, domY) {
var minDist = Infinity;
var idx = -1;
- var points = this.layout_.points;
var dist, dx, dy, point, closestPoint, closestSeries;
for (var setIdx = 0; setIdx < this.layout_.datasets.length; ++setIdx) {
- var first = this.layout_.setPointsOffsets[setIdx];
- var len = this.layout_.setPointsLengths[setIdx];
- for (var i = 0; i < len; ++i) {
- var point = points[first + i];
+ var points = this.layout_.points[setIdx];
+ for (var i = 0; i < points.length; ++i) {
+ var point = points[i];
if (!Dygraph.isValidPoint(point)) continue;
dx = point.canvasx - domX;
dy = point.canvasy - domY;
var row = this.findClosestRow(domX);
var boundary = this.getLeftBoundary_();
var rowIdx = row - boundary;
- var points = this.layout_.points;
+ var sets = this.layout_.points;
var closestPoint, closestSeries;
for (var setIdx = 0; setIdx < this.layout_.datasets.length; ++setIdx) {
- var first = this.layout_.setPointsOffsets[setIdx];
- var len = this.layout_.setPointsLengths[setIdx];
- if (rowIdx >= len) continue;
- var p1 = points[first + rowIdx];
+ var points = this.layout_.points[setIdx];
+ if (rowIdx >= points.length) continue;
+ var p1 = points[rowIdx];
if (!Dygraph.isValidPoint(p1)) continue;
var py = p1.canvasy;
- if (domX > p1.canvasx && rowIdx + 1 < len) {
+ if (domX > p1.canvasx && rowIdx + 1 < points.length) {
// interpolate series Y value using next point
- var p2 = points[first + rowIdx + 1];
+ var p2 = points[rowIdx + 1];
if (Dygraph.isValidPoint(p2)) {
var dx = p2.canvasx - p1.canvasx;
if (dx > 0) {
}
} else if (domX < p1.canvasx && rowIdx > 0) {
// interpolate series Y value using previous point
- var p0 = points[first + rowIdx - 1];
+ var p0 = points[rowIdx - 1];
if (Dygraph.isValidPoint(p0)) {
var dx = p1.canvasx - p0.canvasx;
if (dx > 0) {
Dygraph.prototype.mouseMove_ = function(event) {
// This prevents JS errors when mousing over the canvas before data loads.
var points = this.layout_.points;
- if (points === undefined) return;
+ if (points === undefined || points === null) return;
var canvasCoords = this.eventToDomCoords(event);
var canvasx = canvasCoords[0];
* @return int row number, or -1 if none could be found.
* @private
*/
-Dygraph.prototype.idxToRow_ = function(idx) {
- if (idx < 0) return -1;
+Dygraph.prototype.idxToRow_ = function(setIdx, rowIdx) {
+ if (rowIdx < 0) return -1;
var boundary = this.getLeftBoundary_();
- for (var setIdx = 0; setIdx < this.layout_.datasets.length; ++setIdx) {
- var set = this.layout_.datasets[setIdx];
- if (idx < set.length) {
- return boundary + idx;
- }
- idx -= set.length;
- }
- return -1;
+ return boundary + rowIdx;
+ // for (var setIdx = 0; setIdx < this.layout_.datasets.length; ++setIdx) {
+ // var set = this.layout_.datasets[setIdx];
+ // if (idx < set.length) {
+ // return boundary + idx;
+ // }
+ // idx -= set.length;
+ // }
+ // return -1;
};
Dygraph.prototype.animateSelection_ = function(direction) {
Dygraph.prototype.setSelection = function(row, opt_seriesName) {
// Extract the points we've selected
this.selPoints_ = [];
- var pos = 0;
if (row !== false) {
row -= this.getLeftBoundary_();
for (var setIdx = 0; setIdx < this.layout_.datasets.length; ++setIdx) {
var set = this.layout_.datasets[setIdx];
if (row < set.length) {
- var point = this.layout_.points[pos+row];
+ var point = this.layout_.points[setIdx][row];
if (this.attr_("stackedGraph")) {
- point = this.layout_.unstackPointAtIndex(pos+row);
+ point = this.layout_.unstackPointAtIndex(setIdx, row);
}
if (!(point.yval === null)) this.selPoints_.push(point);
}
- pos += set.length;
}
} else {
if (this.lastRow_ >= 0) changed = true;
return -1;
}
- for (var row=0; row<this.layout_.points.length; row++ ) {
- if (this.layout_.points[row].x == this.selPoints_[0].x) {
- return row + this.getLeftBoundary_();
+ for (var setIdx = 0; setIdx < this.layout_.points.length; setIdx++) {
+ var points = this.layout_.points[setIdx];
+ for (var row = 0; row < points.length; row++) {
+ if (points[row].x == this.selPoints_[0].x) {
+ return row + this.getLeftBoundary_();
+ }
}
}
return -1;