edce7a751170c83e6ca35eb94a3ee86a7f973dcc
1 // Copyright 2006 Dan Vanderkam (danvdk@gmail.com)
2 // All Rights Reserved.
5 * @fileoverview Based on PlotKit, but modified to meet the needs of dygraphs.
6 * In particular, support for:
9 * - dygraphs attribute system
13 * Creates a new DygraphLayout object.
14 * @param {Object} options Options for PlotKit.Layout
15 * @return {Object} The DygraphLayout object
17 DygraphLayout
= function(dygraph
, options
) {
18 this.dygraph_
= dygraph
;
19 this.options
= {}; // TODO(danvk): remove, use attr_ instead.
20 Dygraph
.update(this.options
, options
? options
: {});
21 this.datasets
= new Array();
22 this.annotations
= new Array()
25 DygraphLayout
.prototype.attr_
= function(name
) {
26 return this.dygraph_
.attr_(name
);
29 DygraphLayout
.prototype.addDataset
= function(setname
, set_xy
) {
30 this.datasets
[setname
] = set_xy
;
33 DygraphLayout
.prototype.setAnnotations
= function(ann
) {
34 // The Dygraph object's annotations aren't parsed. We parse them here and
36 var parse
= this.attr_('xValueParser');
37 for (var i
= 0; i
< ann
.length
; i
++) {
39 if (!ann
[i
].xval
&& !ann
[i
].x
) {
40 this.dygraph_
.error("Annotations must have an 'x' property");
44 !(ann
[i
].hasOwnProperty('width') &&
45 ann
[i
].hasOwnProperty('height'))) {
46 this.dygraph_
.error("Must set width and height when setting " +
47 "annotation.icon property");
50 Dygraph
.update(a
, ann
[i
]);
51 if (!a
.xval
) a
.xval
= parse(a
.x
);
52 this.annotations
.push(a
);
56 DygraphLayout
.prototype.evaluate
= function() {
57 this._evaluateLimits();
58 this._evaluateLineCharts();
59 this._evaluateLineTicks();
60 this._evaluateAnnotations();
63 DygraphLayout
.prototype._evaluateLimits
= function() {
64 this.minxval
= this.maxxval
= null;
65 if (this.options
.dateWindow
) {
66 this.minxval
= this.options
.dateWindow
[0];
67 this.maxxval
= this.options
.dateWindow
[1];
69 for (var name
in this.datasets
) {
70 if (!this.datasets
.hasOwnProperty(name
)) continue;
71 var series
= this.datasets
[name
];
72 var x1
= series
[0][0];
73 if (!this.minxval
|| x1
< this.minxval
) this.minxval
= x1
;
75 var x2
= series
[series
.length
- 1][0];
76 if (!this.maxxval
|| x2
> this.maxxval
) this.maxxval
= x2
;
79 this.xrange
= this.maxxval
- this.minxval
;
80 this.xscale
= (this.xrange
!= 0 ? 1/this.xrange
: 1.0);
82 for (var i
= 0; i
< this.options
.yAxes
.length
; i
++) {
83 var axis
= this.options
.yAxes
[i
];
84 axis
.minyval
= axis
.valueRange
[0];
85 axis
.maxyval
= axis
.valueRange
[1];
86 axis
.yrange
= axis
.maxyval
- axis
.minyval
;
87 axis
.yscale
= (axis
.yrange
!= 0 ? 1.0 / axis
.yrange
: 1.0);
91 DygraphLayout
.prototype._evaluateLineCharts
= function() {
93 this.points
= new Array();
94 for (var setName
in this.datasets
) {
95 if (!this.datasets
.hasOwnProperty(setName
)) continue;
97 var dataset
= this.datasets
[setName
];
98 var axis
= this.options
.yAxes
[this.options
.seriesToAxisMap
[setName
]];
100 for (var j
= 0; j
< dataset
.length
; j
++) {
101 var item
= dataset
[j
];
104 x
: ((parseFloat(item
[0]) - this.minxval
) * this.xscale
),
105 y
: 1.0 - ((parseFloat(item
[1]) - axis
.minyval
) * axis
.yscale
),
106 xval
: parseFloat(item
[0]),
107 yval
: parseFloat(item
[1]),
111 // limit the x, y values so they do not overdraw
112 if (point
.y
<= 0.0) {
115 if (point
.y
>= 1.0) {
118 this.points
.push(point
);
123 DygraphLayout
.prototype._evaluateLineTicks
= function() {
124 this.xticks
= new Array();
125 for (var i
= 0; i
< this.options
.xTicks
.length
; i
++) {
126 var tick
= this.options
.xTicks
[i
];
127 var label
= tick
.label
;
128 var pos
= this.xscale
* (tick
.v
- this.minxval
);
129 if ((pos
>= 0.0) && (pos
<= 1.0)) {
130 this.xticks
.push([pos
, label
]);
134 this.yticks
= new Array();
135 for (var i
= 0; i
< this.options
.yAxes
.length
; i
++ ) {
136 var axis
= this.options
.yAxes
[i
];
137 for (var j
= 0; j
< axis
.ticks
.length
; j
++) {
138 var tick
= axis
.ticks
[j
];
139 var label
= tick
.label
;
140 var pos
= 1.0 - (axis
.yscale
* (tick
.v
- axis
.minyval
));
141 if ((pos
>= 0.0) && (pos
<= 1.0)) {
142 this.yticks
.push([i
, pos
, label
]);
150 * Behaves the same way as PlotKit.Layout, but also copies the errors
153 DygraphLayout
.prototype.evaluateWithError
= function() {
155 if (!this.options
.errorBars
) return;
157 // Copy over the error terms
158 var i
= 0; // index in this.points
159 for (var setName
in this.datasets
) {
160 if (!this.datasets
.hasOwnProperty(setName
)) continue;
162 var dataset
= this.datasets
[setName
];
163 for (var j
= 0; j
< dataset
.length
; j
++, i
++) {
164 var item
= dataset
[j
];
165 var xv
= parseFloat(item
[0]);
166 var yv
= parseFloat(item
[1]);
168 if (xv
== this.points
[i
].xval
&&
169 yv
== this.points
[i
].yval
) {
170 this.points
[i
].errorMinus
= parseFloat(item
[2]);
171 this.points
[i
].errorPlus
= parseFloat(item
[3]);
177 DygraphLayout
.prototype._evaluateAnnotations
= function() {
178 // Add the annotations to the point to which they belong.
179 // Make a map from (setName, xval) to annotation for quick lookups.
180 var annotations
= {};
181 for (var i
= 0; i
< this.annotations
.length
; i
++) {
182 var a
= this.annotations
[i
];
183 annotations
[a
.xval
+ "," + a
.series
] = a
;
186 this.annotated_points
= [];
187 for (var i
= 0; i
< this.points
.length
; i
++) {
188 var p
= this.points
[i
];
189 var k
= p
.xval
+ "," + p
.name
;
190 if (k
in annotations
) {
191 p
.annotation
= annotations
[k
];
192 this.annotated_points
.push(p
);
198 * Convenience function to remove all the data sets from a graph
200 DygraphLayout
.prototype.removeAllDatasets
= function() {
201 delete this.datasets
;
202 this.datasets
= new Array();
206 * Change the values of various layout options
207 * @param {Object} new_options an associative array of new properties
209 DygraphLayout
.prototype.updateOptions
= function(new_options
) {
210 Dygraph
.update(this.options
, new_options
? new_options
: {});
213 // Subclass PlotKit.CanvasRenderer to add:
214 // 1. X/Y grid overlay
215 // 2. Ability to draw error bars (if required)
218 * Sets some PlotKit.CanvasRenderer options
219 * @param {Object} element The canvas to attach to
220 * @param {Layout} layout The DygraphLayout object for this graph.
221 * @param {Object} options Options to pass on to CanvasRenderer
223 DygraphCanvasRenderer
= function(dygraph
, element
, layout
, options
) {
224 // TODO(danvk): remove options, just use dygraph.attr_.
225 this.dygraph_
= dygraph
;
232 "axisLineColor": "black",
233 "axisLineWidth": 0.5,
235 "axisLabelColor": "black",
236 "axisLabelFont": "Arial",
237 "axisLabelFontSize": 9,
238 "axisLabelWidth": 50,
241 "gridLineColor": "rgb(128,128,128)",
243 "underlayCallback": null
245 Dygraph
.update(this.options
, options
);
247 this.layout
= layout
;
248 this.element
= element
;
249 this.container
= this.element
.parentNode
;
251 this.height
= this.element
.height
;
252 this.width
= this.element
.width
;
254 // --- check whether everything is ok before we return
255 if (!this.isIE
&& !(DygraphCanvasRenderer
.isSupported(this.element
)))
256 throw "Canvas is not supported.";
259 this.xlabels
= new Array();
260 this.ylabels
= new Array();
261 this.annotations
= new Array();
263 // TODO(danvk): consider all axes in this computation.
265 // TODO(danvk): per-axis setting.
266 x
: this.options
.yAxisLabelWidth
+ 2 * this.options
.axisTickSize
,
269 this.area
.w
= this.width
- this.area
.x
- this.options
.rightGap
;
270 this.area
.h
= this.height
- this.options
.axisLabelFontSize
-
271 2 * this.options
.axisTickSize
;
273 this.container
.style
.position
= "relative";
274 this.container
.style
.width
= this.width
+ "px";
277 DygraphCanvasRenderer
.prototype.clear
= function() {
279 // VML takes a while to start up, so we just poll every this.IEDelay
281 if (this.clearDelay
) {
282 this.clearDelay
.cancel();
283 this.clearDelay
= null;
285 var context
= this.element
.getContext("2d");
288 // TODO(danvk): this is broken, since MochiKit.Async is gone.
289 this.clearDelay
= MochiKit
.Async
.wait(this.IEDelay
);
290 this.clearDelay
.addCallback(bind(this.clear
, this));
295 var context
= this.element
.getContext("2d");
296 context
.clearRect(0, 0, this.width
, this.height
);
298 for (var i
= 0; i
< this.xlabels
.length
; i
++) {
299 var el
= this.xlabels
[i
];
300 el
.parentNode
.removeChild(el
);
302 for (var i
= 0; i
< this.ylabels
.length
; i
++) {
303 var el
= this.ylabels
[i
];
304 el
.parentNode
.removeChild(el
);
306 for (var i
= 0; i
< this.annotations
.length
; i
++) {
307 var el
= this.annotations
[i
];
308 el
.parentNode
.removeChild(el
);
310 this.xlabels
= new Array();
311 this.ylabels
= new Array();
312 this.annotations
= new Array();
316 DygraphCanvasRenderer
.isSupported
= function(canvasName
) {
319 if (typeof(canvasName
) == 'undefined' || canvasName
== null)
320 canvas
= document
.createElement("canvas");
323 var context
= canvas
.getContext("2d");
326 var ie
= navigator
.appVersion
.match(/MSIE (\d\.\d)/);
327 var opera
= (navigator
.userAgent
.toLowerCase().indexOf("opera") != -1);
328 if ((!ie
) || (ie
[1] < 6) || (opera
))
336 * Draw an X/Y grid on top of the existing plot
338 DygraphCanvasRenderer
.prototype.render
= function() {
339 // Shrink the drawing area to accomodate additional y-axes.
340 if (this.layout
.options
.yAxes
.length
== 2) {
341 // TODO(danvk): per-axis setting.
342 this.area
.w
-= (this.options
.yAxisLabelWidth
+ 2 * this.options
.axisTickSize
);
343 } else if (this.layout
.options
.yAxes
.length
> 2) {
344 this.dygraph_
.error("Only two y-axes are supported at this time. (Trying " +
345 "to use " + this.layout
.yAxes
.length
+ ")");
348 // Draw the new X/Y grid
349 var ctx
= this.element
.getContext("2d");
351 if (this.options
.underlayCallback
) {
352 this.options
.underlayCallback(ctx
, this.area
, this.layout
, this.dygraph_
);
355 if (this.options
.drawYGrid
) {
356 var ticks
= this.layout
.yticks
;
358 ctx
.strokeStyle
= this.options
.gridLineColor
;
359 ctx
.lineWidth
= this.options
.axisLineWidth
;
360 for (var i
= 0; i
< ticks
.length
; i
++) {
361 if (ticks
[i
][0] != 0) continue; // TODO(danvk): per-axis property
363 var y
= this.area
.y
+ ticks
[i
][1] * this.area
.h
;
366 ctx
.lineTo(x
+ this.area
.w
, y
);
372 if (this.options
.drawXGrid
) {
373 var ticks
= this.layout
.xticks
;
375 ctx
.strokeStyle
= this.options
.gridLineColor
;
376 ctx
.lineWidth
= this.options
.axisLineWidth
;
377 for (var i
=0; i
<ticks
.length
; i
++) {
378 var x
= this.area
.x
+ ticks
[i
][1] * this.area
.w
;
379 var y
= this.area
.y
+ this.area
.h
;
382 ctx
.lineTo(x
, this.area
.y
);
388 // Do the ordinary rendering, as before
389 this._renderLineChart();
391 this._renderAnnotations();
395 DygraphCanvasRenderer
.prototype._renderAxis
= function() {
396 if (!this.options
.drawXAxis
&& !this.options
.drawYAxis
)
399 var context
= this.element
.getContext("2d");
402 "position": "absolute",
403 "fontSize": this.options
.axisLabelFontSize
+ "px",
405 "color": this.options
.axisLabelColor
,
406 "width": this.options
.axisLabelWidth
+ "px",
409 var makeDiv
= function(txt
) {
410 var div
= document
.createElement("div");
411 for (var name
in labelStyle
) {
412 if (labelStyle
.hasOwnProperty(name
)) {
413 div
.style
[name
] = labelStyle
[name
];
416 div
.appendChild(document
.createTextNode(txt
));
422 context
.strokeStyle
= this.options
.axisLineColor
;
423 context
.lineWidth
= this.options
.axisLineWidth
;
425 if (this.options
.drawYAxis
) {
426 if (this.layout
.yticks
&& this.layout
.yticks
.length
> 0) {
427 for (var i
= 0; i
< this.layout
.yticks
.length
; i
++) {
428 var tick
= this.layout
.yticks
[i
];
429 if (typeof(tick
) == "function") return;
432 x
= this.area
.x
+ this.area
.w
- labelStyle
.width
;
434 var y
= this.area
.y
+ tick
[1] * this.area
.h
;
436 context
.moveTo(x
, y
);
437 context
.lineTo(x
- this.options
.axisTickSize
, y
);
441 var label
= makeDiv(tick
[2]);
442 var top
= (y
- this.options
.axisLabelFontSize
/ 2);
443 if (top
< 0) top
= 0;
445 if (top
+ this.options
.axisLabelFontSize
+ 3 > this.height
) {
446 label
.style
.bottom
= "0px";
448 label
.style
.top
= top
+ "px";
451 label
.style
.left
= "0px";
452 label
.style
.textAlign
= "right";
453 } else if (tick
[0] == 1) {
454 label
.style
.left
= (this.area
.x
+ this.area
.w
+
455 this.options
.axisTickSize
) + "px";
456 label
.style
.textAlign
= "left";
458 label
.style
.width
= this.options
.yAxisLabelWidth
+ "px";
459 this.container
.appendChild(label
);
460 this.ylabels
.push(label
);
463 // The lowest tick on the y-axis often overlaps with the leftmost
464 // tick on the x-axis. Shift the bottom tick up a little bit to
465 // compensate if necessary.
466 var bottomTick
= this.ylabels
[0];
467 var fontSize
= this.options
.axisLabelFontSize
;
468 var bottom
= parseInt(bottomTick
.style
.top
) + fontSize
;
469 if (bottom
> this.height
- fontSize
) {
470 bottomTick
.style
.top
= (parseInt(bottomTick
.style
.top
) -
471 fontSize
/ 2) + "px";
476 context
.moveTo(this.area
.x
, this.area
.y
);
477 context
.lineTo(this.area
.x
, this.area
.y
+ this.area
.h
);
482 if (this.options
.drawXAxis
) {
483 if (this.layout
.xticks
) {
484 for (var i
= 0; i
< this.layout
.xticks
.length
; i
++) {
485 var tick
= this.layout
.xticks
[i
];
486 if (typeof(dataset
) == "function") return;
488 var x
= this.area
.x
+ tick
[0] * this.area
.w
;
489 var y
= this.area
.y
+ this.area
.h
;
491 context
.moveTo(x
, y
);
492 context
.lineTo(x
, y
+ this.options
.axisTickSize
);
496 var label
= makeDiv(tick
[1]);
497 label
.style
.textAlign
= "center";
498 label
.style
.bottom
= "0px";
500 var left
= (x
- this.options
.axisLabelWidth
/2);
501 if (left
+ this.options
.axisLabelWidth
> this.width
) {
502 left
= this.width
- this.options
.xAxisLabelWidth
;
503 label
.style
.textAlign
= "right";
507 label
.style
.textAlign
= "left";
510 label
.style
.left
= left
+ "px";
511 label
.style
.width
= this.options
.xAxisLabelWidth
+ "px";
512 this.container
.appendChild(label
);
513 this.xlabels
.push(label
);
518 context
.moveTo(this.area
.x
, this.area
.y
+ this.area
.h
);
519 context
.lineTo(this.area
.x
+ this.area
.w
, this.area
.y
+ this.area
.h
);
528 DygraphCanvasRenderer
.prototype._renderAnnotations
= function() {
529 var annotationStyle
= {
530 "position": "absolute",
531 "fontSize": this.options
.axisLabelFontSize
+ "px",
536 var bindEvt
= function(eventName
, classEventName
, p
, self
) {
538 var a
= p
.annotation
;
539 if (a
.hasOwnProperty(eventName
)) {
540 a
[eventName
](a
, p
, self
.dygraph_
, e
);
541 } else if (self
.dygraph_
.attr_(classEventName
)) {
542 self
.dygraph_
.attr_(classEventName
)(a
, p
, self
.dygraph_
,e
);
547 // Get a list of point with annotations.
548 var points
= this.layout
.annotated_points
;
549 for (var i
= 0; i
< points
.length
; i
++) {
551 if (p
.canvasx
< this.area
.x
|| p
.canvasx
> this.area
.x
+ this.area
.w
) {
555 var a
= p
.annotation
;
557 if (a
.hasOwnProperty("tickHeight")) {
558 tick_height
= a
.tickHeight
;
561 var div
= document
.createElement("div");
562 for (var name
in annotationStyle
) {
563 if (annotationStyle
.hasOwnProperty(name
)) {
564 div
.style
[name
] = annotationStyle
[name
];
567 if (!a
.hasOwnProperty('icon')) {
568 div
.className
= "dygraphDefaultAnnotation";
570 if (a
.hasOwnProperty('cssClass')) {
571 div
.className
+= " " + a
.cssClass
;
574 var width
= a
.hasOwnProperty('width') ? a
.width
: 16;
575 var height
= a
.hasOwnProperty('height') ? a
.height
: 16;
576 if (a
.hasOwnProperty('icon')) {
577 var img
= document
.createElement("img");
581 div
.appendChild(img
);
582 } else if (p
.annotation
.hasOwnProperty('shortText')) {
583 div
.appendChild(document
.createTextNode(p
.annotation
.shortText
));
585 div
.style
.left
= (p
.canvasx
- width
/ 2) + "px";
586 if (a
.attachAtBottom
) {
587 div
.style
.top
= (this.area
.h
- height
- tick_height
) + "px";
589 div
.style
.top
= (p
.canvasy
- height
- tick_height
) + "px";
591 div
.style
.width
= width
+ "px";
592 div
.style
.height
= height
+ "px";
593 div
.title
= p
.annotation
.text
;
594 div
.style
.color
= this.colors
[p
.name
];
595 div
.style
.borderColor
= this.colors
[p
.name
];
598 Dygraph
.addEvent(div
, 'click',
599 bindEvt('clickHandler', 'annotationClickHandler', p
, this));
600 Dygraph
.addEvent(div
, 'mouseover',
601 bindEvt('mouseOverHandler', 'annotationMouseOverHandler', p
, this));
602 Dygraph
.addEvent(div
, 'mouseout',
603 bindEvt('mouseOutHandler', 'annotationMouseOutHandler', p
, this));
604 Dygraph
.addEvent(div
, 'dblclick',
605 bindEvt('dblClickHandler', 'annotationDblClickHandler', p
, this));
607 this.container
.appendChild(div
);
608 this.annotations
.push(div
);
610 var ctx
= this.element
.getContext("2d");
611 ctx
.strokeStyle
= this.colors
[p
.name
];
613 if (!a
.attachAtBottom
) {
614 ctx
.moveTo(p
.canvasx
, p
.canvasy
);
615 ctx
.lineTo(p
.canvasx
, p
.canvasy
- 2 - tick_height
);
617 ctx
.moveTo(p
.canvasx
, this.area
.h
);
618 ctx
.lineTo(p
.canvasx
, this.area
.h
- 2 - tick_height
);
627 * Overrides the CanvasRenderer method to draw error bars
629 DygraphCanvasRenderer
.prototype._renderLineChart
= function() {
630 var context
= this.element
.getContext("2d");
631 var colorCount
= this.options
.colorScheme
.length
;
632 var colorScheme
= this.options
.colorScheme
;
633 var fillAlpha
= this.options
.fillAlpha
;
634 var errorBars
= this.layout
.options
.errorBars
;
635 var fillGraph
= this.layout
.options
.fillGraph
;
636 var stackedGraph
= this.layout
.options
.stackedGraph
;
637 var stepPlot
= this.layout
.options
.stepPlot
;
640 for (var name
in this.layout
.datasets
) {
641 if (this.layout
.datasets
.hasOwnProperty(name
)) {
645 var setCount
= setNames
.length
;
648 for (var i
= 0; i
< setCount
; i
++) {
649 this.colors
[setNames
[i
]] = colorScheme
[i
% colorCount
];
654 for (var i
= 0; i
< this.layout
.points
.length
; i
++) {
655 var point
= this.layout
.points
[i
];
656 point
.canvasx
= this.area
.w
* point
.x
+ this.area
.x
;
657 point
.canvasy
= this.area
.h
* point
.y
+ this.area
.y
;
661 var isOK
= function(x
) { return x
&& !isNaN(x
); };
666 this.dygraph_
.warn("Can't use fillGraph option with error bars");
669 for (var i
= 0; i
< setCount
; i
++) {
670 var setName
= setNames
[i
];
671 var axis
= this.layout
.options
.yAxes
[
672 this.layout
.options
.seriesToAxisMap
[setName
]];
673 var color
= this.colors
[setName
];
675 // setup graphics context
679 var prevYs
= [-1, -1];
680 var yscale
= axis
.yscale
;
681 // should be same color as the lines but only 15% opaque.
682 var rgb
= new RGBColor(color
);
683 var err_color
= 'rgba(' + rgb
.r
+ ',' + rgb
.g
+ ',' + rgb
.b
+ ',' +
685 ctx
.fillStyle
= err_color
;
687 for (var j
= 0; j
< this.layout
.points
.length
; j
++) {
688 var point
= this.layout
.points
[j
];
689 if (point
.name
== setName
) {
690 if (!isOK(point
.y
)) {
697 var newYs
= [ prevY
- point
.errorPlus
* yscale
,
698 prevY
+ point
.errorMinus
* yscale
];
701 var newYs
= [ point
.y
- point
.errorPlus
* yscale
,
702 point
.y
+ point
.errorMinus
* yscale
];
704 newYs
[0] = this.area
.h
* newYs
[0] + this.area
.y
;
705 newYs
[1] = this.area
.h
* newYs
[1] + this.area
.y
;
708 ctx
.moveTo(prevX
, newYs
[0]);
710 ctx
.moveTo(prevX
, prevYs
[0]);
712 ctx
.lineTo(point
.canvasx
, newYs
[0]);
713 ctx
.lineTo(point
.canvasx
, newYs
[1]);
715 ctx
.lineTo(prevX
, newYs
[1]);
717 ctx
.lineTo(prevX
, prevYs
[1]);
722 prevX
= point
.canvasx
;
727 } else if (fillGraph
) {
728 var baseline
= [] // for stacked graphs: baseline for filling
730 // process sets in reverse order (needed for stacked graphs)
731 for (var i
= setCount
- 1; i
>= 0; i
--) {
732 var setName
= setNames
[i
];
733 var color
= this.colors
[setName
];
734 var axis
= this.layout
.options
.yAxes
[
735 this.layout
.options
.seriesToAxisMap
[setName
]];
736 var axisY
= 1.0 + axis
.minyval
* axis
.yscale
;
737 if (axisY
< 0.0) axisY
= 0.0;
738 else if (axisY
> 1.0) axisY
= 1.0;
739 axisY
= this.area
.h
* axisY
+ this.area
.y
;
741 // setup graphics context
744 var prevYs
= [-1, -1];
745 var yscale
= axis
.yscale
;
746 // should be same color as the lines but only 15% opaque.
747 var rgb
= new RGBColor(color
);
748 var err_color
= 'rgba(' + rgb
.r
+ ',' + rgb
.g
+ ',' + rgb
.b
+ ',' +
750 ctx
.fillStyle
= err_color
;
752 for (var j
= 0; j
< this.layout
.points
.length
; j
++) {
753 var point
= this.layout
.points
[j
];
754 if (point
.name
== setName
) {
755 if (!isOK(point
.y
)) {
761 lastY
= baseline
[point
.canvasx
];
762 if (lastY
=== undefined
) lastY
= axisY
;
763 baseline
[point
.canvasx
] = point
.canvasy
;
764 newYs
= [ point
.canvasy
, lastY
];
766 newYs
= [ point
.canvasy
, axisY
];
769 ctx
.moveTo(prevX
, prevYs
[0]);
771 ctx
.lineTo(point
.canvasx
, prevYs
[0]);
773 ctx
.lineTo(point
.canvasx
, newYs
[0]);
775 ctx
.lineTo(point
.canvasx
, newYs
[1]);
776 ctx
.lineTo(prevX
, prevYs
[1]);
780 prevX
= point
.canvasx
;
787 for (var i
= 0; i
< setCount
; i
++) {
788 var setName
= setNames
[i
];
789 var color
= this.colors
[setName
];
790 var strokeWidth
= this.dygraph_
.attr_("strokeWidth", setName
);
792 // setup graphics context
794 var point
= this.layout
.points
[0];
795 var pointSize
= this.dygraph_
.attr_("pointSize", setName
);
796 var prevX
= null, prevY
= null;
797 var drawPoints
= this.dygraph_
.attr_("drawPoints", setName
);
798 var points
= this.layout
.points
;
799 for (var j
= 0; j
< points
.length
; j
++) {
800 var point
= points
[j
];
801 if (point
.name
== setName
) {
802 if (!isOK(point
.canvasy
)) {
803 // this will make us move to the next point, not draw a line to it.
804 prevX
= prevY
= null;
806 // A point is "isolated" if it is non-null but both the previous
807 // and next points are null.
808 var isIsolated
= (!prevX
&& (j
== points
.length
- 1 ||
809 !isOK(points
[j
+1].canvasy
)));
812 prevX
= point
.canvasx
;
813 prevY
= point
.canvasy
;
815 // TODO(danvk): figure out why this conditional is necessary.
818 ctx
.strokeStyle
= color
;
819 ctx
.lineWidth
= strokeWidth
;
820 ctx
.moveTo(prevX
, prevY
);
822 ctx
.lineTo(point
.canvasx
, prevY
);
824 prevX
= point
.canvasx
;
825 prevY
= point
.canvasy
;
826 ctx
.lineTo(prevX
, prevY
);
831 if (drawPoints
|| isIsolated
) {
833 ctx
.fillStyle
= color
;
834 ctx
.arc(point
.canvasx
, point
.canvasy
, pointSize
,
835 0, 2 * Math
.PI
, false);