this.setPointsLengths = [];
this.setPointsOffsets = [];
+ var connectSeparated = this.attr_('connectSeparatedPoints');
for (var setIdx = 0; setIdx < this.datasets.length; ++setIdx) {
var dataset = this.datasets[setIdx];
var setName = this.setNames[setIdx];
yval: yValue,
name: setName
};
+ if (connectSeparated && item[1] === null) {
+ point.yval = null;
+ }
this.points.push(point);
setPointsLength += 1;
}
ctx.closePath();
ctx.stroke();
}
+ ctx.restore();
}
if (this.attr_('drawXGrid')) {
ctx.closePath();
ctx.stroke();
}
+ ctx.restore();
}
// Do the ordinary rendering, as before
var points = this.layout.annotated_points;
for (var i = 0; i < points.length; i++) {
var p = points[i];
- if (p.canvasx < this.area.x || p.canvasx > this.area.x + this.area.w) {
+ if (p.canvasx < this.area.x || p.canvasx > this.area.x + this.area.w ||
+ p.canvasy < this.area.y || p.canvasy > this.area.y + this.area.h) {
continue;
}
}
};
+DygraphCanvasRenderer.makeNextPointStep_ = function(connect, points, end) {
+ if (connect) {
+ return function(j) {
+ while (++j < end) {
+ if (!(points[j].yval === null)) break;
+ }
+ return j;
+ }
+ } else {
+ return function(j) { return j + 1 };
+ }
+};
+
+DygraphCanvasRenderer.prototype._drawStyledLine = function(
+ ctx, i, setName, color, strokeWidth, strokePattern, drawPoints,
+ drawPointCallback, pointSize) {
+ var isNullOrNaN = function(x) {
+ return (x === null || isNaN(x));
+ };
+
+ var stepPlot = this.attr_("stepPlot");
+ var firstIndexInSet = this.layout.setPointsOffsets[i];
+ var setLength = this.layout.setPointsLengths[i];
+ var afterLastIndexInSet = firstIndexInSet + setLength;
+ var points = this.layout.points;
+ var prevX = null;
+ var prevY = null;
+ var pointsOnLine = []; // Array of [canvasx, canvasy] pairs.
+ if (!Dygraph.isArrayLike(strokePattern)) {
+ strokePattern = null;
+ }
+
+ var point;
+ var next = DygraphCanvasRenderer.makeNextPointStep_(
+ this.attr_('connectSeparatedPoints'), points, afterLastIndexInSet);
+ ctx.save();
+ for (var j = firstIndexInSet; j < afterLastIndexInSet; j = next(j)) {
+ point = points[j];
+ if (isNullOrNaN(point.canvasy)) {
+ if (stepPlot && prevX !== null) {
+ // Draw a horizontal line to the start of the missing data
+ ctx.beginPath();
+ ctx.strokeStyle = color;
+ ctx.lineWidth = this.attr_('strokeWidth');
+ this._dashedLine(ctx, prevX, prevY, point.canvasx, prevY, strokePattern);
+ ctx.stroke();
+ }
+ // this will make us move to the next point, not draw a line to it.
+ prevX = prevY = null;
+ } else {
+ // A point is "isolated" if it is non-null but both the previous
+ // and next points are null.
+ var isIsolated = (!prevX && (j == points.length - 1 ||
+ isNullOrNaN(points[j+1].canvasy)));
+ if (prevX === null) {
+ prevX = point.canvasx;
+ prevY = point.canvasy;
+ } else {
+ // Skip over points that will be drawn in the same pixel.
+ if (Math.round(prevX) == Math.round(point.canvasx) &&
+ Math.round(prevY) == Math.round(point.canvasy)) {
+ continue;
+ }
+ // TODO(antrob): skip over points that lie on a line that is already
+ // going to be drawn. There is no need to have more than 2
+ // consecutive points that are collinear.
+ if (strokeWidth) {
+ ctx.beginPath();
+ ctx.strokeStyle = color;
+ ctx.lineWidth = strokeWidth;
+ if (stepPlot) {
+ this._dashedLine(ctx, prevX, prevY, point.canvasx, prevY, strokePattern);
+ prevX = point.canvasx;
+ }
+ this._dashedLine(ctx, prevX, prevY, point.canvasx, point.canvasy, strokePattern);
+ prevX = point.canvasx;
+ prevY = point.canvasy;
+ ctx.stroke();
+ }
+ }
+
+ if (drawPoints || isIsolated) {
+ pointsOnLine.push([point.canvasx, point.canvasy]);
+ }
+ }
+ }
+ for (var idx = 0; idx < pointsOnLine.length; idx++) {
+ var cb = pointsOnLine[idx];
+ ctx.save();
+ drawPointCallback(
+ this.dygraph_, setName, ctx, cb[0], cb[1], color, pointSize);
+ ctx.restore();
+ }
+ ctx.restore();
+};
+
+DygraphCanvasRenderer.prototype._drawLine = function(ctx, i) {
+ var setNames = this.layout.setNames;
+ var setName = setNames[i];
+
+ var strokeWidth = this.dygraph_.attr_("strokeWidth", setName);
+ var borderWidth = this.dygraph_.attr_("strokeBorderWidth", setName);
+ var drawPointCallback = this.dygraph_.attr_("drawPointCallback", setName) ||
+ Dygraph.Circles.DEFAULT;
+ if (borderWidth && strokeWidth) {
+ this._drawStyledLine(ctx, i, setName,
+ this.dygraph_.attr_("strokeBorderColor", setName),
+ strokeWidth + 2 * borderWidth,
+ this.dygraph_.attr_("strokePattern", setName),
+ this.dygraph_.attr_("drawPoints", setName),
+ drawPointCallback,
+ this.dygraph_.attr_("pointSize", setName));
+ }
+
+ this._drawStyledLine(ctx, i, setName,
+ this.colors[setName],
+ strokeWidth,
+ this.dygraph_.attr_("strokePattern", setName),
+ this.dygraph_.attr_("drawPoints", setName),
+ drawPointCallback,
+ this.dygraph_.attr_("pointSize", setName));
+};
/**
* Actually draw the lines chart, including error bars.
* @private
*/
DygraphCanvasRenderer.prototype._renderLineChart = function() {
- var isNullOrNaN = function(x) {
- return (x === null || isNaN(x));
- };
-
// TODO(danvk): use this.attr_ for many of these.
- var context = this.elementContext;
+ var ctx = this.elementContext;
var fillAlpha = this.attr_('fillAlpha');
var errorBars = this.attr_("errorBars") || this.attr_("customBars");
var fillGraph = this.attr_("fillGraph");
}
// create paths
- var ctx = context;
if (errorBars) {
+ ctx.save();
if (fillGraph) {
this.dygraph_.warn("Can't use fillGraph option with error bars");
}
axis = this.dygraph_.axisPropertiesForSeries(setName);
color = this.colors[setName];
+ var firstIndexInSet = this.layout.setPointsOffsets[i];
+ var setLength = this.layout.setPointsLengths[i];
+ var afterLastIndexInSet = firstIndexInSet + setLength;
+
+ var next = DygraphCanvasRenderer.makeNextPointStep_(
+ this.attr_('connectSeparatedPoints'), points,
+ afterLastIndexInSet);
+
// setup graphics context
- ctx.save();
prevX = NaN;
prevY = NaN;
prevYs = [-1, -1];
fillAlpha + ')';
ctx.fillStyle = err_color;
ctx.beginPath();
- for (j = 0; j < pointsLength; j++) {
+ for (j = firstIndexInSet; j < afterLastIndexInSet; j = next(j)) {
point = points[j];
- if (point.name == setName) {
+ if (point.name == setName) { // TODO(klausw): this is always true
if (!Dygraph.isOK(point.y)) {
prevX = NaN;
continue;
}
ctx.fill();
}
+ ctx.restore();
} else if (fillGraph) {
+ ctx.save();
var baseline = []; // for stacked graphs: baseline for filling
// process sets in reverse order (needed for stacked graphs)
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 afterLastIndexInSet = firstIndexInSet + setLength;
+
+ var next = DygraphCanvasRenderer.makeNextPointStep_(
+ this.attr_('connectSeparatedPoints'), points,
+ afterLastIndexInSet);
// setup graphics context
- ctx.save();
prevX = NaN;
prevYs = [-1, -1];
yscale = axis.yscale;
fillAlpha + ')';
ctx.fillStyle = err_color;
ctx.beginPath();
- for (j = 0; j < pointsLength; j++) {
+ for (j = firstIndexInSet; j < afterLastIndexInSet; j = next(j)) {
point = points[j];
- if (point.name == setName) {
+ if (point.name == setName) { // TODO(klausw): this is always true
if (!Dygraph.isOK(point.y)) {
prevX = NaN;
continue;
}
ctx.fill();
}
+ ctx.restore();
}
// Drawing the lines.
- var firstIndexInSet = 0;
- var afterLastIndexInSet = 0;
- var setLength = 0;
for (i = 0; i < setCount; i += 1) {
- firstIndexInSet = this.layout.setPointsOffsets[i];
- setLength = this.layout.setPointsLengths[i];
- afterLastIndexInSet = firstIndexInSet + setLength;
- setName = setNames[i];
- color = this.colors[setName];
- var strokeWidth = this.dygraph_.attr_("strokeWidth", setName);
-
- // setup graphics context
- context.save();
- var pointSize = this.dygraph_.attr_("pointSize", setName);
- prevX = null;
- prevY = null;
- var drawPoints = this.dygraph_.attr_("drawPoints", setName);
- var strokePattern = this.dygraph_.attr_("strokePattern", setName);
- if (!Dygraph.isArrayLike(strokePattern)) {
- strokePattern = null;
- }
- for (j = firstIndexInSet; j < afterLastIndexInSet; j++) {
- point = points[j];
- if (isNullOrNaN(point.canvasy)) {
- if (stepPlot && prevX !== null) {
- // Draw a horizontal line to the start of the missing data
- ctx.beginPath();
- ctx.strokeStyle = color;
- ctx.lineWidth = this.attr_('strokeWidth');
- this._dashedLine(ctx, prevX, prevY, point.canvasx, prevY, strokePattern);
- ctx.stroke();
- }
- // this will make us move to the next point, not draw a line to it.
- prevX = prevY = null;
- } else {
- // A point is "isolated" if it is non-null but both the previous
- // and next points are null.
- var isIsolated = (!prevX && (j == points.length - 1 ||
- isNullOrNaN(points[j+1].canvasy)));
- if (prevX === null) {
- prevX = point.canvasx;
- prevY = point.canvasy;
- } else {
- // Skip over points that will be drawn in the same pixel.
- if (Math.round(prevX) == Math.round(point.canvasx) &&
- Math.round(prevY) == Math.round(point.canvasy)) {
- continue;
- }
- // TODO(antrob): skip over points that lie on a line that is already
- // going to be drawn. There is no need to have more than 2
- // consecutive points that are collinear.
- if (strokeWidth) {
- ctx.beginPath();
- ctx.strokeStyle = color;
- ctx.lineWidth = strokeWidth;
- if (stepPlot) {
- this._dashedLine(ctx, prevX, prevY, point.canvasx, prevY, strokePattern);
- prevX = point.canvasx;
- }
- this._dashedLine(ctx, prevX, prevY, point.canvasx, point.canvasy, strokePattern);
- prevX = point.canvasx;
- prevY = point.canvasy;
- ctx.stroke();
- }
- }
-
- if (drawPoints || isIsolated) {
- ctx.beginPath();
- ctx.fillStyle = color;
- ctx.arc(point.canvasx, point.canvasy, pointSize,
- 0, 2 * Math.PI, false);
- ctx.fill();
- }
- }
- }
+ this._drawLine(ctx, i);
}
-
- context.restore();
};
/**
};
Dygraph.NAME = "Dygraph";
-Dygraph.VERSION = "1.2dev";
+Dygraph.VERSION = "1.2";
Dygraph.__repr__ = function() {
return "[" + this.NAME + " " + this.VERSION + "]";
};
// Default attribute values.
Dygraph.DEFAULT_ATTRS = {
highlightCircleSize: 3,
+ highlightSeriesOpts: null,
+ highlightSeriesBackgroundAlpha: 0.5,
labelsDivWidth: 250,
labelsDivStyles: {
sigFigs: null,
strokeWidth: 1.0,
+ strokeBorderWidth: 0,
+ strokeBorderColor: "white",
axisTickSize: 3,
axisLabelFontSize: 14,
this.boundaryIds_ = [];
this.setIndexByName_ = {};
+ this.datasetIndex_ = [];
// Create the containing DIV and other interactive elements
this.createInterface_();
* @return { ... } The value of the option.
*/
Dygraph.prototype.attr_ = function(name, seriesName) {
- if (this.user_attrs_ !== null && seriesName &&
- typeof(this.user_attrs_[seriesName]) != 'undefined' &&
- this.user_attrs_[seriesName] !== null &&
- typeof(this.user_attrs_[seriesName][name]) != 'undefined') {
- return this.user_attrs_[seriesName][name];
- } else if (this.user_attrs_ !== null && typeof(this.user_attrs_[name]) != 'undefined') {
- return this.user_attrs_[name];
- } else if (this.attrs_ !== null && typeof(this.attrs_[name]) != 'undefined') {
- return this.attrs_[name];
- } else {
- return null;
+
+ var sources = [];
+ sources.push(this.attrs_);
+ if (this.user_attrs_) {
+ sources.push(this.user_attrs_);
+ if (seriesName) {
+ if (this.user_attrs_.hasOwnProperty(seriesName)) {
+ sources.push(this.user_attrs_[seriesName]);
+ }
+ if (seriesName === this.highlightSet_ &&
+ this.user_attrs_.hasOwnProperty('highlightSeriesOpts')) {
+ sources.push(this.user_attrs_['highlightSeriesOpts']);
+ }
+ }
}
+
+ var ret = null;
+ for (var i = sources.length - 1; i >= 0; --i) {
+ var source = sources[i];
+ if (source.hasOwnProperty(name)) {
+ ret = source[name];
+ break;
+ }
+ }
+ return ret;
};
/**
var dygraph = this;
this.mouseMoveHandler = function(e) {
- dygraph.mouseMove_(e);
+ dygraph.mouseMove_(e);
};
Dygraph.addEvent(this.mouseEventElement_, 'mousemove', this.mouseMoveHandler);
this.mouseOutHandler = function(e) {
- dygraph.mouseOut_(e);
+ dygraph.mouseOut_(e);
};
Dygraph.addEvent(this.mouseEventElement_, 'mouseout', this.mouseOutHandler);
// remove mouse event handlers
Dygraph.removeEvent(this.mouseEventElement_, 'mouseout', this.mouseOutHandler);
Dygraph.removeEvent(this.mouseEventElement_, 'mousemove', this.mouseMoveHandler);
+ Dygraph.removeEvent(this.mouseEventElement_, 'mousemove', this.mouseUpHandler_);
removeRecursive(this.maindiv_);
var nullOut = function(obj) {
"top": "0px",
"left": (this.width_ - divWidth - 2) + "px",
"background": "white",
+ "lineHeight": "normal",
"textAlign": "left",
"overflow": "hidden"};
Dygraph.update(messagestyle, this.attr_('labelsDivStyles'));
div.className = "dygraph-legend";
for (var name in messagestyle) {
if (messagestyle.hasOwnProperty(name)) {
- div.style[name] = messagestyle[name];
+ try {
+ div.style[name] = messagestyle[name];
+ } catch (e) {
+ this.warn("You are using unsupported css properties for your browser in labelsDivStyles");
+ }
}
}
this.graphDiv.appendChild(div);
prevEndX: null, // pixel coordinates
prevEndY: null, // pixel coordinates
prevDragDirection: null,
+ cancelNextDblclick: false, // see comment in dygraph-interaction-model.js
// The value on the left side of the graph when a pan operation starts.
initialLeftmostDate: null,