1 // Copyright 2006 Dan Vanderkam (danvdk@gmail.com)
2 // All Rights Reserved.
5 * @fileoverview Based on PlotKit.CanvasRenderer, but modified to meet the
8 * In particular, support for:
11 * - dygraphs attribute system
15 * This class determines the charting area (in pixel coordinates), maps the
16 * percentage coordinates in the DygraphLayout to pixels and draws them.
17 * It's also responsible for creating chart DOM elements, i.e. annotations,
18 * tick mark labels, the title and the x/y-axis labels.
19 * This class is based on PlotKit.CanvasRenderer.
21 * @param {Object} element The canvas to attach to
22 * @param {Object} elementContext The 2d context of the canvas (injected so it
23 * can be mocked for testing.)
24 * @param {Layout} layout The DygraphLayout object for this graph.
27 DygraphCanvasRenderer
= function(dygraph
, element
, elementContext
, layout
) {
28 this.dygraph_
= dygraph
;
31 this.element
= element
;
32 this.elementContext
= elementContext
;
33 this.container
= this.element
.parentNode
;
35 this.height
= this.element
.height
;
36 this.width
= this.element
.width
;
38 // --- check whether everything is ok before we return
39 if (!this.isIE
&& !(DygraphCanvasRenderer
.isSupported(this.element
)))
40 throw "Canvas is not supported.";
43 this.xlabels
= new Array();
44 this.ylabels
= new Array();
45 this.annotations
= new Array();
46 this.chartLabels
= {};
48 this.area
= this.computeArea_();
49 this.container
.style
.position
= "relative";
50 this.container
.style
.width
= this.width
+ "px";
52 // Set up a clipping area for the canvas (and the interaction canvas).
53 // This ensures that we don't overdraw.
54 var ctx
= this.dygraph_
.canvas_ctx_
;
56 ctx
.rect(this.area
.x
, this.area
.y
, this.area
.w
, this.area
.h
);
59 ctx
= this.dygraph_
.hidden_ctx_
;
61 ctx
.rect(this.area
.x
, this.area
.y
, this.area
.w
, this.area
.h
);
65 DygraphCanvasRenderer
.prototype.attr_
= function(x
) {
66 return this.dygraph_
.attr_(x
);
69 // Compute the box which the chart should be drawn in. This is the canvas's
70 // box, less space needed for axis and chart labels.
71 // TODO(danvk): this belongs in DygraphLayout.
72 DygraphCanvasRenderer
.prototype.computeArea_
= function() {
74 // TODO(danvk): per-axis setting.
78 if (this.attr_('drawYAxis')) {
79 area
.x
= this.attr_('yAxisLabelWidth') + 2 * this.attr_('axisTickSize');
82 area
.w
= this.width
- area
.x
- this.attr_('rightGap');
84 if (this.attr_('drawXAxis')) {
85 if (this.attr_('xAxisHeight')) {
86 area
.h
-= this.attr_('xAxisHeight');
88 area
.h
-= this.attr_('axisLabelFontSize') + 2 * this.attr_('axisTickSize');
92 // Shrink the drawing area to accomodate additional y-axes.
93 if (this.dygraph_
.numAxes() == 2) {
94 // TODO(danvk): per-axis setting.
95 area
.w
-= (this.attr_('yAxisLabelWidth') + 2 * this.attr_('axisTickSize'));
96 } else if (this.dygraph_
.numAxes() > 2) {
97 this.dygraph_
.error("Only two y-axes are supported at this time. (Trying " +
98 "to use " + this.dygraph_
.numAxes() + ")");
101 // Add space for chart labels: title, xlabel and ylabel.
102 if (this.attr_('title')) {
103 area
.h
-= this.attr_('titleHeight');
104 area
.y
+= this.attr_('titleHeight');
106 if (this.attr_('xlabel')) {
107 area
.h
-= this.attr_('xLabelHeight');
109 if (this.attr_('ylabel')) {
110 // It would make sense to shift the chart here to make room for the y-axis
111 // label, but the default yAxisLabelWidth is large enough that this results
112 // in overly-padded charts. The y-axis label should fit fine. If it
113 // doesn't, the yAxisLabelWidth option can be increased.
119 DygraphCanvasRenderer
.prototype.clear
= function() {
121 // VML takes a while to start up, so we just poll every this.IEDelay
123 if (this.clearDelay
) {
124 this.clearDelay
.cancel();
125 this.clearDelay
= null;
127 var context
= this.elementContext
;
130 // TODO(danvk): this is broken, since MochiKit.Async is gone.
131 this.clearDelay
= MochiKit
.Async
.wait(this.IEDelay
);
132 this.clearDelay
.addCallback(bind(this.clear
, this));
137 var context
= this.elementContext
;
138 context
.clearRect(0, 0, this.width
, this.height
);
140 for (var i
= 0; i
< this.xlabels
.length
; i
++) {
141 var el
= this.xlabels
[i
];
142 if (el
.parentNode
) el
.parentNode
.removeChild(el
);
144 for (var i
= 0; i
< this.ylabels
.length
; i
++) {
145 var el
= this.ylabels
[i
];
146 if (el
.parentNode
) el
.parentNode
.removeChild(el
);
148 for (var i
= 0; i
< this.annotations
.length
; i
++) {
149 var el
= this.annotations
[i
];
150 if (el
.parentNode
) el
.parentNode
.removeChild(el
);
152 for (var k
in this.chartLabels
) {
153 if (!this.chartLabels
.hasOwnProperty(k
)) continue;
154 var el
= this.chartLabels
[k
];
155 if (el
.parentNode
) el
.parentNode
.removeChild(el
);
157 this.xlabels
= new Array();
158 this.ylabels
= new Array();
159 this.annotations
= new Array();
160 this.chartLabels
= {};
164 DygraphCanvasRenderer
.isSupported
= function(canvasName
) {
167 if (typeof(canvasName
) == 'undefined' || canvasName
== null)
168 canvas
= document
.createElement("canvas");
171 var context
= canvas
.getContext("2d");
174 var ie
= navigator
.appVersion
.match(/MSIE (\d\.\d)/);
175 var opera
= (navigator
.userAgent
.toLowerCase().indexOf("opera") != -1);
176 if ((!ie
) || (ie
[1] < 6) || (opera
))
184 * @param { [String] } colors Array of color strings. Should have one entry for
185 * each series to be rendered.
187 DygraphCanvasRenderer
.prototype.setColors
= function(colors
) {
188 this.colorScheme_
= colors
;
192 * Draw an X/Y grid on top of the existing plot
194 DygraphCanvasRenderer
.prototype.render
= function() {
195 // Draw the new X/Y grid
. Lines appear crisper when pixels are rounded to
196 // half-integers. This prevents them from drawing in two rows/cols.
197 var ctx
= this.elementContext
;
198 function halfUp(x
){return Math
.round(x
)+0.5};
199 function halfDown(y
){return Math
.round(y
)-0.5};
201 if (this.attr_('underlayCallback')) {
202 // NOTE: we pass the dygraph object to this callback twice to avoid breaking
203 // users who expect a deprecated form of this callback.
204 this.attr_('underlayCallback')(ctx
, this.area
, this.dygraph_
, this.dygraph_
);
207 if (this.attr_('drawYGrid')) {
208 var ticks
= this.layout
.yticks
;
210 ctx
.strokeStyle
= this.attr_('gridLineColor');
211 ctx
.lineWidth
= this.attr_('gridLineWidth');
212 for (var i
= 0; i
< ticks
.length
; i
++) {
213 // TODO(danvk): allow secondary axes to draw a grid, too.
214 if (ticks
[i
][0] != 0) continue;
215 var x
= halfUp(this.area
.x
);
216 var y
= halfDown(this.area
.y
+ ticks
[i
][1] * this.area
.h
);
219 ctx
.lineTo(x
+ this.area
.w
, y
);
225 if (this.attr_('drawXGrid')) {
226 var ticks
= this.layout
.xticks
;
228 ctx
.strokeStyle
= this.attr_('gridLineColor');
229 ctx
.lineWidth
= this.attr_('gridLineWidth');
230 for (var i
=0; i
<ticks
.length
; i
++) {
231 var x
= halfUp(this.area
.x
+ ticks
[i
][0] * this.area
.w
);
232 var y
= halfDown(this.area
.y
+ this.area
.h
);
235 ctx
.lineTo(x
, this.area
.y
);
241 // Do the ordinary rendering, as before
242 this._renderLineChart();
244 this._renderChartLabels();
245 this._renderAnnotations();
249 DygraphCanvasRenderer
.prototype._renderAxis
= function() {
250 if (!this.attr_('drawXAxis') && !this.attr_('drawYAxis')) return;
252 // Round pixels to half-integer boundaries for crisper drawing.
253 function halfUp(x
){return Math
.round(x
)+0.5};
254 function halfDown(y
){return Math
.round(y
)-0.5};
256 var context
= this.elementContext
;
259 position
: "absolute",
260 fontSize
: this.attr_('axisLabelFontSize') + "px",
262 color
: this.attr_('axisLabelColor'),
263 width
: this.attr_('axisLabelWidth') + "px",
264 // height: this.attr_('axisLabelFontSize') + 2 + "px",
267 var makeDiv
= function(txt
, axis
) {
268 var div
= document
.createElement("div");
269 for (var name
in labelStyle
) {
270 if (labelStyle
.hasOwnProperty(name
)) {
271 div
.style
[name
] = labelStyle
[name
];
274 var inner_div
= document
.createElement("div");
275 // TODO(danvk): separate class for secondary y-axis
276 inner_div
.className
= 'dygraph-axis-label dygraph-axis-label-' + axis
;
277 inner_div
.appendChild(document
.createTextNode(txt
));
278 div
.appendChild(inner_div
);
284 context
.strokeStyle
= this.attr_('axisLineColor');
285 context
.lineWidth
= this.attr_('axisLineWidth');
287 if (this.attr_('drawYAxis')) {
288 if (this.layout
.yticks
&& this.layout
.yticks
.length
> 0) {
289 for (var i
= 0; i
< this.layout
.yticks
.length
; i
++) {
290 var tick
= this.layout
.yticks
[i
];
291 if (typeof(tick
) == "function") return;
294 if (tick
[0] == 1) { // right-side y-axis
295 x
= this.area
.x
+ this.area
.w
;
298 var y
= this.area
.y
+ tick
[1] * this.area
.h
;
300 context
.moveTo(halfUp(x
), halfDown(y
));
301 context
.lineTo(halfUp(x
- sgn
* this.attr_('axisTickSize')), halfDown(y
));
305 var label
= makeDiv(tick
[2], 'y');
306 var top
= (y
- this.attr_('axisLabelFontSize') / 2);
307 if (top
< 0) top
= 0;
309 if (top
+ this.attr_('axisLabelFontSize') + 3 > this.height
) {
310 label
.style
.bottom
= "0px";
312 label
.style
.top
= top
+ "px";
315 label
.style
.left
= (this.area
.x
- this.attr_('yAxisLabelWidth') - this.attr_('axisTickSize')) + "px";
316 label
.style
.textAlign
= "right";
317 } else if (tick
[0] == 1) {
318 label
.style
.left
= (this.area
.x
+ this.area
.w
+
319 this.attr_('axisTickSize')) + "px";
320 label
.style
.textAlign
= "left";
322 label
.style
.width
= this.attr_('yAxisLabelWidth') + "px";
323 this.container
.appendChild(label
);
324 this.ylabels
.push(label
);
327 // The lowest tick on the y-axis often overlaps with the leftmost
328 // tick on the x-axis. Shift the bottom tick up a little bit to
329 // compensate if necessary.
330 var bottomTick
= this.ylabels
[0];
331 var fontSize
= this.attr_('axisLabelFontSize');
332 var bottom
= parseInt(bottomTick
.style
.top
) + fontSize
;
333 if (bottom
> this.height
- fontSize
) {
334 bottomTick
.style
.top
= (parseInt(bottomTick
.style
.top
) -
335 fontSize
/ 2) + "px";
339 // draw a vertical line on the left to separate the chart from the labels.
341 context
.moveTo(halfUp(this.area
.x
), halfDown(this.area
.y
));
342 context
.lineTo(halfUp(this.area
.x
), halfDown(this.area
.y
+ this.area
.h
));
346 // if there's a secondary y-axis, draw a vertical line for that, too.
347 if (this.dygraph_
.numAxes() == 2) {
349 context
.moveTo(halfDown(this.area
.x
+ this.area
.w
), halfDown(this.area
.y
));
350 context
.lineTo(halfDown(this.area
.x
+ this.area
.w
), halfDown(this.area
.y
+ this.area
.h
));
356 if (this.attr_('drawXAxis')) {
357 if (this.layout
.xticks
) {
358 for (var i
= 0; i
< this.layout
.xticks
.length
; i
++) {
359 var tick
= this.layout
.xticks
[i
];
360 if (typeof(dataset
) == "function") return;
362 var x
= this.area
.x
+ tick
[0] * this.area
.w
;
363 var y
= this.area
.y
+ this.area
.h
;
365 context
.moveTo(halfUp(x
), halfDown(y
));
366 context
.lineTo(halfUp(x
), halfDown(y
+ this.attr_('axisTickSize')));
370 var label
= makeDiv(tick
[1], 'x');
371 label
.style
.textAlign
= "center";
372 label
.style
.top
= (y
+ this.attr_('axisTickSize')) + 'px';
374 var left
= (x
- this.attr_('axisLabelWidth')/2);
375 if (left
+ this.attr_('axisLabelWidth') > this.width
) {
376 left
= this.width
- this.attr_('xAxisLabelWidth');
377 label
.style
.textAlign
= "right";
381 label
.style
.textAlign
= "left";
384 label
.style
.left
= left
+ "px";
385 label
.style
.width
= this.attr_('xAxisLabelWidth') + "px";
386 this.container
.appendChild(label
);
387 this.xlabels
.push(label
);
392 context
.moveTo(halfUp(this.area
.x
), halfDown(this.area
.y
+ this.area
.h
));
393 context
.lineTo(halfUp(this.area
.x
+ this.area
.w
), halfDown(this.area
.y
+ this.area
.h
));
402 DygraphCanvasRenderer
.prototype._renderChartLabels
= function() {
403 // Generate divs for the chart title, xlabel and ylabel.
404 // Space for these divs has already been taken away from the charting area in
405 // the DygraphCanvasRenderer constructor.
406 if (this.attr_('title')) {
407 var div
= document
.createElement("div");
408 div
.style
.position
= 'absolute';
409 div
.style
.top
= '0px';
410 div
.style
.left
= this.area
.x
+ 'px';
411 div
.style
.width
= this.area
.w
+ 'px';
412 div
.style
.height
= this.attr_('titleHeight') + 'px';
413 div
.style
.textAlign
= 'center';
414 div
.style
.fontSize
= (this.attr_('titleHeight') - 8) + 'px';
415 div
.style
.fontWeight
= 'bold';
416 var class_div
= document
.createElement("div");
417 class_div
.className
= 'dygraph-label dygraph-title';
418 class_div
.innerHTML
= this.attr_('title');
419 div
.appendChild(class_div
);
420 this.container
.appendChild(div
);
421 this.chartLabels
.title
= div
;
424 if (this.attr_('xlabel')) {
425 var div
= document
.createElement("div");
426 div
.style
.position
= 'absolute';
427 div
.style
.bottom
= 0; // TODO(danvk): this is lazy. Calculate style.top.
428 div
.style
.left
= this.area
.x
+ 'px';
429 div
.style
.width
= this.area
.w
+ 'px';
430 div
.style
.height
= this.attr_('xLabelHeight') + 'px';
431 div
.style
.textAlign
= 'center';
432 div
.style
.fontSize
= (this.attr_('xLabelHeight') - 2) + 'px';
434 var class_div
= document
.createElement("div");
435 class_div
.className
= 'dygraph-label dygraph-xlabel';
436 class_div
.innerHTML
= this.attr_('xlabel');
437 div
.appendChild(class_div
);
438 this.container
.appendChild(div
);
439 this.chartLabels
.xlabel
= div
;
442 if (this.attr_('ylabel')) {
446 width
: this.attr_('yLabelWidth'),
449 // TODO(danvk): is this outer div actually necessary?
450 var div
= document
.createElement("div");
451 div
.style
.position
= 'absolute';
452 div
.style
.left
= box
.left
;
453 div
.style
.top
= box
.top
+ 'px';
454 div
.style
.width
= box
.width
+ 'px';
455 div
.style
.height
= box
.height
+ 'px';
456 div
.style
.fontSize
= (this.attr_('yLabelWidth') - 2) + 'px';
458 var inner_div
= document
.createElement("div");
459 inner_div
.style
.position
= 'absolute';
460 inner_div
.style
.width
= box
.height
+ 'px';
461 inner_div
.style
.height
= box
.width
+ 'px';
462 inner_div
.style
.top
= (box
.height
/ 2 - box.width / 2) + 'px';
463 inner_div
.style
.left
= (box
.width
/ 2 - box.height / 2) + 'px';
464 inner_div
.style
.textAlign
= 'center';
466 // CSS rotation is an HTML5 feature which is not standardized. Hence every
467 // browser has its own name for the CSS style.
468 inner_div
.style
.transform
= 'rotate(-90deg)'; // HTML5
469 inner_div
.style
.WebkitTransform
= 'rotate(-90deg)'; // Safari/Chrome
470 inner_div
.style
.MozTransform
= 'rotate(-90deg)'; // Firefox
471 inner_div
.style
.OTransform
= 'rotate(-90deg)'; // Opera
472 inner_div
.style
.msTransform
= 'rotate(-90deg)'; // IE9
474 if (typeof(document
.documentMode
) !== 'undefined' &&
475 document
.documentMode
< 9) {
476 // We're dealing w/ an old version of IE
, so we have to rotate the text
477 // using a BasicImage transform. This uses a different origin of rotation
478 // than HTML5 rotation (top left of div vs. its center).
479 inner_div
.style
.filter
=
480 'progid:DXImageTransform.Microsoft.BasicImage(rotation=3)';
481 inner_div
.style
.left
= '0px';
482 inner_div
.style
.top
= '0px';
485 var class_div
= document
.createElement("div");
486 class_div
.className
= 'dygraph-label dygraph-ylabel';
487 class_div
.innerHTML
= this.attr_('ylabel');
489 inner_div
.appendChild(class_div
);
490 div
.appendChild(inner_div
);
491 this.container
.appendChild(div
);
492 this.chartLabels
.ylabel
= div
;
497 DygraphCanvasRenderer
.prototype._renderAnnotations
= function() {
498 var annotationStyle
= {
499 "position": "absolute",
500 "fontSize": this.attr_('axisLabelFontSize') + "px",
505 var bindEvt
= function(eventName
, classEventName
, p
, self
) {
507 var a
= p
.annotation
;
508 if (a
.hasOwnProperty(eventName
)) {
509 a
[eventName
](a
, p
, self
.dygraph_
, e
);
510 } else if (self
.dygraph_
.attr_(classEventName
)) {
511 self
.dygraph_
.attr_(classEventName
)(a
, p
, self
.dygraph_
,e
);
516 // Get a list of point with annotations.
517 var points
= this.layout
.annotated_points
;
518 for (var i
= 0; i
< points
.length
; i
++) {
520 if (p
.canvasx
< this.area
.x
|| p
.canvasx
> this.area
.x
+ this.area
.w
) {
524 var a
= p
.annotation
;
526 if (a
.hasOwnProperty("tickHeight")) {
527 tick_height
= a
.tickHeight
;
530 var div
= document
.createElement("div");
531 for (var name
in annotationStyle
) {
532 if (annotationStyle
.hasOwnProperty(name
)) {
533 div
.style
[name
] = annotationStyle
[name
];
536 if (!a
.hasOwnProperty('icon')) {
537 div
.className
= "dygraphDefaultAnnotation";
539 if (a
.hasOwnProperty('cssClass')) {
540 div
.className
+= " " + a
.cssClass
;
543 var width
= a
.hasOwnProperty('width') ? a
.width
: 16;
544 var height
= a
.hasOwnProperty('height') ? a
.height
: 16;
545 if (a
.hasOwnProperty('icon')) {
546 var img
= document
.createElement("img");
550 div
.appendChild(img
);
551 } else if (p
.annotation
.hasOwnProperty('shortText')) {
552 div
.appendChild(document
.createTextNode(p
.annotation
.shortText
));
554 div
.style
.left
= (p
.canvasx
- width
/ 2) + "px";
555 if (a
.attachAtBottom
) {
556 div
.style
.top
= (this.area
.h
- height
- tick_height
) + "px";
558 div
.style
.top
= (p
.canvasy
- height
- tick_height
) + "px";
560 div
.style
.width
= width
+ "px";
561 div
.style
.height
= height
+ "px";
562 div
.title
= p
.annotation
.text
;
563 div
.style
.color
= this.colors
[p
.name
];
564 div
.style
.borderColor
= this.colors
[p
.name
];
567 Dygraph
.addEvent(div
, 'click',
568 bindEvt('clickHandler', 'annotationClickHandler', p
, this));
569 Dygraph
.addEvent(div
, 'mouseover',
570 bindEvt('mouseOverHandler', 'annotationMouseOverHandler', p
, this));
571 Dygraph
.addEvent(div
, 'mouseout',
572 bindEvt('mouseOutHandler', 'annotationMouseOutHandler', p
, this));
573 Dygraph
.addEvent(div
, 'dblclick',
574 bindEvt('dblClickHandler', 'annotationDblClickHandler', p
, this));
576 this.container
.appendChild(div
);
577 this.annotations
.push(div
);
579 var ctx
= this.elementContext
;
580 ctx
.strokeStyle
= this.colors
[p
.name
];
582 if (!a
.attachAtBottom
) {
583 ctx
.moveTo(p
.canvasx
, p
.canvasy
);
584 ctx
.lineTo(p
.canvasx
, p
.canvasy
- 2 - tick_height
);
586 ctx
.moveTo(p
.canvasx
, this.area
.h
);
587 ctx
.lineTo(p
.canvasx
, this.area
.h
- 2 - tick_height
);
596 * Overrides the CanvasRenderer method to draw error bars
598 DygraphCanvasRenderer
.prototype._renderLineChart
= function() {
599 // TODO(danvk): use this.attr_ for many of these.
600 var context
= this.elementContext
;
601 var fillAlpha
= this.attr_('fillAlpha');
602 var errorBars
= this.attr_("errorBars") || this.attr_("customBars");
603 var fillGraph
= this.attr_("fillGraph");
604 var stackedGraph
= this.attr_("stackedGraph");
605 var stepPlot
= this.attr_("stepPlot");
608 for (var name
in this.layout
.datasets
) {
609 if (this.layout
.datasets
.hasOwnProperty(name
)) {
613 var setCount
= setNames
.length
;
615 // TODO(danvk): Move this mapping into Dygraph and get it out of here.
617 for (var i
= 0; i
< setCount
; i
++) {
618 this.colors
[setNames
[i
]] = this.colorScheme_
[i
% this.colorScheme_
.length
];
623 for (var i
= 0; i
< this.layout
.points
.length
; i
++) {
624 var point
= this.layout
.points
[i
];
625 point
.canvasx
= this.area
.w
* point
.x
+ this.area
.x
;
626 point
.canvasy
= this.area
.h
* point
.y
+ this.area
.y
;
633 this.dygraph_
.warn("Can't use fillGraph option with error bars");
636 for (var i
= 0; i
< setCount
; i
++) {
637 var setName
= setNames
[i
];
638 var axis
= this.dygraph_
.axisPropertiesForSeries(setName
);
639 var color
= this.colors
[setName
];
641 // setup graphics context
645 var prevYs
= [-1, -1];
646 var yscale
= axis
.yscale
;
647 // should be same color as the lines but only 15% opaque.
648 var rgb
= new RGBColor(color
);
649 var err_color
= 'rgba(' + rgb
.r
+ ',' + rgb
.g
+ ',' + rgb
.b
+ ',' +
651 ctx
.fillStyle
= err_color
;
653 for (var j
= 0; j
< this.layout
.points
.length
; j
++) {
654 var point
= this.layout
.points
[j
];
655 if (point
.name
== setName
) {
656 if (!Dygraph
.isOK(point
.y
)) {
663 var newYs
= [ prevY
- point
.errorPlus
* yscale
,
664 prevY
+ point
.errorMinus
* yscale
];
667 var newYs
= [ point
.y
- point
.errorPlus
* yscale
,
668 point
.y
+ point
.errorMinus
* yscale
];
670 newYs
[0] = this.area
.h
* newYs
[0] + this.area
.y
;
671 newYs
[1] = this.area
.h
* newYs
[1] + this.area
.y
;
674 ctx
.moveTo(prevX
, newYs
[0]);
676 ctx
.moveTo(prevX
, prevYs
[0]);
678 ctx
.lineTo(point
.canvasx
, newYs
[0]);
679 ctx
.lineTo(point
.canvasx
, newYs
[1]);
681 ctx
.lineTo(prevX
, newYs
[1]);
683 ctx
.lineTo(prevX
, prevYs
[1]);
688 prevX
= point
.canvasx
;
693 } else if (fillGraph
) {
694 var baseline
= [] // for stacked graphs: baseline for filling
696 // process sets in reverse order (needed for stacked graphs)
697 for (var i
= setCount
- 1; i
>= 0; i
--) {
698 var setName
= setNames
[i
];
699 var color
= this.colors
[setName
];
700 var axis
= this.dygraph_
.axisPropertiesForSeries(setName
);
701 var axisY
= 1.0 + axis
.minyval
* axis
.yscale
;
702 if (axisY
< 0.0) axisY
= 0.0;
703 else if (axisY
> 1.0) axisY
= 1.0;
704 axisY
= this.area
.h
* axisY
+ this.area
.y
;
706 // setup graphics context
709 var prevYs
= [-1, -1];
710 var yscale
= axis
.yscale
;
711 // should be same color as the lines but only 15% opaque.
712 var rgb
= new RGBColor(color
);
713 var err_color
= 'rgba(' + rgb
.r
+ ',' + rgb
.g
+ ',' + rgb
.b
+ ',' +
715 ctx
.fillStyle
= err_color
;
717 for (var j
= 0; j
< this.layout
.points
.length
; j
++) {
718 var point
= this.layout
.points
[j
];
719 if (point
.name
== setName
) {
720 if (!Dygraph
.isOK(point
.y
)) {
726 lastY
= baseline
[point
.canvasx
];
727 if (lastY
=== undefined
) lastY
= axisY
;
728 baseline
[point
.canvasx
] = point
.canvasy
;
729 newYs
= [ point
.canvasy
, lastY
];
731 newYs
= [ point
.canvasy
, axisY
];
734 ctx
.moveTo(prevX
, prevYs
[0]);
736 ctx
.lineTo(point
.canvasx
, prevYs
[0]);
738 ctx
.lineTo(point
.canvasx
, newYs
[0]);
740 ctx
.lineTo(point
.canvasx
, newYs
[1]);
741 ctx
.lineTo(prevX
, prevYs
[1]);
745 prevX
= point
.canvasx
;
752 var isNullOrNaN
= function(x
) {
753 return (x
=== null || isNaN(x
));
756 for (var i
= 0; i
< setCount
; i
++) {
757 var setName
= setNames
[i
];
758 var color
= this.colors
[setName
];
759 var strokeWidth
= this.dygraph_
.attr_("strokeWidth", setName
);
761 // setup graphics context
763 var point
= this.layout
.points
[0];
764 var pointSize
= this.dygraph_
.attr_("pointSize", setName
);
765 var prevX
= null, prevY
= null;
766 var drawPoints
= this.dygraph_
.attr_("drawPoints", setName
);
767 var points
= this.layout
.points
;
768 for (var j
= 0; j
< points
.length
; j
++) {
769 var point
= points
[j
];
770 if (point
.name
== setName
) {
771 if (isNullOrNaN(point
.canvasy
)) {
772 if (stepPlot
&& prevX
!= null) {
773 // Draw a horizontal line to the start of the missing data
775 ctx
.strokeStyle
= color
;
776 ctx
.lineWidth
= this.attr_('strokeWidth');
777 ctx
.moveTo(prevX
, prevY
);
778 ctx
.lineTo(point
.canvasx
, prevY
);
781 // this will make us move to the next point, not draw a line to it.
782 prevX
= prevY
= null;
784 // A point is "isolated" if it is non-null but both the previous
785 // and next points are null.
786 var isIsolated
= (!prevX
&& (j
== points
.length
- 1 ||
787 isNullOrNaN(points
[j
+1].canvasy
)));
789 if (prevX
=== null) {
790 prevX
= point
.canvasx
;
791 prevY
= point
.canvasy
;
793 // TODO(danvk): figure out why this conditional is necessary.
796 ctx
.strokeStyle
= color
;
797 ctx
.lineWidth
= strokeWidth
;
798 ctx
.moveTo(prevX
, prevY
);
800 ctx
.lineTo(point
.canvasx
, prevY
);
802 prevX
= point
.canvasx
;
803 prevY
= point
.canvasy
;
804 ctx
.lineTo(prevX
, prevY
);
809 if (drawPoints
|| isIsolated
) {
811 ctx
.fillStyle
= color
;
812 ctx
.arc(point
.canvasx
, point
.canvasy
, pointSize
,
813 0, 2 * Math
.PI
, false);