3 * Copyright 2006 Dan Vanderkam (danvdk@gmail.com)
4 * MIT-licensed (http://opensource.org/licenses/MIT)
8 * @fileoverview Based on PlotKit.CanvasRenderer, but modified to meet the
11 * In particular, support for:
14 * - dygraphs attribute system
18 * The DygraphCanvasRenderer class does the actual rendering of the chart onto
19 * a canvas. It's based on PlotKit.CanvasRenderer.
20 * @param {Object} element The canvas to attach to
21 * @param {Object} elementContext The 2d context of the canvas (injected so it
22 * can be mocked for testing.)
23 * @param {Layout} layout The DygraphLayout object for this graph.
29 var DygraphCanvasRenderer
= function(dygraph
, element
, elementContext
, layout
) {
30 this.dygraph_
= dygraph
;
33 this.element
= element
;
34 this.elementContext
= elementContext
;
35 this.container
= this.element
.parentNode
;
37 this.height
= this.element
.height
;
38 this.width
= this.element
.width
;
40 // --- check whether everything is ok before we return
41 if (!this.isIE
&& !(DygraphCanvasRenderer
.isSupported(this.element
)))
42 throw "Canvas is not supported.";
45 this.xlabels
= new Array();
46 this.ylabels
= new Array();
47 this.annotations
= new Array();
48 this.chartLabels
= {};
50 this.area
= layout
.getPlotArea();
51 this.container
.style
.position
= "relative";
52 this.container
.style
.width
= this.width
+ "px";
54 // Set up a clipping area for the canvas (and the interaction canvas).
55 // This ensures that we don't overdraw.
56 if (this.dygraph_
.isUsingExcanvas_
) {
57 this._createIEClipArea();
59 // on Android 3 and 4, setting a clipping area on a canvas prevents it from
60 // displaying anything.
61 if (!Dygraph
.isAndroid()) {
62 var ctx
= this.dygraph_
.canvas_ctx_
;
64 ctx
.rect(this.area
.x
, this.area
.y
, this.area
.w
, this.area
.h
);
67 ctx
= this.dygraph_
.hidden_ctx_
;
69 ctx
.rect(this.area
.x
, this.area
.y
, this.area
.w
, this.area
.h
);
75 DygraphCanvasRenderer
.prototype.attr_
= function(x
) {
76 return this.dygraph_
.attr_(x
);
79 DygraphCanvasRenderer
.prototype.clear
= function() {
81 // VML takes a while to start up, so we just poll every this.IEDelay
83 if (this.clearDelay
) {
84 this.clearDelay
.cancel();
85 this.clearDelay
= null;
87 var context
= this.elementContext
;
90 // TODO(danvk): this is broken, since MochiKit.Async is gone.
91 this.clearDelay
= MochiKit
.Async
.wait(this.IEDelay
);
92 this.clearDelay
.addCallback(bind(this.clear
, this));
97 var context
= this.elementContext
;
98 context
.clearRect(0, 0, this.width
, this.height
);
100 for (var i
= 0; i
< this.xlabels
.length
; i
++) {
101 var el
= this.xlabels
[i
];
102 if (el
.parentNode
) el
.parentNode
.removeChild(el
);
104 for (var i
= 0; i
< this.ylabels
.length
; i
++) {
105 var el
= this.ylabels
[i
];
106 if (el
.parentNode
) el
.parentNode
.removeChild(el
);
108 for (var i
= 0; i
< this.annotations
.length
; i
++) {
109 var el
= this.annotations
[i
];
110 if (el
.parentNode
) el
.parentNode
.removeChild(el
);
112 for (var k
in this.chartLabels
) {
113 if (!this.chartLabels
.hasOwnProperty(k
)) continue;
114 var el
= this.chartLabels
[k
];
115 if (el
.parentNode
) el
.parentNode
.removeChild(el
);
117 this.xlabels
= new Array();
118 this.ylabels
= new Array();
119 this.annotations
= new Array();
120 this.chartLabels
= {};
124 DygraphCanvasRenderer
.isSupported
= function(canvasName
) {
127 if (typeof(canvasName
) == 'undefined' || canvasName
== null)
128 canvas
= document
.createElement("canvas");
131 var context
= canvas
.getContext("2d");
134 var ie
= navigator
.appVersion
.match(/MSIE (\d\.\d)/);
135 var opera
= (navigator
.userAgent
.toLowerCase().indexOf("opera") != -1);
136 if ((!ie
) || (ie
[1] < 6) || (opera
))
144 * @param { [String] } colors Array of color strings. Should have one entry for
145 * each series to be rendered.
147 DygraphCanvasRenderer
.prototype.setColors
= function(colors
) {
148 this.colorScheme_
= colors
;
152 * Draw an X/Y grid on top of the existing plot
154 DygraphCanvasRenderer
.prototype.render
= function() {
155 // Draw the new X/Y grid
. Lines appear crisper when pixels are rounded to
156 // half-integers. This prevents them from drawing in two rows/cols.
157 var ctx
= this.elementContext
;
158 function halfUp(x
){return Math
.round(x
)+0.5};
159 function halfDown(y
){return Math
.round(y
)-0.5};
161 if (this.attr_('underlayCallback')) {
162 // NOTE: we pass the dygraph object to this callback twice to avoid breaking
163 // users who expect a deprecated form of this callback.
164 this.attr_('underlayCallback')(ctx
, this.area
, this.dygraph_
, this.dygraph_
);
167 if (this.attr_('drawYGrid')) {
168 var ticks
= this.layout
.yticks
;
169 // TODO(konigsberg): I don't think these calls to save() have a corresponding restore().
171 ctx
.strokeStyle
= this.attr_('gridLineColor');
172 ctx
.lineWidth
= this.attr_('gridLineWidth');
173 for (var i
= 0; i
< ticks
.length
; i
++) {
174 // TODO(danvk): allow secondary axes to draw a grid, too.
175 if (ticks
[i
][0] != 0) continue;
176 var x
= halfUp(this.area
.x
);
177 var y
= halfDown(this.area
.y
+ ticks
[i
][1] * this.area
.h
);
180 ctx
.lineTo(x
+ this.area
.w
, y
);
186 if (this.attr_('drawXGrid')) {
187 var ticks
= this.layout
.xticks
;
189 ctx
.strokeStyle
= this.attr_('gridLineColor');
190 ctx
.lineWidth
= this.attr_('gridLineWidth');
191 for (var i
=0; i
<ticks
.length
; i
++) {
192 var x
= halfUp(this.area
.x
+ ticks
[i
][0] * this.area
.w
);
193 var y
= halfDown(this.area
.y
+ this.area
.h
);
196 ctx
.lineTo(x
, this.area
.y
);
202 // Do the ordinary rendering, as before
203 this._renderLineChart();
205 this._renderChartLabels();
206 this._renderAnnotations();
209 DygraphCanvasRenderer
.prototype._createIEClipArea
= function() {
210 var className
= 'dygraph-clip-div';
211 var graphDiv
= this.dygraph_
.graphDiv
;
213 // Remove old clip divs.
214 for (var i
= graphDiv
.childNodes
.length
-1; i
>= 0; i
--) {
215 if (graphDiv
.childNodes
[i
].className
== className
) {
216 graphDiv
.removeChild(graphDiv
.childNodes
[i
]);
220 // Determine background color to give clip divs.
221 var backgroundColor
= document
.bgColor
;
222 var element
= this.dygraph_
.graphDiv
;
223 while (element
!= document
) {
224 var bgcolor
= element
.currentStyle
.backgroundColor
;
225 if (bgcolor
&& bgcolor
!= 'transparent') {
226 backgroundColor
= bgcolor
;
229 element
= element
.parentNode
;
232 function createClipDiv(area
) {
233 if (area
.w
== 0 || area
.h
== 0) {
236 var elem
= document
.createElement('div');
237 elem
.className
= className
;
238 elem
.style
.backgroundColor
= backgroundColor
;
239 elem
.style
.position
= 'absolute';
240 elem
.style
.left
= area
.x
+ 'px';
241 elem
.style
.top
= area
.y
+ 'px';
242 elem
.style
.width
= area
.w
+ 'px';
243 elem
.style
.height
= area
.h
+ 'px';
244 graphDiv
.appendChild(elem
);
247 var plotArea
= this.area
;
249 createClipDiv({x
:0, y
:0, w
:plotArea
.x
, h
:this.height
});
251 createClipDiv({x
:plotArea
.x
, y
:0, w
:this.width
-plotArea
.x
, h
:plotArea
.y
});
253 createClipDiv({x
:plotArea
.x
+plotArea
.w
, y
:0, w
:this.width
-plotArea
.x
-plotArea
.w
, h
:this.height
});
255 createClipDiv({x
:plotArea
.x
, y
:plotArea
.y
+plotArea
.h
, w
:this.width
-plotArea
.x
, h
:this.height
-plotArea
.h
-plotArea
.y
});
258 DygraphCanvasRenderer
.prototype._renderAxis
= function() {
259 if (!this.attr_('drawXAxis') && !this.attr_('drawYAxis')) return;
261 // Round pixels to half-integer boundaries for crisper drawing.
262 function halfUp(x
){return Math
.round(x
)+0.5};
263 function halfDown(y
){return Math
.round(y
)-0.5};
265 var context
= this.elementContext
;
268 position
: "absolute",
269 fontSize
: this.attr_('axisLabelFontSize') + "px",
271 color
: this.attr_('axisLabelColor'),
272 width
: this.attr_('axisLabelWidth') + "px",
273 // height: this.attr_('axisLabelFontSize') + 2 + "px",
274 lineHeight
: "normal", // Something other than "normal" line-height screws up label positioning.
277 var makeDiv
= function(txt
, axis
, prec_axis
) {
278 var div
= document
.createElement("div");
279 for (var name
in labelStyle
) {
280 if (labelStyle
.hasOwnProperty(name
)) {
281 div
.style
[name
] = labelStyle
[name
];
284 var inner_div
= document
.createElement("div");
285 inner_div
.className
= 'dygraph-axis-label' +
286 ' dygraph-axis-label-' + axis
+
287 (prec_axis
? ' dygraph-axis-label-' + prec_axis
: '');
288 inner_div
.appendChild(document
.createTextNode(txt
));
289 div
.appendChild(inner_div
);
295 context
.strokeStyle
= this.attr_('axisLineColor');
296 context
.lineWidth
= this.attr_('axisLineWidth');
298 if (this.attr_('drawYAxis')) {
299 if (this.layout
.yticks
&& this.layout
.yticks
.length
> 0) {
300 var num_axes
= this.dygraph_
.numAxes();
301 for (var i
= 0; i
< this.layout
.yticks
.length
; i
++) {
302 var tick
= this.layout
.yticks
[i
];
303 if (typeof(tick
) == "function") return;
306 var prec_axis
= 'y1';
307 if (tick
[0] == 1) { // right-side y-axis
308 x
= this.area
.x
+ this.area
.w
;
312 var y
= this.area
.y
+ tick
[1] * this.area
.h
;
314 /* Tick marks are currently clipped, so don't bother drawing them.
316 context.moveTo(halfUp(x), halfDown(y));
317 context.lineTo(halfUp(x - sgn * this.attr_('axisTickSize')), halfDown(y));
322 var label
= makeDiv(tick
[2], 'y', num_axes
== 2 ? prec_axis
: null);
323 var top
= (y
- this.attr_('axisLabelFontSize') / 2);
324 if (top
< 0) top
= 0;
326 if (top
+ this.attr_('axisLabelFontSize') + 3 > this.height
) {
327 label
.style
.bottom
= "0px";
329 label
.style
.top
= top
+ "px";
332 label
.style
.left
= (this.area
.x
- this.attr_('yAxisLabelWidth') - this.attr_('axisTickSize')) + "px";
333 label
.style
.textAlign
= "right";
334 } else if (tick
[0] == 1) {
335 label
.style
.left
= (this.area
.x
+ this.area
.w
+
336 this.attr_('axisTickSize')) + "px";
337 label
.style
.textAlign
= "left";
339 label
.style
.width
= this.attr_('yAxisLabelWidth') + "px";
340 this.container
.appendChild(label
);
341 this.ylabels
.push(label
);
344 // The lowest tick on the y-axis often overlaps with the leftmost
345 // tick on the x-axis. Shift the bottom tick up a little bit to
346 // compensate if necessary.
347 var bottomTick
= this.ylabels
[0];
348 var fontSize
= this.attr_('axisLabelFontSize');
349 var bottom
= parseInt(bottomTick
.style
.top
) + fontSize
;
350 if (bottom
> this.height
- fontSize
) {
351 bottomTick
.style
.top
= (parseInt(bottomTick
.style
.top
) -
352 fontSize
/ 2) + "px";
356 // draw a vertical line on the left to separate the chart from the labels.
358 context
.moveTo(halfUp(this.area
.x
), halfDown(this.area
.y
));
359 context
.lineTo(halfUp(this.area
.x
), halfDown(this.area
.y
+ this.area
.h
));
363 // if there's a secondary y-axis, draw a vertical line for that, too.
364 if (this.dygraph_
.numAxes() == 2) {
366 context
.moveTo(halfDown(this.area
.x
+ this.area
.w
), halfDown(this.area
.y
));
367 context
.lineTo(halfDown(this.area
.x
+ this.area
.w
), halfDown(this.area
.y
+ this.area
.h
));
373 if (this.attr_('drawXAxis')) {
374 if (this.layout
.xticks
) {
375 for (var i
= 0; i
< this.layout
.xticks
.length
; i
++) {
376 var tick
= this.layout
.xticks
[i
];
377 if (typeof(dataset
) == "function") return;
379 var x
= this.area
.x
+ tick
[0] * this.area
.w
;
380 var y
= this.area
.y
+ this.area
.h
;
382 /* Tick marks are currently clipped, so don't bother drawing them.
384 context.moveTo(halfUp(x), halfDown(y));
385 context.lineTo(halfUp(x), halfDown(y + this.attr_('axisTickSize')));
390 var label
= makeDiv(tick
[1], 'x');
391 label
.style
.textAlign
= "center";
392 label
.style
.top
= (y
+ this.attr_('axisTickSize')) + 'px';
394 var left
= (x
- this.attr_('axisLabelWidth')/2);
395 if (left
+ this.attr_('axisLabelWidth') > this.width
) {
396 left
= this.width
- this.attr_('xAxisLabelWidth');
397 label
.style
.textAlign
= "right";
401 label
.style
.textAlign
= "left";
404 label
.style
.left
= left
+ "px";
405 label
.style
.width
= this.attr_('xAxisLabelWidth') + "px";
406 this.container
.appendChild(label
);
407 this.xlabels
.push(label
);
412 context
.moveTo(halfUp(this.area
.x
), halfDown(this.area
.y
+ this.area
.h
));
413 context
.lineTo(halfUp(this.area
.x
+ this.area
.w
), halfDown(this.area
.y
+ this.area
.h
));
422 DygraphCanvasRenderer
.prototype._renderChartLabels
= function() {
423 // Generate divs for the chart title, xlabel and ylabel.
424 // Space for these divs has already been taken away from the charting area in
425 // the DygraphCanvasRenderer constructor.
426 if (this.attr_('title')) {
427 var div
= document
.createElement("div");
428 div
.style
.position
= 'absolute';
429 div
.style
.top
= '0px';
430 div
.style
.left
= this.area
.x
+ 'px';
431 div
.style
.width
= this.area
.w
+ 'px';
432 div
.style
.height
= this.attr_('titleHeight') + 'px';
433 div
.style
.textAlign
= 'center';
434 div
.style
.fontSize
= (this.attr_('titleHeight') - 8) + 'px';
435 div
.style
.fontWeight
= 'bold';
436 var class_div
= document
.createElement("div");
437 class_div
.className
= 'dygraph-label dygraph-title';
438 class_div
.innerHTML
= this.attr_('title');
439 div
.appendChild(class_div
);
440 this.container
.appendChild(div
);
441 this.chartLabels
.title
= div
;
444 if (this.attr_('xlabel')) {
445 var div
= document
.createElement("div");
446 div
.style
.position
= 'absolute';
447 div
.style
.bottom
= 0; // TODO(danvk): this is lazy. Calculate style.top.
448 div
.style
.left
= this.area
.x
+ 'px';
449 div
.style
.width
= this.area
.w
+ 'px';
450 div
.style
.height
= this.attr_('xLabelHeight') + 'px';
451 div
.style
.textAlign
= 'center';
452 div
.style
.fontSize
= (this.attr_('xLabelHeight') - 2) + 'px';
454 var class_div
= document
.createElement("div");
455 class_div
.className
= 'dygraph-label dygraph-xlabel';
456 class_div
.innerHTML
= this.attr_('xlabel');
457 div
.appendChild(class_div
);
458 this.container
.appendChild(div
);
459 this.chartLabels
.xlabel
= div
;
462 if (this.attr_('ylabel')) {
466 width
: this.attr_('yLabelWidth'),
469 // TODO(danvk): is this outer div actually necessary?
470 var div
= document
.createElement("div");
471 div
.style
.position
= 'absolute';
472 div
.style
.left
= box
.left
;
473 div
.style
.top
= box
.top
+ 'px';
474 div
.style
.width
= box
.width
+ 'px';
475 div
.style
.height
= box
.height
+ 'px';
476 div
.style
.fontSize
= (this.attr_('yLabelWidth') - 2) + 'px';
478 var inner_div
= document
.createElement("div");
479 inner_div
.style
.position
= 'absolute';
480 inner_div
.style
.width
= box
.height
+ 'px';
481 inner_div
.style
.height
= box
.width
+ 'px';
482 inner_div
.style
.top
= (box
.height
/ 2 - box.width / 2) + 'px';
483 inner_div
.style
.left
= (box
.width
/ 2 - box.height / 2) + 'px';
484 inner_div
.style
.textAlign
= 'center';
486 // CSS rotation is an HTML5 feature which is not standardized. Hence every
487 // browser has its own name for the CSS style.
488 inner_div
.style
.transform
= 'rotate(-90deg)'; // HTML5
489 inner_div
.style
.WebkitTransform
= 'rotate(-90deg)'; // Safari/Chrome
490 inner_div
.style
.MozTransform
= 'rotate(-90deg)'; // Firefox
491 inner_div
.style
.OTransform
= 'rotate(-90deg)'; // Opera
492 inner_div
.style
.msTransform
= 'rotate(-90deg)'; // IE9
494 if (typeof(document
.documentMode
) !== 'undefined' &&
495 document
.documentMode
< 9) {
496 // We're dealing w/ an old version of IE
, so we have to rotate the text
497 // using a BasicImage transform. This uses a different origin of rotation
498 // than HTML5 rotation (top left of div vs. its center).
499 inner_div
.style
.filter
=
500 'progid:DXImageTransform.Microsoft.BasicImage(rotation=3)';
501 inner_div
.style
.left
= '0px';
502 inner_div
.style
.top
= '0px';
505 var class_div
= document
.createElement("div");
506 class_div
.className
= 'dygraph-label dygraph-ylabel';
507 class_div
.innerHTML
= this.attr_('ylabel');
509 inner_div
.appendChild(class_div
);
510 div
.appendChild(inner_div
);
511 this.container
.appendChild(div
);
512 this.chartLabels
.ylabel
= div
;
517 DygraphCanvasRenderer
.prototype._renderAnnotations
= function() {
518 var annotationStyle
= {
519 "position": "absolute",
520 "fontSize": this.attr_('axisLabelFontSize') + "px",
525 var bindEvt
= function(eventName
, classEventName
, p
, self
) {
527 var a
= p
.annotation
;
528 if (a
.hasOwnProperty(eventName
)) {
529 a
[eventName
](a
, p
, self
.dygraph_
, e
);
530 } else if (self
.dygraph_
.attr_(classEventName
)) {
531 self
.dygraph_
.attr_(classEventName
)(a
, p
, self
.dygraph_
,e
);
536 // Get a list of point with annotations.
537 var points
= this.layout
.annotated_points
;
538 for (var i
= 0; i
< points
.length
; i
++) {
540 if (p
.canvasx
< this.area
.x
|| p
.canvasx
> this.area
.x
+ this.area
.w
) {
544 var a
= p
.annotation
;
546 if (a
.hasOwnProperty("tickHeight")) {
547 tick_height
= a
.tickHeight
;
550 var div
= document
.createElement("div");
551 for (var name
in annotationStyle
) {
552 if (annotationStyle
.hasOwnProperty(name
)) {
553 div
.style
[name
] = annotationStyle
[name
];
556 if (!a
.hasOwnProperty('icon')) {
557 div
.className
= "dygraphDefaultAnnotation";
559 if (a
.hasOwnProperty('cssClass')) {
560 div
.className
+= " " + a
.cssClass
;
563 var width
= a
.hasOwnProperty('width') ? a
.width
: 16;
564 var height
= a
.hasOwnProperty('height') ? a
.height
: 16;
565 if (a
.hasOwnProperty('icon')) {
566 var img
= document
.createElement("img");
570 div
.appendChild(img
);
571 } else if (p
.annotation
.hasOwnProperty('shortText')) {
572 div
.appendChild(document
.createTextNode(p
.annotation
.shortText
));
574 div
.style
.left
= (p
.canvasx
- width
/ 2) + "px";
575 if (a
.attachAtBottom
) {
576 div
.style
.top
= (this.area
.h
- height
- tick_height
) + "px";
578 div
.style
.top
= (p
.canvasy
- height
- tick_height
) + "px";
580 div
.style
.width
= width
+ "px";
581 div
.style
.height
= height
+ "px";
582 div
.title
= p
.annotation
.text
;
583 div
.style
.color
= this.colors
[p
.name
];
584 div
.style
.borderColor
= this.colors
[p
.name
];
587 Dygraph
.addEvent(div
, 'click',
588 bindEvt('clickHandler', 'annotationClickHandler', p
, this));
589 Dygraph
.addEvent(div
, 'mouseover',
590 bindEvt('mouseOverHandler', 'annotationMouseOverHandler', p
, this));
591 Dygraph
.addEvent(div
, 'mouseout',
592 bindEvt('mouseOutHandler', 'annotationMouseOutHandler', p
, this));
593 Dygraph
.addEvent(div
, 'dblclick',
594 bindEvt('dblClickHandler', 'annotationDblClickHandler', p
, this));
596 this.container
.appendChild(div
);
597 this.annotations
.push(div
);
599 var ctx
= this.elementContext
;
600 ctx
.strokeStyle
= this.colors
[p
.name
];
602 if (!a
.attachAtBottom
) {
603 ctx
.moveTo(p
.canvasx
, p
.canvasy
);
604 ctx
.lineTo(p
.canvasx
, p
.canvasy
- 2 - tick_height
);
606 ctx
.moveTo(p
.canvasx
, this.area
.h
);
607 ctx
.lineTo(p
.canvasx
, this.area
.h
- 2 - tick_height
);
616 * Overrides the CanvasRenderer method to draw error bars
618 DygraphCanvasRenderer
.prototype._renderLineChart
= function() {
619 var isNullOrNaN
= function(x
) {
620 return (x
=== null || isNaN(x
));
623 // TODO(danvk): use this.attr_ for many of these.
624 var context
= this.elementContext
;
625 var fillAlpha
= this.attr_('fillAlpha');
626 var errorBars
= this.attr_("errorBars") || this.attr_("customBars");
627 var fillGraph
= this.attr_("fillGraph");
628 var stackedGraph
= this.attr_("stackedGraph");
629 var stepPlot
= this.attr_("stepPlot");
630 var points
= this.layout
.points
;
631 var pointsLength
= points
.length
;
634 for (var name
in this.layout
.datasets
) {
635 if (this.layout
.datasets
.hasOwnProperty(name
)) {
639 var setCount
= setNames
.length
;
641 // TODO(danvk): Move this mapping into Dygraph and get it out of here.
643 for (var i
= 0; i
< setCount
; i
++) {
644 this.colors
[setNames
[i
]] = this.colorScheme_
[i
% this.colorScheme_
.length
];
649 for (var i
= pointsLength
; i
--;) {
650 var point
= points
[i
];
651 point
.canvasx
= this.area
.w
* point
.x
+ this.area
.x
;
652 point
.canvasy
= this.area
.h
* point
.y
+ this.area
.y
;
659 this.dygraph_
.warn("Can't use fillGraph option with error bars");
662 for (var i
= 0; i
< setCount
; i
++) {
663 var setName
= setNames
[i
];
664 var axis
= this.dygraph_
.axisPropertiesForSeries(setName
);
665 var color
= this.colors
[setName
];
667 // setup graphics context
671 var prevYs
= [-1, -1];
672 var yscale
= axis
.yscale
;
673 // should be same color as the lines but only 15% opaque.
674 var rgb
= new RGBColor(color
);
675 var err_color
= 'rgba(' + rgb
.r
+ ',' + rgb
.g
+ ',' + rgb
.b
+ ',' +
677 ctx
.fillStyle
= err_color
;
679 for (var j
= 0; j
< pointsLength
; j
++) {
680 var point
= points
[j
];
681 if (point
.name
== setName
) {
682 if (!Dygraph
.isOK(point
.y
)) {
689 var newYs
= [ point
.y_bottom
, point
.y_top
];
692 var newYs
= [ point
.y_bottom
, point
.y_top
];
694 newYs
[0] = this.area
.h
* newYs
[0] + this.area
.y
;
695 newYs
[1] = this.area
.h
* newYs
[1] + this.area
.y
;
698 ctx
.moveTo(prevX
, newYs
[0]);
700 ctx
.moveTo(prevX
, prevYs
[0]);
702 ctx
.lineTo(point
.canvasx
, newYs
[0]);
703 ctx
.lineTo(point
.canvasx
, newYs
[1]);
705 ctx
.lineTo(prevX
, newYs
[1]);
707 ctx
.lineTo(prevX
, prevYs
[1]);
712 prevX
= point
.canvasx
;
717 } else if (fillGraph
) {
718 var baseline
= [] // for stacked graphs: baseline for filling
720 // process sets in reverse order (needed for stacked graphs)
721 for (var i
= setCount
- 1; i
>= 0; i
--) {
722 var setName
= setNames
[i
];
723 var color
= this.colors
[setName
];
724 var axis
= this.dygraph_
.axisPropertiesForSeries(setName
);
725 var axisY
= 1.0 + axis
.minyval
* axis
.yscale
;
726 if (axisY
< 0.0) axisY
= 0.0;
727 else if (axisY
> 1.0) axisY
= 1.0;
728 axisY
= this.area
.h
* axisY
+ this.area
.y
;
730 // setup graphics context
733 var prevYs
= [-1, -1];
734 var yscale
= axis
.yscale
;
735 // should be same color as the lines but only 15% opaque.
736 var rgb
= new RGBColor(color
);
737 var err_color
= 'rgba(' + rgb
.r
+ ',' + rgb
.g
+ ',' + rgb
.b
+ ',' +
739 ctx
.fillStyle
= err_color
;
741 for (var j
= 0; j
< pointsLength
; j
++) {
742 var point
= points
[j
];
743 if (point
.name
== setName
) {
744 if (!Dygraph
.isOK(point
.y
)) {
750 var lastY
= baseline
[point
.canvasx
];
751 if (lastY
=== undefined
) lastY
= axisY
;
752 baseline
[point
.canvasx
] = point
.canvasy
;
753 newYs
= [ point
.canvasy
, lastY
];
755 newYs
= [ point
.canvasy
, axisY
];
758 ctx
.moveTo(prevX
, prevYs
[0]);
760 ctx
.lineTo(point
.canvasx
, prevYs
[0]);
762 ctx
.lineTo(point
.canvasx
, newYs
[0]);
764 ctx
.lineTo(point
.canvasx
, newYs
[1]);
765 ctx
.lineTo(prevX
, prevYs
[1]);
769 prevX
= point
.canvasx
;
776 // Drawing the lines.
777 var firstIndexInSet
= 0;
778 var afterLastIndexInSet
= 0;
780 for (var i
= 0; i
< setCount
; i
+= 1) {
781 setLength
= this.layout
.setPointsLengths
[i
];
782 afterLastIndexInSet
+= setLength
;
783 var setName
= setNames
[i
];
784 var color
= this.colors
[setName
];
785 var strokeWidth
= this.dygraph_
.attr_("strokeWidth", setName
);
787 // setup graphics context
789 var pointSize
= this.dygraph_
.attr_("pointSize", setName
);
790 var prevX
= null, prevY
= null;
791 var drawPoints
= this.dygraph_
.attr_("drawPoints", setName
);
792 for (var j
= firstIndexInSet
; j
< afterLastIndexInSet
; j
++) {
793 var point
= points
[j
];
794 if (isNullOrNaN(point
.canvasy
)) {
795 if (stepPlot
&& prevX
!= null) {
796 // Draw a horizontal line to the start of the missing data
798 ctx
.strokeStyle
= color
;
799 ctx
.lineWidth
= this.attr_('strokeWidth');
800 ctx
.moveTo(prevX
, prevY
);
801 ctx
.lineTo(point
.canvasx
, prevY
);
804 // this will make us move to the next point, not draw a line to it.
805 prevX
= prevY
= null;
807 // A point is "isolated" if it is non-null but both the previous
808 // and next points are null.
809 var isIsolated
= (!prevX
&& (j
== points
.length
- 1 ||
810 isNullOrNaN(points
[j
+1].canvasy
)));
811 if (prevX
=== null) {
812 prevX
= point
.canvasx
;
813 prevY
= point
.canvasy
;
815 // Skip over points that will be drawn in the same pixel.
816 if (Math
.round(prevX
) == Math
.round(point
.canvasx
) &&
817 Math
.round(prevY
) == Math
.round(point
.canvasy
)) {
820 // TODO(antrob): skip over points that lie on a line that is already
821 // going to be drawn. There is no need to have more than 2
822 // consecutive points that are collinear.
825 ctx
.strokeStyle
= color
;
826 ctx
.lineWidth
= strokeWidth
;
827 ctx
.moveTo(prevX
, prevY
);
829 ctx
.lineTo(point
.canvasx
, prevY
);
831 prevX
= point
.canvasx
;
832 prevY
= point
.canvasy
;
833 ctx
.lineTo(prevX
, prevY
);
838 if (drawPoints
|| isIsolated
) {
840 ctx
.fillStyle
= color
;
841 ctx
.arc(point
.canvasx
, point
.canvasy
, pointSize
,
842 0, 2 * Math
.PI
, false);
847 firstIndexInSet
= afterLastIndexInSet
;