this.options = {}; // TODO(danvk): remove, use attr_ instead.
Dygraph.update(this.options, options ? options : {});
this.datasets = new Array();
+ this.annotations = new Array();
};
DygraphLayout.prototype.attr_ = function(name) {
this.datasets[setname] = set_xy;
};
+DygraphLayout.prototype.setAnnotations = function(ann) {
+ // The Dygraph object's annotations aren't parsed. We parse them here and
+ // save a copy.
+ var parse = this.attr_('xValueParser');
+ for (var i = 0; i < ann.length; i++) {
+ var a = {};
+ if (!ann[i].xval && !ann[i].x) {
+ this.dygraph_.error("Annotations must have an 'x' property");
+ return;
+ }
+ if (ann[i].icon &&
+ !(ann[i].hasOwnProperty('width') &&
+ ann[i].hasOwnProperty('height'))) {
+ this.dygraph_.error("Must set width and height when setting " +
+ "annotation.icon property");
+ return;
+ }
+ Dygraph.update(a, ann[i]);
+ if (!a.xval) a.xval = parse(a.x);
+ this.annotations.push(a);
+ }
+};
+
DygraphLayout.prototype.evaluate = function() {
this._evaluateLimits();
this._evaluateLineCharts();
this._evaluateLineTicks();
+ this._evaluateAnnotations();
};
DygraphLayout.prototype._evaluateLimits = function() {
name: setName
};
- // limit the x, y values so they do not overdraw
- if (point.y <= 0.0) {
- point.y = 0.0;
- }
- if (point.y >= 1.0) {
- point.y = 1.0;
- }
this.points.push(point);
}
}
}
};
+DygraphLayout.prototype._evaluateAnnotations = function() {
+ // Add the annotations to the point to which they belong.
+ // Make a map from (setName, xval) to annotation for quick lookups.
+ var annotations = {};
+ for (var i = 0; i < this.annotations.length; i++) {
+ var a = this.annotations[i];
+ annotations[a.xval + "," + a.series] = a;
+ }
+
+ this.annotated_points = [];
+ for (var 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);
+ }
+ }
+};
+
/**
* Convenience function to remove all the data sets from a graph
*/
// internal state
this.xlabels = new Array();
this.ylabels = new Array();
+ this.annotations = new Array();
this.area = {
x: this.options.yAxisLabelWidth + 2 * this.options.axisTickSize,
var el = this.ylabels[i];
el.parentNode.removeChild(el);
}
+ for (var i = 0; i < this.annotations.length; i++) {
+ var el = this.annotations[i];
+ el.parentNode.removeChild(el);
+ }
this.xlabels = new Array();
this.ylabels = new Array();
+ this.annotations = new Array();
};
var ctx = this.element.getContext("2d");
if (this.options.underlayCallback) {
- this.options.underlayCallback(ctx, this.area, this.layout);
+ this.options.underlayCallback(ctx, this.area, this.layout, this.dygraph_);
}
if (this.options.drawYGrid) {
// Do the ordinary rendering, as before
this._renderLineChart();
this._renderAxis();
+ this._renderAnnotations();
};
};
+DygraphCanvasRenderer.prototype._renderAnnotations = function() {
+ var annotationStyle = {
+ "position": "absolute",
+ "fontSize": this.options.axisLabelFontSize + "px",
+ "zIndex": 10,
+ "overflow": "hidden"
+ };
+
+ var bindEvt = function(eventName, classEventName, p, self) {
+ return function(e) {
+ var a = p.annotation;
+ if (a.hasOwnProperty(eventName)) {
+ a[eventName](a, p, self.dygraph_, e);
+ } else if (self.dygraph_.attr_(classEventName)) {
+ self.dygraph_.attr_(classEventName)(a, p, self.dygraph_,e );
+ }
+ };
+ }
+
+ // Get a list of point with annotations.
+ 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) {
+ continue;
+ }
+
+ var a = p.annotation;
+ var tick_height = 6;
+ if (a.hasOwnProperty("tickHeight")) {
+ tick_height = a.tickHeight;
+ }
+
+ var div = document.createElement("div");
+ for (var name in annotationStyle) {
+ if (annotationStyle.hasOwnProperty(name)) {
+ div.style[name] = annotationStyle[name];
+ }
+ }
+ if (!a.hasOwnProperty('icon')) {
+ div.className = "dygraphDefaultAnnotation";
+ }
+ if (a.hasOwnProperty('cssClass')) {
+ div.className += " " + a.cssClass;
+ }
+
+ var width = a.hasOwnProperty('width') ? a.width : 16;
+ var height = a.hasOwnProperty('height') ? a.height : 16;
+ if (a.hasOwnProperty('icon')) {
+ var img = document.createElement("img");
+ img.src = a.icon;
+ img.width = width;
+ img.height = height;
+ div.appendChild(img);
+ } else if (p.annotation.hasOwnProperty('shortText')) {
+ div.appendChild(document.createTextNode(p.annotation.shortText));
+ }
+ div.style.left = (p.canvasx - width / 2) + "px";
+ if (a.attachAtBottom) {
+ div.style.top = (this.area.h - height - tick_height) + "px";
+ } else {
+ div.style.top = (p.canvasy - height - tick_height) + "px";
+ }
+ div.style.width = width + "px";
+ div.style.height = height + "px";
+ div.title = p.annotation.text;
+ div.style.color = this.colors[p.name];
+ div.style.borderColor = this.colors[p.name];
+ a.div = div;
+
+ Dygraph.addEvent(div, 'click',
+ bindEvt('clickHandler', 'annotationClickHandler', p, this));
+ Dygraph.addEvent(div, 'mouseover',
+ bindEvt('mouseOverHandler', 'annotationMouseOverHandler', p, this));
+ Dygraph.addEvent(div, 'mouseout',
+ bindEvt('mouseOutHandler', 'annotationMouseOutHandler', p, this));
+ Dygraph.addEvent(div, 'dblclick',
+ bindEvt('dblClickHandler', 'annotationDblClickHandler', p, this));
+
+ this.container.appendChild(div);
+ this.annotations.push(div);
+
+ var ctx = this.element.getContext("2d");
+ ctx.strokeStyle = this.colors[p.name];
+ ctx.beginPath();
+ if (!a.attachAtBottom) {
+ ctx.moveTo(p.canvasx, p.canvasy);
+ ctx.lineTo(p.canvasx, p.canvasy - 2 - tick_height);
+ } else {
+ ctx.moveTo(p.canvasx, this.area.h);
+ ctx.lineTo(p.canvasx, this.area.h - 2 - tick_height);
+ }
+ ctx.closePath();
+ ctx.stroke();
+ }
+};
+
+
/**
* Overrides the CanvasRenderer method to draw error bars
*/
var fillAlpha = this.options.fillAlpha;
var errorBars = this.layout.options.errorBars;
var fillGraph = this.layout.options.fillGraph;
+ var stackedGraph = this.layout.options.stackedGraph;
+ var stepPlot = this.layout.options.stepPlot;
var setNames = [];
for (var name in this.layout.datasets) {
// setup graphics context
ctx.save();
- ctx.strokeStyle = color;
- ctx.lineWidth = this.options.strokeWidth;
var prevX = NaN;
+ var prevY = NaN;
var prevYs = [-1, -1];
- var count = 0;
var yscale = this.layout.yscale;
// should be same color as the lines but only 15% opaque.
var rgb = new RGBColor(color);
ctx.beginPath();
for (var j = 0; j < this.layout.points.length; j++) {
var point = this.layout.points[j];
- count++;
if (point.name == setName) {
if (!isOK(point.y)) {
prevX = NaN;
continue;
}
+
// TODO(danvk): here
- var newYs = [ point.y - point.errorPlus * yscale,
- point.y + point.errorMinus * yscale ];
+ if (stepPlot) {
+ var newYs = [ prevY - point.errorPlus * yscale,
+ prevY + point.errorMinus * yscale ];
+ prevY = point.y;
+ } else {
+ var newYs = [ point.y - point.errorPlus * yscale,
+ point.y + point.errorMinus * yscale ];
+ }
newYs[0] = this.area.h * newYs[0] + this.area.y;
newYs[1] = this.area.h * newYs[1] + this.area.y;
if (!isNaN(prevX)) {
- ctx.moveTo(prevX, prevYs[0]);
+ 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]);
- ctx.lineTo(prevX, prevYs[1]);
+ if (stepPlot) {
+ ctx.lineTo(prevX, newYs[1]);
+ } else {
+ ctx.lineTo(prevX, prevYs[1]);
+ }
ctx.closePath();
}
- prevYs[0] = newYs[0];
- prevYs[1] = newYs[1];
+ prevYs = newYs;
prevX = point.canvasx;
}
}
ctx.fill();
}
} else if (fillGraph) {
- // TODO(danvk): merge this code with the logic above; they're very similar.
- for (var i = 0; i < setCount; i++) {
+ var axisY = 1.0 + this.layout.minyval * this.layout.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 baseline = [] // for stacked graphs: baseline for filling
+
+ // process sets in reverse order (needed for stacked graphs)
+ for (var i = setCount - 1; i >= 0; i--) {
var setName = setNames[i];
- var setNameLast;
- if (i>0) setNameLast = setNames[i-1];
var color = this.colors[setName];
// setup graphics context
ctx.save();
- ctx.strokeStyle = color;
- ctx.lineWidth = this.options.strokeWidth;
var prevX = NaN;
var prevYs = [-1, -1];
- var count = 0;
var yscale = this.layout.yscale;
// should be same color as the lines but only 15% opaque.
var rgb = new RGBColor(color);
ctx.beginPath();
for (var j = 0; j < this.layout.points.length; j++) {
var point = this.layout.points[j];
- count++;
if (point.name == setName) {
if (!isOK(point.y)) {
prevX = NaN;
continue;
}
- var pX = 1.0 + this.layout.minyval * this.layout.yscale;
- if (pX < 0.0) pX = 0.0;
- else if (pX > 1.0) pX = 1.0;
- var newYs = [ point.y, pX ];
- newYs[0] = this.area.h * newYs[0] + this.area.y;
- newYs[1] = this.area.h * newYs[1] + this.area.y;
+ var newYs;
+ if (stackedGraph) {
+ lastY = baseline[point.canvasx];
+ if (lastY === undefined) lastY = axisY;
+ baseline[point.canvasx] = point.canvasy;
+ newYs = [ point.canvasy, lastY ];
+ } else {
+ newYs = [ point.canvasy, axisY ];
+ }
if (!isNaN(prevX)) {
ctx.moveTo(prevX, prevYs[0]);
- ctx.lineTo(point.canvasx, newYs[0]);
+ if (stepPlot) {
+ ctx.lineTo(point.canvasx, prevYs[0]);
+ } else {
+ ctx.lineTo(point.canvasx, newYs[0]);
+ }
ctx.lineTo(point.canvasx, newYs[1]);
ctx.lineTo(prevX, prevYs[1]);
ctx.closePath();
}
- prevYs[0] = newYs[0];
- prevYs[1] = newYs[1];
+ prevYs = newYs;
prevX = point.canvasx;
}
}
ctx.strokeStyle = color;
ctx.lineWidth = this.options.strokeWidth;
ctx.moveTo(prevX, prevY);
+ if (stepPlot) {
+ ctx.lineTo(point.canvasx, prevY);
+ }
prevX = point.canvasx;
prevY = point.canvasy;
ctx.lineTo(prevX, prevY);