X-Git-Url: https://adrianiainlam.tk/git/?a=blobdiff_plain;f=dygraph-layout.js;h=4b600f773b8893a617b081a10245493d2b0c8fca;hb=e2531db7bf7914193b9cc2a562962335cb1ee384;hp=c6d829726557b7d4e18a8ba704f7a3b2548209ed;hpb=152b74a7864b92596a22a532ddfc535918049781;p=dygraphs.git diff --git a/dygraph-layout.js b/dygraph-layout.js index c6d8297..4b600f7 100644 --- a/dygraph-layout.js +++ b/dygraph-layout.js @@ -35,6 +35,7 @@ var DygraphLayout = function(dygraph) { 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. @@ -51,21 +52,25 @@ DygraphLayout.prototype.addDataset = function(setname, set_xy) { this.setNames.push(setname); }; +/** + * Returns the box which the chart should be drawn in. This is the canvas's + * box, less space needed for the axis and chart labels. + * + * @return {{x: number, y: number, w: number, h: number}} + */ DygraphLayout.prototype.getPlotArea = function() { - return this.computePlotArea_(); + return this.area_; }; // Compute the box which the chart should be drawn in. This is the canvas's // box, less space needed for axis and chart labels. -DygraphLayout.prototype.computePlotArea_ = function() { +// NOTE: This should only be called by Dygraph.predraw_(). +DygraphLayout.prototype.computePlotArea = function() { var area = { // TODO(danvk): per-axis setting. x: 0, y: 0 }; - if (this.attr_('drawYAxis')) { - area.x = this.attr_('yAxisLabelWidth') + 2 * this.attr_('axisTickSize'); - } area.w = this.dygraph_.width_ - area.x - this.attr_('rightGap'); area.h = this.dygraph_.height_; @@ -121,29 +126,7 @@ DygraphLayout.prototype.computePlotArea_ = function() { }; this.dygraph_.cascadeEvents_('layout', e); - if (this.attr_('drawXAxis')) { - if (this.attr_('xAxisHeight')) { - area.h -= this.attr_('xAxisHeight'); - } else { - area.h -= this.attr_('axisLabelFontSize') + 2 * this.attr_('axisTickSize'); - } - } - - // Shrink the drawing area to accomodate additional y-axes. - if (this.dygraph_.numAxes() == 2) { - // TODO(danvk): per-axis setting. - area.w -= (this.attr_('yAxisLabelWidth') + 2 * this.attr_('axisTickSize')); - } else if (this.dygraph_.numAxes() > 2) { - this.dygraph_.error("Only two y-axes are supported at this time. (Trying " + - "to use " + this.dygraph_.numAxes() + ")"); - } - - // Add space for range selector, if needed. - if (this.attr_('showRangeSelector')) { - area.h -= this.attr_('rangeSelectorHeight') + 4; - } - - return area; + this.area_ = area; }; DygraphLayout.prototype.setAnnotations = function(ann) { @@ -153,7 +136,7 @@ DygraphLayout.prototype.setAnnotations = function(ann) { var parse = this.attr_('xValueParser') || function(x) { return x; }; for (var i = 0; i < ann.length; i++) { var a = {}; - if (!ann[i].xval && !ann[i].x) { + if (!ann[i].xval && ann[i].x === undefined) { this.dygraph_.error("Annotations must have an 'x' property"); return; } @@ -191,24 +174,11 @@ DygraphLayout.prototype.evaluate = function() { }; DygraphLayout.prototype._evaluateLimits = function() { - this.minxval = this.maxxval = null; - if (this.dateWindow_) { - this.minxval = this.dateWindow_[0]; - this.maxxval = this.dateWindow_[1]; - } else { - for (var setIdx = 0; setIdx < this.datasets.length; ++setIdx) { - var series = this.datasets[setIdx]; - if (series.length > 1) { - var x1 = series[0][0]; - if (!this.minxval || x1 < this.minxval) this.minxval = x1; - - var x2 = series[series.length - 1][0]; - if (!this.maxxval || x2 > this.maxxval) this.maxxval = x2; - } - } - } - this.xrange = this.maxxval - this.minxval; - this.xscale = (this.xrange !== 0 ? 1/this.xrange : 1.0); + var xlimits = this.dygraph_.xAxisRange(); + this.minxval = xlimits[0]; + this.maxxval = xlimits[1]; + var xrange = xlimits[1] - xlimits[0]; + this.xscale = (xrange !== 0 ? 1 / xrange : 1.0); for (var i = 0; i < this.yAxes_.length; i++) { var axis = this.yAxes_[i]; @@ -229,8 +199,8 @@ DygraphLayout.prototype._evaluateLimits = function() { } }; -DygraphLayout._calcYNormal = function(axis, value) { - if (axis.logscale) { +DygraphLayout._calcYNormal = function(axis, value, logscale) { + if (logscale) { return 1.0 - ((Dygraph.log10(value) - Dygraph.log10(axis.minyval)) * axis.ylogscale); } else { return 1.0 - ((value - axis.minyval) * axis.yscale); @@ -238,34 +208,26 @@ DygraphLayout._calcYNormal = function(axis, value) { }; 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); + var boundaryIdStart = this.dygraph_.getLeftBoundary_(); + 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); + // TODO (konigsberg): use optionsForAxis instead. + var logscale = this.dygraph_.attributes_.getForSeries("logscale", 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]; @@ -275,22 +237,24 @@ DygraphLayout.prototype._evaluateLineCharts = function() { // Range from 0-1 where 0 represents left and 1 represents right. var xNormal = (xValue - this.minxval) * this.xscale; // Range from 0-1 where 0 represents top and 1 represents bottom - var yNormal = DygraphLayout._calcYNormal(axis, yValue); + var yNormal = DygraphLayout._calcYNormal(axis, yValue, logscale); + // 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? + idx: j + boundaryIdStart }; - i++; } - this.setPointsLengths.push(i - this.setPointsOffsets[setIdx]); + + this.points[setIdx] = seriesPoints; } }; @@ -306,7 +270,7 @@ DygraphLayout.parseFloat_ = function(val) { // Assume it's a number or NaN. If it's something else, I'll be shocked. return val; -} +}; DygraphLayout.prototype._evaluateLineTicks = function() { var i, tick, label, pos; @@ -346,24 +310,28 @@ DygraphLayout.prototype.evaluateWithError = function() { // 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 axis = this.dygraph_.axisPropertiesForSeries(setName); + // TODO (konigsberg): use optionsForAxis instead. + var logscale = this.dygraph_.attributes_.getForSeries("logscale", setName); + for (j = 0; j < dataset.length; j++, i++) { var item = dataset[j]; 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, logscale); + points[j].y_bottom = DygraphLayout._calcYNormal(axis, yv_plus, logscale); } } } @@ -387,12 +355,15 @@ DygraphLayout.prototype._evaluateAnnotations = function() { } // 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); + } } } }; @@ -415,10 +386,10 @@ DygraphLayout.prototype.removeAllDatasets = function() { * 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) { + if (!Dygraph.isValidPoint(point)) { return point; } @@ -434,10 +405,14 @@ DygraphLayout.prototype.unstackPointAtIndex = function(idx) { // 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; - break; + // We need to iterate over setIdx just in case some series have invalid values + // at current row + for(setIdx++; setIdx < this.points.length; setIdx++) { + var nextPoint = this.points[setIdx][row]; + if (nextPoint.xval == point.xval && // should always be true? + Dygraph.isValidPoint(nextPoint)) { + unstackedPoint.yval -= nextPoint.yval; + break; // stop at first valid point } }