sample annotation
[dygraphs.git] / dygraph-canvas.js
CommitLineData
6a1aa64f
DV
1// Copyright 2006 Dan Vanderkam (danvdk@gmail.com)
2// All Rights Reserved.
3
4/**
3df0ccf0
DV
5 * @fileoverview Based on PlotKit, but modified to meet the needs of dygraphs.
6 * In particular, support for:
7 * - grid overlays
8 * - error bars
9 * - dygraphs attribute system
6a1aa64f
DV
10 */
11
6a1aa64f 12/**
3df0ccf0 13 * Creates a new DygraphLayout object.
6a1aa64f 14 * @param {Object} options Options for PlotKit.Layout
285a6bda 15 * @return {Object} The DygraphLayout object
6a1aa64f 16 */
efe0829a
DV
17DygraphLayout = function(dygraph, options) {
18 this.dygraph_ = dygraph;
19 this.options = {}; // TODO(danvk): remove, use attr_ instead.
fc80a396 20 Dygraph.update(this.options, options ? options : {});
efe0829a 21 this.datasets = new Array();
6a1aa64f 22};
efe0829a
DV
23
24DygraphLayout.prototype.attr_ = function(name) {
25 return this.dygraph_.attr_(name);
26};
27
28DygraphLayout.prototype.addDataset = function(setname, set_xy) {
29 this.datasets[setname] = set_xy;
30};
31
ce49c2fa
DV
32// TODO(danvk): CONTRACT remove
33DygraphLayout.prototype.addAnnotation = function() {
34 // Add an annotation to one series.
35 this.annotations = [];
36 for (var x = 10; x < 30; x += 2) {
37 this.annotations.push( {
38 series: 'sine wave',
39 xval: this.attr_('xValueParser')("200610" + x),
40 shortText: x,
41 text: 'Stock Market Crash ' + x
42 } );
43 }
44 this.annotations.push( {
45 series: 'another line',
46 xval: this.attr_('xValueParser')("20061013"),
47 shortText: 'X',
48 text: 'Another one'
49 } );
50};
51
efe0829a
DV
52DygraphLayout.prototype.evaluate = function() {
53 this._evaluateLimits();
54 this._evaluateLineCharts();
55 this._evaluateLineTicks();
ce49c2fa 56 this._evaluateAnnotations();
efe0829a
DV
57};
58
59DygraphLayout.prototype._evaluateLimits = function() {
60 this.minxval = this.maxxval = null;
f6401bf6
DV
61 if (this.options.dateWindow) {
62 this.minxval = this.options.dateWindow[0];
63 this.maxxval = this.options.dateWindow[1];
64 } else {
65 for (var name in this.datasets) {
66 if (!this.datasets.hasOwnProperty(name)) continue;
67 var series = this.datasets[name];
68 var x1 = series[0][0];
69 if (!this.minxval || x1 < this.minxval) this.minxval = x1;
85b99f0b 70
f6401bf6
DV
71 var x2 = series[series.length - 1][0];
72 if (!this.maxxval || x2 > this.maxxval) this.maxxval = x2;
73 }
efe0829a
DV
74 }
75 this.xrange = this.maxxval - this.minxval;
76 this.xscale = (this.xrange != 0 ? 1/this.xrange : 1.0);
77
78 this.minyval = this.options.yAxis[0];
79 this.maxyval = this.options.yAxis[1];
80 this.yrange = this.maxyval - this.minyval;
81 this.yscale = (this.yrange != 0 ? 1/this.yrange : 1.0);
82};
83
84DygraphLayout.prototype._evaluateLineCharts = function() {
85 // add all the rects
86 this.points = new Array();
87 for (var setName in this.datasets) {
85b99f0b
DV
88 if (!this.datasets.hasOwnProperty(setName)) continue;
89
90 var dataset = this.datasets[setName];
91 for (var j = 0; j < dataset.length; j++) {
92 var item = dataset[j];
93 var point = {
ff00d3e2 94 // TODO(danvk): here
85b99f0b
DV
95 x: ((parseFloat(item[0]) - this.minxval) * this.xscale),
96 y: 1.0 - ((parseFloat(item[1]) - this.minyval) * this.yscale),
97 xval: parseFloat(item[0]),
98 yval: parseFloat(item[1]),
99 name: setName
100 };
101
102 // limit the x, y values so they do not overdraw
103 if (point.y <= 0.0) {
104 point.y = 0.0;
105 }
106 if (point.y >= 1.0) {
107 point.y = 1.0;
108 }
1a26f3fb 109 this.points.push(point);
85b99f0b 110 }
efe0829a
DV
111 }
112};
113
114DygraphLayout.prototype._evaluateLineTicks = function() {
115 this.xticks = new Array();
116 for (var i = 0; i < this.options.xTicks.length; i++) {
117 var tick = this.options.xTicks[i];
118 var label = tick.label;
119 var pos = this.xscale * (tick.v - this.minxval);
120 if ((pos >= 0.0) && (pos <= 1.0)) {
121 this.xticks.push([pos, label]);
122 }
123 }
124
125 this.yticks = new Array();
126 for (var i = 0; i < this.options.yTicks.length; i++) {
127 var tick = this.options.yTicks[i];
128 var label = tick.label;
129 var pos = 1.0 - (this.yscale * (tick.v - this.minyval));
130 if ((pos >= 0.0) && (pos <= 1.0)) {
131 this.yticks.push([pos, label]);
132 }
133 }
134};
135
6a1aa64f
DV
136
137/**
138 * Behaves the same way as PlotKit.Layout, but also copies the errors
139 * @private
140 */
285a6bda 141DygraphLayout.prototype.evaluateWithError = function() {
6a1aa64f
DV
142 this.evaluate();
143 if (!this.options.errorBars) return;
144
145 // Copy over the error terms
146 var i = 0; // index in this.points
147 for (var setName in this.datasets) {
85b99f0b
DV
148 if (!this.datasets.hasOwnProperty(setName)) continue;
149 var j = 0;
150 var dataset = this.datasets[setName];
151 for (var j = 0; j < dataset.length; j++, i++) {
152 var item = dataset[j];
153 var xv = parseFloat(item[0]);
154 var yv = parseFloat(item[1]);
155
156 if (xv == this.points[i].xval &&
157 yv == this.points[i].yval) {
158 this.points[i].errorMinus = parseFloat(item[2]);
159 this.points[i].errorPlus = parseFloat(item[3]);
160 }
161 }
6a1aa64f
DV
162 }
163};
164
ce49c2fa
DV
165DygraphLayout.prototype._evaluateAnnotations = function() {
166 // Add the annotations to the point to which they belong.
167 // Make a map from (setName, xval) to annotation for quick lookups.
168 var annotations = {};
169 for (var i = 0; i < this.annotations.length; i++) {
170 var a = this.annotations[i];
171 annotations[a.xval + "," + a.series] = a;
172 }
173
174 this.annotated_points = [];
175 for (var i = 0; i < this.points.length; i++) {
176 var p = this.points[i];
177 var k = p.xval + "," + p.name;
178 if (k in annotations) {
179 p.annotation = annotations[k];
180 this.annotated_points.push(p);
181 }
182 }
183};
184
6a1aa64f
DV
185/**
186 * Convenience function to remove all the data sets from a graph
187 */
285a6bda 188DygraphLayout.prototype.removeAllDatasets = function() {
6a1aa64f
DV
189 delete this.datasets;
190 this.datasets = new Array();
191};
192
193/**
194 * Change the values of various layout options
195 * @param {Object} new_options an associative array of new properties
196 */
285a6bda 197DygraphLayout.prototype.updateOptions = function(new_options) {
fc80a396 198 Dygraph.update(this.options, new_options ? new_options : {});
6a1aa64f
DV
199};
200
201// Subclass PlotKit.CanvasRenderer to add:
202// 1. X/Y grid overlay
203// 2. Ability to draw error bars (if required)
204
205/**
206 * Sets some PlotKit.CanvasRenderer options
207 * @param {Object} element The canvas to attach to
285a6bda 208 * @param {Layout} layout The DygraphLayout object for this graph.
6a1aa64f
DV
209 * @param {Object} options Options to pass on to CanvasRenderer
210 */
9317362d
DV
211DygraphCanvasRenderer = function(dygraph, element, layout, options) {
212 // TODO(danvk): remove options, just use dygraph.attr_.
9317362d 213 this.dygraph_ = dygraph;
fbe31dc8
DV
214
215 // default options
216 this.options = {
f474c2a3
DV
217 "strokeWidth": 0.5,
218 "drawXAxis": true,
219 "drawYAxis": true,
220 "axisLineColor": "black",
221 "axisLineWidth": 0.5,
222 "axisTickSize": 3,
223 "axisLabelColor": "black",
224 "axisLabelFont": "Arial",
225 "axisLabelFontSize": 9,
226 "axisLabelWidth": 50,
227 "drawYGrid": true,
228 "drawXGrid": true,
43af96e7 229 "gridLineColor": "rgb(128,128,128)",
e7746234
EC
230 "fillAlpha": 0.15,
231 "underlayCallback": null
fbe31dc8 232 };
fc80a396 233 Dygraph.update(this.options, options);
6a1aa64f 234
fbe31dc8 235 this.layout = layout;
b0c3b730 236 this.element = element;
fbe31dc8
DV
237 this.container = this.element.parentNode;
238
fbe31dc8
DV
239 this.height = this.element.height;
240 this.width = this.element.width;
241
242 // --- check whether everything is ok before we return
243 if (!this.isIE && !(DygraphCanvasRenderer.isSupported(this.element)))
244 throw "Canvas is not supported.";
245
246 // internal state
247 this.xlabels = new Array();
248 this.ylabels = new Array();
ce49c2fa 249 this.annotations = new Array();
fbe31dc8
DV
250
251 this.area = {
252 x: this.options.yAxisLabelWidth + 2 * this.options.axisTickSize,
253 y: 0
254 };
255 this.area.w = this.width - this.area.x - this.options.rightGap;
256 this.area.h = this.height - this.options.axisLabelFontSize -
257 2 * this.options.axisTickSize;
258
b0c3b730
DV
259 this.container.style.position = "relative";
260 this.container.style.width = this.width + "px";
fbe31dc8
DV
261};
262
263DygraphCanvasRenderer.prototype.clear = function() {
264 if (this.isIE) {
265 // VML takes a while to start up, so we just poll every this.IEDelay
266 try {
267 if (this.clearDelay) {
268 this.clearDelay.cancel();
269 this.clearDelay = null;
270 }
271 var context = this.element.getContext("2d");
272 }
273 catch (e) {
76171648 274 // TODO(danvk): this is broken, since MochiKit.Async is gone.
fbe31dc8
DV
275 this.clearDelay = MochiKit.Async.wait(this.IEDelay);
276 this.clearDelay.addCallback(bind(this.clear, this));
277 return;
278 }
279 }
280
281 var context = this.element.getContext("2d");
282 context.clearRect(0, 0, this.width, this.height);
283
2160ed4a 284 for (var i = 0; i < this.xlabels.length; i++) {
b0c3b730
DV
285 var el = this.xlabels[i];
286 el.parentNode.removeChild(el);
2160ed4a
DV
287 }
288 for (var i = 0; i < this.ylabels.length; i++) {
b0c3b730
DV
289 var el = this.ylabels[i];
290 el.parentNode.removeChild(el);
2160ed4a 291 }
ce49c2fa
DV
292 for (var i = 0; i < this.annotations.length; i++) {
293 var el = this.annotations[i];
294 el.parentNode.removeChild(el);
295 }
fbe31dc8
DV
296 this.xlabels = new Array();
297 this.ylabels = new Array();
ce49c2fa 298 this.annotations = new Array();
fbe31dc8
DV
299};
300
301
302DygraphCanvasRenderer.isSupported = function(canvasName) {
303 var canvas = null;
304 try {
21d3323f 305 if (typeof(canvasName) == 'undefined' || canvasName == null)
b0c3b730 306 canvas = document.createElement("canvas");
fbe31dc8 307 else
b0c3b730 308 canvas = canvasName;
fbe31dc8
DV
309 var context = canvas.getContext("2d");
310 }
311 catch (e) {
312 var ie = navigator.appVersion.match(/MSIE (\d\.\d)/);
313 var opera = (navigator.userAgent.toLowerCase().indexOf("opera") != -1);
314 if ((!ie) || (ie[1] < 6) || (opera))
315 return false;
316 return true;
317 }
318 return true;
6a1aa64f 319};
6a1aa64f
DV
320
321/**
322 * Draw an X/Y grid on top of the existing plot
323 */
285a6bda 324DygraphCanvasRenderer.prototype.render = function() {
6a1aa64f
DV
325 // Draw the new X/Y grid
326 var ctx = this.element.getContext("2d");
e7746234
EC
327
328 if (this.options.underlayCallback) {
d9e6fa47 329 this.options.underlayCallback(ctx, this.area, this.layout, this.dygraph_);
e7746234
EC
330 }
331
6a1aa64f
DV
332 if (this.options.drawYGrid) {
333 var ticks = this.layout.yticks;
334 ctx.save();
f2f24402 335 ctx.strokeStyle = this.options.gridLineColor;
6a1aa64f
DV
336 ctx.lineWidth = this.options.axisLineWidth;
337 for (var i = 0; i < ticks.length; i++) {
338 var x = this.area.x;
339 var y = this.area.y + ticks[i][0] * this.area.h;
340 ctx.beginPath();
341 ctx.moveTo(x, y);
342 ctx.lineTo(x + this.area.w, y);
343 ctx.closePath();
344 ctx.stroke();
345 }
346 }
347
348 if (this.options.drawXGrid) {
349 var ticks = this.layout.xticks;
350 ctx.save();
f2f24402 351 ctx.strokeStyle = this.options.gridLineColor;
6a1aa64f
DV
352 ctx.lineWidth = this.options.axisLineWidth;
353 for (var i=0; i<ticks.length; i++) {
354 var x = this.area.x + ticks[i][0] * this.area.w;
355 var y = this.area.y + this.area.h;
356 ctx.beginPath();
357 ctx.moveTo(x, y);
358 ctx.lineTo(x, this.area.y);
359 ctx.closePath();
360 ctx.stroke();
361 }
362 }
2ce09b19
DV
363
364 // Do the ordinary rendering, as before
2ce09b19 365 this._renderLineChart();
fbe31dc8 366 this._renderAxis();
ce49c2fa 367 this._renderAnnotations();
fbe31dc8
DV
368};
369
370
371DygraphCanvasRenderer.prototype._renderAxis = function() {
372 if (!this.options.drawXAxis && !this.options.drawYAxis)
373 return;
374
375 var context = this.element.getContext("2d");
376
34fedff8
DV
377 var labelStyle = {
378 "position": "absolute",
379 "fontSize": this.options.axisLabelFontSize + "px",
380 "zIndex": 10,
f474c2a3 381 "color": this.options.axisLabelColor,
34fedff8
DV
382 "width": this.options.axisLabelWidth + "px",
383 "overflow": "hidden"
384 };
385 var makeDiv = function(txt) {
386 var div = document.createElement("div");
387 for (var name in labelStyle) {
85b99f0b
DV
388 if (labelStyle.hasOwnProperty(name)) {
389 div.style[name] = labelStyle[name];
390 }
fbe31dc8 391 }
34fedff8
DV
392 div.appendChild(document.createTextNode(txt));
393 return div;
fbe31dc8
DV
394 };
395
396 // axis lines
397 context.save();
f474c2a3 398 context.strokeStyle = this.options.axisLineColor;
fbe31dc8
DV
399 context.lineWidth = this.options.axisLineWidth;
400
fbe31dc8 401 if (this.options.drawYAxis) {
8b7a0cc3 402 if (this.layout.yticks && this.layout.yticks.length > 0) {
2160ed4a
DV
403 for (var i = 0; i < this.layout.yticks.length; i++) {
404 var tick = this.layout.yticks[i];
fbe31dc8
DV
405 if (typeof(tick) == "function") return;
406 var x = this.area.x;
407 var y = this.area.y + tick[0] * this.area.h;
408 context.beginPath();
409 context.moveTo(x, y);
410 context.lineTo(x - this.options.axisTickSize, y);
411 context.closePath();
412 context.stroke();
413
34fedff8 414 var label = makeDiv(tick[1]);
fbe31dc8
DV
415 var top = (y - this.options.axisLabelFontSize / 2);
416 if (top < 0) top = 0;
417
418 if (top + this.options.axisLabelFontSize + 3 > this.height) {
419 label.style.bottom = "0px";
420 } else {
421 label.style.top = top + "px";
422 }
423 label.style.left = "0px";
424 label.style.textAlign = "right";
425 label.style.width = this.options.yAxisLabelWidth + "px";
b0c3b730 426 this.container.appendChild(label);
fbe31dc8 427 this.ylabels.push(label);
2160ed4a 428 }
fbe31dc8
DV
429
430 // The lowest tick on the y-axis often overlaps with the leftmost
431 // tick on the x-axis. Shift the bottom tick up a little bit to
432 // compensate if necessary.
433 var bottomTick = this.ylabels[0];
434 var fontSize = this.options.axisLabelFontSize;
435 var bottom = parseInt(bottomTick.style.top) + fontSize;
436 if (bottom > this.height - fontSize) {
437 bottomTick.style.top = (parseInt(bottomTick.style.top) -
438 fontSize / 2) + "px";
439 }
440 }
441
442 context.beginPath();
443 context.moveTo(this.area.x, this.area.y);
444 context.lineTo(this.area.x, this.area.y + this.area.h);
445 context.closePath();
446 context.stroke();
447 }
448
449 if (this.options.drawXAxis) {
450 if (this.layout.xticks) {
2160ed4a
DV
451 for (var i = 0; i < this.layout.xticks.length; i++) {
452 var tick = this.layout.xticks[i];
fbe31dc8
DV
453 if (typeof(dataset) == "function") return;
454
455 var x = this.area.x + tick[0] * this.area.w;
456 var y = this.area.y + this.area.h;
457 context.beginPath();
458 context.moveTo(x, y);
459 context.lineTo(x, y + this.options.axisTickSize);
460 context.closePath();
461 context.stroke();
462
34fedff8 463 var label = makeDiv(tick[1]);
fbe31dc8
DV
464 label.style.textAlign = "center";
465 label.style.bottom = "0px";
466
467 var left = (x - this.options.axisLabelWidth/2);
468 if (left + this.options.axisLabelWidth > this.width) {
469 left = this.width - this.options.xAxisLabelWidth;
470 label.style.textAlign = "right";
471 }
472 if (left < 0) {
473 left = 0;
474 label.style.textAlign = "left";
475 }
476
477 label.style.left = left + "px";
478 label.style.width = this.options.xAxisLabelWidth + "px";
b0c3b730 479 this.container.appendChild(label);
fbe31dc8 480 this.xlabels.push(label);
2160ed4a 481 }
fbe31dc8
DV
482 }
483
484 context.beginPath();
485 context.moveTo(this.area.x, this.area.y + this.area.h);
486 context.lineTo(this.area.x + this.area.w, this.area.y + this.area.h);
487 context.closePath();
488 context.stroke();
489 }
490
491 context.restore();
6a1aa64f
DV
492};
493
fbe31dc8 494
ce49c2fa
DV
495DygraphCanvasRenderer.prototype._renderAnnotations = function() {
496 var annotationStyle = {
497 "position": "absolute",
498 "fontSize": this.options.axisLabelFontSize + "px",
499 "zIndex": 10,
500 "width": "20px",
501 "overflow": "hidden",
502 "border": "1px solid black",
503 "background-color": "white",
504 "text-align": "center"
505 };
506
507 // Get a list of point with annotations.
508 var points = this.layout.annotated_points;
509 for (var i = 0; i < points.length; i++) {
510 var p = points[i];
511 var div = document.createElement("div");
512 for (var name in annotationStyle) {
513 if (annotationStyle.hasOwnProperty(name)) {
514 div.style[name] = annotationStyle[name];
515 }
516 }
517 div.appendChild(document.createTextNode(p.annotation.shortText));
518 div.style.left = (p.canvasx - 10) + "px";
519 div.style.top = p.canvasy + "px";
520 div.title = p.annotation.text;
521 div.style.color = this.colors[p.name];
522 div.style.borderColor = this.colors[p.name];
523 this.container.appendChild(div);
524 this.annotations.push(div);
525 }
526};
527
528
6a1aa64f
DV
529/**
530 * Overrides the CanvasRenderer method to draw error bars
531 */
285a6bda 532DygraphCanvasRenderer.prototype._renderLineChart = function() {
6a1aa64f
DV
533 var context = this.element.getContext("2d");
534 var colorCount = this.options.colorScheme.length;
535 var colorScheme = this.options.colorScheme;
43af96e7 536 var fillAlpha = this.options.fillAlpha;
6a1aa64f 537 var errorBars = this.layout.options.errorBars;
5954ef32 538 var fillGraph = this.layout.options.fillGraph;
354e15ab 539 var stackedGraph = this.layout.options.stackedGraph;
afdc483f 540 var stepPlot = this.layout.options.stepPlot;
21d3323f
DV
541
542 var setNames = [];
ca43052c 543 for (var name in this.layout.datasets) {
85b99f0b
DV
544 if (this.layout.datasets.hasOwnProperty(name)) {
545 setNames.push(name);
546 }
ca43052c 547 }
21d3323f 548 var setCount = setNames.length;
6a1aa64f 549
f032c51d
AV
550 this.colors = {}
551 for (var i = 0; i < setCount; i++) {
552 this.colors[setNames[i]] = colorScheme[i % colorCount];
553 }
554
ff00d3e2
DV
555 // Update Points
556 // TODO(danvk): here
2160ed4a
DV
557 for (var i = 0; i < this.layout.points.length; i++) {
558 var point = this.layout.points[i];
6a1aa64f
DV
559 point.canvasx = this.area.w * point.x + this.area.x;
560 point.canvasy = this.area.h * point.y + this.area.y;
561 }
6a1aa64f
DV
562
563 // create paths
9317362d 564 var isOK = function(x) { return x && !isNaN(x); };
6a1aa64f 565
80aaae18
DV
566 var ctx = context;
567 if (errorBars) {
6a834bbb
DV
568 if (fillGraph) {
569 this.dygraph_.warn("Can't use fillGraph option with error bars");
570 }
571
6a1aa64f
DV
572 for (var i = 0; i < setCount; i++) {
573 var setName = setNames[i];
f032c51d 574 var color = this.colors[setName];
6a1aa64f
DV
575
576 // setup graphics context
80aaae18 577 ctx.save();
56623f3b 578 var prevX = NaN;
afdc483f 579 var prevY = NaN;
6a1aa64f 580 var prevYs = [-1, -1];
6a1aa64f 581 var yscale = this.layout.yscale;
f474c2a3
DV
582 // should be same color as the lines but only 15% opaque.
583 var rgb = new RGBColor(color);
43af96e7
NK
584 var err_color = 'rgba(' + rgb.r + ',' + rgb.g + ',' + rgb.b + ',' +
585 fillAlpha + ')';
f474c2a3 586 ctx.fillStyle = err_color;
05c9d0c4
DV
587 ctx.beginPath();
588 for (var j = 0; j < this.layout.points.length; j++) {
589 var point = this.layout.points[j];
6a1aa64f 590 if (point.name == setName) {
5954ef32 591 if (!isOK(point.y)) {
56623f3b 592 prevX = NaN;
ae85914a 593 continue;
5011e7a1 594 }
ce49c2fa 595
ff00d3e2 596 // TODO(danvk): here
afdc483f
NN
597 if (stepPlot) {
598 var newYs = [ prevY - point.errorPlus * yscale,
47600757 599 prevY + point.errorMinus * yscale ];
afdc483f
NN
600 prevY = point.y;
601 } else {
602 var newYs = [ point.y - point.errorPlus * yscale,
47600757 603 point.y + point.errorMinus * yscale ];
afdc483f 604 }
6a1aa64f
DV
605 newYs[0] = this.area.h * newYs[0] + this.area.y;
606 newYs[1] = this.area.h * newYs[1] + this.area.y;
56623f3b 607 if (!isNaN(prevX)) {
afdc483f 608 if (stepPlot) {
47600757 609 ctx.moveTo(prevX, newYs[0]);
afdc483f 610 } else {
47600757 611 ctx.moveTo(prevX, prevYs[0]);
afdc483f 612 }
5954ef32
DV
613 ctx.lineTo(point.canvasx, newYs[0]);
614 ctx.lineTo(point.canvasx, newYs[1]);
afdc483f 615 if (stepPlot) {
47600757 616 ctx.lineTo(prevX, newYs[1]);
afdc483f 617 } else {
47600757 618 ctx.lineTo(prevX, prevYs[1]);
afdc483f 619 }
5954ef32
DV
620 ctx.closePath();
621 }
354e15ab 622 prevYs = newYs;
5954ef32
DV
623 prevX = point.canvasx;
624 }
625 }
626 ctx.fill();
627 }
628 } else if (fillGraph) {
354e15ab
DE
629 var axisY = 1.0 + this.layout.minyval * this.layout.yscale;
630 if (axisY < 0.0) axisY = 0.0;
631 else if (axisY > 1.0) axisY = 1.0;
632 axisY = this.area.h * axisY + this.area.y;
633
634 var baseline = [] // for stacked graphs: baseline for filling
635
636 // process sets in reverse order (needed for stacked graphs)
637 for (var i = setCount - 1; i >= 0; i--) {
5954ef32 638 var setName = setNames[i];
f032c51d 639 var color = this.colors[setName];
5954ef32
DV
640
641 // setup graphics context
642 ctx.save();
56623f3b 643 var prevX = NaN;
5954ef32 644 var prevYs = [-1, -1];
5954ef32
DV
645 var yscale = this.layout.yscale;
646 // should be same color as the lines but only 15% opaque.
647 var rgb = new RGBColor(color);
43af96e7
NK
648 var err_color = 'rgba(' + rgb.r + ',' + rgb.g + ',' + rgb.b + ',' +
649 fillAlpha + ')';
5954ef32
DV
650 ctx.fillStyle = err_color;
651 ctx.beginPath();
652 for (var j = 0; j < this.layout.points.length; j++) {
653 var point = this.layout.points[j];
5954ef32
DV
654 if (point.name == setName) {
655 if (!isOK(point.y)) {
56623f3b 656 prevX = NaN;
5954ef32
DV
657 continue;
658 }
354e15ab
DE
659 var newYs;
660 if (stackedGraph) {
661 lastY = baseline[point.canvasx];
662 if (lastY === undefined) lastY = axisY;
663 baseline[point.canvasx] = point.canvasy;
664 newYs = [ point.canvasy, lastY ];
665 } else {
666 newYs = [ point.canvasy, axisY ];
667 }
56623f3b 668 if (!isNaN(prevX)) {
05c9d0c4 669 ctx.moveTo(prevX, prevYs[0]);
afdc483f 670 if (stepPlot) {
47600757 671 ctx.lineTo(point.canvasx, prevYs[0]);
afdc483f 672 } else {
47600757 673 ctx.lineTo(point.canvasx, newYs[0]);
afdc483f 674 }
05c9d0c4
DV
675 ctx.lineTo(point.canvasx, newYs[1]);
676 ctx.lineTo(prevX, prevYs[1]);
677 ctx.closePath();
6a1aa64f 678 }
354e15ab 679 prevYs = newYs;
6a1aa64f
DV
680 prevX = point.canvasx;
681 }
05c9d0c4 682 }
6a1aa64f
DV
683 ctx.fill();
684 }
80aaae18
DV
685 }
686
687 for (var i = 0; i < setCount; i++) {
688 var setName = setNames[i];
f032c51d 689 var color = this.colors[setName];
80aaae18
DV
690
691 // setup graphics context
692 context.save();
693 var point = this.layout.points[0];
694 var pointSize = this.dygraph_.attr_("pointSize");
695 var prevX = null, prevY = null;
696 var drawPoints = this.dygraph_.attr_("drawPoints");
697 var points = this.layout.points;
698 for (var j = 0; j < points.length; j++) {
699 var point = points[j];
700 if (point.name == setName) {
701 if (!isOK(point.canvasy)) {
702 // this will make us move to the next point, not draw a line to it.
703 prevX = prevY = null;
704 } else {
705 // A point is "isolated" if it is non-null but both the previous
706 // and next points are null.
707 var isIsolated = (!prevX && (j == points.length - 1 ||
708 !isOK(points[j+1].canvasy)));
709
710 if (!prevX) {
711 prevX = point.canvasx;
712 prevY = point.canvasy;
713 } else {
714 ctx.beginPath();
715 ctx.strokeStyle = color;
716 ctx.lineWidth = this.options.strokeWidth;
717 ctx.moveTo(prevX, prevY);
afdc483f 718 if (stepPlot) {
47600757 719 ctx.lineTo(point.canvasx, prevY);
afdc483f 720 }
80aaae18
DV
721 prevX = point.canvasx;
722 prevY = point.canvasy;
723 ctx.lineTo(prevX, prevY);
724 ctx.stroke();
725 }
726
727 if (drawPoints || isIsolated) {
728 ctx.beginPath();
729 ctx.fillStyle = color;
7bf6a9fe
DV
730 ctx.arc(point.canvasx, point.canvasy, pointSize,
731 0, 2 * Math.PI, false);
80aaae18
DV
732 ctx.fill();
733 }
734 }
735 }
736 }
737 }
6a1aa64f 738
6a1aa64f
DV
739 context.restore();
740};