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
35 // save a copy. If there is no parser, then the user must be using raw format.
36 this.annotations
= [];
37 var parse
= this.attr_('xValueParser') || function(x
) { return x
; };
38 for (var i
= 0; i
< ann
.length
; i
++) {
40 if (!ann
[i
].xval
&& !ann
[i
].x
) {
41 this.dygraph_
.error("Annotations must have an 'x' property");
45 !(ann
[i
].hasOwnProperty('width') &&
46 ann
[i
].hasOwnProperty('height'))) {
47 this.dygraph_
.error("Must set width and height when setting " +
48 "annotation.icon property");
51 Dygraph
.update(a
, ann
[i
]);
52 if (!a
.xval
) a
.xval
= parse(a
.x
);
53 this.annotations
.push(a
);
57 DygraphLayout
.prototype.evaluate
= function() {
58 this._evaluateLimits();
59 this._evaluateLineCharts();
60 this._evaluateLineTicks();
61 this._evaluateAnnotations();
64 DygraphLayout
.prototype._evaluateLimits
= function() {
65 this.minxval
= this.maxxval
= null;
66 if (this.options
.dateWindow
) {
67 this.minxval
= this.options
.dateWindow
[0];
68 this.maxxval
= this.options
.dateWindow
[1];
70 for (var name
in this.datasets
) {
71 if (!this.datasets
.hasOwnProperty(name
)) continue;
72 var series
= this.datasets
[name
];
73 if (series
.length
> 1) {
74 var x1
= series
[0][0];
75 if (!this.minxval
|| x1
< this.minxval
) this.minxval
= x1
;
77 var x2
= series
[series
.length
- 1][0];
78 if (!this.maxxval
|| x2
> this.maxxval
) this.maxxval
= x2
;
82 this.xrange
= this.maxxval
- this.minxval
;
83 this.xscale
= (this.xrange
!= 0 ? 1/this.xrange
: 1.0);
85 for (var i
= 0; i
< this.options
.yAxes
.length
; i
++) {
86 var axis
= this.options
.yAxes
[i
];
87 axis
.minyval
= axis
.computedValueRange
[0];
88 axis
.maxyval
= axis
.computedValueRange
[1];
89 axis
.yrange
= axis
.maxyval
- axis
.minyval
;
90 axis
.yscale
= (axis
.yrange
!= 0 ? 1.0 / axis
.yrange
: 1.0);
92 if (axis
.g
.attr_("logscale")) {
93 axis
.ylogrange
= Dygraph
.log10(axis
.maxyval
) - Dygraph
.log10(axis
.minyval
);
94 axis
.ylogscale
= (axis
.ylogrange
!= 0 ? 1.0 / axis
.ylogrange
: 1.0);
95 if (!isFinite(axis
.ylogrange
) || isNaN(axis
.ylogrange
)) {
96 axis
.g
.error('axis ' + i
+ ' of graph at ' + axis
.g
+
97 ' can\'t be displayed in log scale for range [' +
98 axis
.minyval
+ ' - ' + axis
.maxyval
+ ']');
104 DygraphLayout
.prototype._evaluateLineCharts
= function() {
106 this.points
= new Array();
107 for (var setName
in this.datasets
) {
108 if (!this.datasets
.hasOwnProperty(setName
)) continue;
110 var dataset
= this.datasets
[setName
];
111 var axis
= this.options
.yAxes
[this.options
.seriesToAxisMap
[setName
]];
113 for (var j
= 0; j
< dataset
.length
; j
++) {
114 var item
= dataset
[j
];
118 yval
= 1.0 - ((Dygraph
.log10(parseFloat(item
[1])) - Dygraph
.log10(axis
.minyval
)) * axis
.ylogscale
); // really should just be yscale.
120 yval
= 1.0 - ((parseFloat(item
[1]) - axis
.minyval
) * axis
.yscale
);
124 x
: ((parseFloat(item
[0]) - this.minxval
) * this.xscale
),
126 xval
: parseFloat(item
[0]),
127 yval
: parseFloat(item
[1]),
131 this.points
.push(point
);
136 DygraphLayout
.prototype._evaluateLineTicks
= function() {
137 this.xticks
= new Array();
138 for (var i
= 0; i
< this.options
.xTicks
.length
; i
++) {
139 var tick
= this.options
.xTicks
[i
];
140 var label
= tick
.label
;
141 var pos
= this.xscale
* (tick
.v
- this.minxval
);
142 if ((pos
>= 0.0) && (pos
<= 1.0)) {
143 this.xticks
.push([pos
, label
]);
147 this.yticks
= new Array();
148 for (var i
= 0; i
< this.options
.yAxes
.length
; i
++ ) {
149 var axis
= this.options
.yAxes
[i
];
150 for (var j
= 0; j
< axis
.ticks
.length
; j
++) {
151 var tick
= axis
.ticks
[j
];
152 var label
= tick
.label
;
153 var pos
= this.dygraph_
.toPercentYCoord(tick
.v
, i
);
154 if ((pos
>= 0.0) && (pos
<= 1.0)) {
155 this.yticks
.push([i
, pos
, label
]);
163 * Behaves the same way as PlotKit.Layout, but also copies the errors
166 DygraphLayout
.prototype.evaluateWithError
= function() {
168 if (!this.options
.errorBars
) return;
170 // Copy over the error terms
171 var i
= 0; // index in this.points
172 for (var setName
in this.datasets
) {
173 if (!this.datasets
.hasOwnProperty(setName
)) continue;
175 var dataset
= this.datasets
[setName
];
176 for (var j
= 0; j
< dataset
.length
; j
++, i
++) {
177 var item
= dataset
[j
];
178 var xv
= parseFloat(item
[0]);
179 var yv
= parseFloat(item
[1]);
181 if (xv
== this.points
[i
].xval
&&
182 yv
== this.points
[i
].yval
) {
183 this.points
[i
].errorMinus
= parseFloat(item
[2]);
184 this.points
[i
].errorPlus
= parseFloat(item
[3]);
190 DygraphLayout
.prototype._evaluateAnnotations
= function() {
191 // Add the annotations to the point to which they belong.
192 // Make a map from (setName, xval) to annotation for quick lookups.
193 var annotations
= {};
194 for (var i
= 0; i
< this.annotations
.length
; i
++) {
195 var a
= this.annotations
[i
];
196 annotations
[a
.xval
+ "," + a
.series
] = a
;
199 this.annotated_points
= [];
200 for (var i
= 0; i
< this.points
.length
; i
++) {
201 var p
= this.points
[i
];
202 var k
= p
.xval
+ "," + p
.name
;
203 if (k
in annotations
) {
204 p
.annotation
= annotations
[k
];
205 this.annotated_points
.push(p
);
211 * Convenience function to remove all the data sets from a graph
213 DygraphLayout
.prototype.removeAllDatasets
= function() {
214 delete this.datasets
;
215 this.datasets
= new Array();
219 * Change the values of various layout options
220 * @param {Object} new_options an associative array of new properties
222 DygraphLayout
.prototype.updateOptions
= function(new_options
) {
223 Dygraph
.update(this.options
, new_options
? new_options
: {});
227 * Return a copy of the point at the indicated index, with its yval unstacked.
228 * @param int index of point in layout_.points
230 DygraphLayout
.prototype.unstackPointAtIndex
= function(idx
) {
231 var point
= this.points
[idx
];
233 // Clone the point since we modify it
234 var unstackedPoint
= {};
235 for (var i
in point
) {
236 unstackedPoint
[i
] = point
[i
];
239 if (!this.attr_("stackedGraph")) {
240 return unstackedPoint
;
243 // The unstacked yval is equal to the current yval minus the yval of the
244 // next point at the same xval.
245 for (var i
= idx
+1; i
< this.points
.length
; i
++) {
246 if (this.points
[i
].xval
== point
.xval
) {
247 unstackedPoint
.yval
-= this.points
[i
].yval
;
252 return unstackedPoint
;
255 // Subclass PlotKit.CanvasRenderer to add:
256 // 1. X/Y grid overlay
257 // 2. Ability to draw error bars (if required)
260 * Sets some PlotKit.CanvasRenderer options
261 * @param {Object} element The canvas to attach to
262 * @param {Layout} layout The DygraphLayout object for this graph.
263 * @param {Object} options Options to pass on to CanvasRenderer
265 DygraphCanvasRenderer
= function(dygraph
, element
, layout
, options
) {
266 // TODO(danvk): remove options, just use dygraph.attr_.
267 this.dygraph_
= dygraph
;
274 "axisLineColor": "black",
275 "axisLineWidth": 0.5,
277 "axisLabelColor": "black",
278 "axisLabelFont": "Arial",
279 "axisLabelFontSize": 9,
280 "axisLabelWidth": 50,
283 "gridLineColor": "rgb(128,128,128)",
285 "underlayCallback": null
287 Dygraph
.update(this.options
, options
);
289 this.layout
= layout
;
290 this.element
= element
;
291 this.container
= this.element
.parentNode
;
293 this.height
= this.element
.height
;
294 this.width
= this.element
.width
;
296 // --- check whether everything is ok before we return
297 if (!this.isIE
&& !(DygraphCanvasRenderer
.isSupported(this.element
)))
298 throw "Canvas is not supported.";
301 this.xlabels
= new Array();
302 this.ylabels
= new Array();
303 this.annotations
= new Array();
305 // TODO(danvk): consider all axes in this computation.
307 // TODO(danvk): per-axis setting.
308 x
: this.options
.yAxisLabelWidth
+ 2 * this.options
.axisTickSize
,
311 this.area
.w
= this.width
- this.area
.x
- this.options
.rightGap
;
312 this.area
.h
= this.height
- this.options
.axisLabelFontSize
-
313 2 * this.options
.axisTickSize
;
315 // Shrink the drawing area to accomodate additional y-axes.
316 if (this.dygraph_
.numAxes() == 2) {
317 // TODO(danvk): per-axis setting.
318 this.area
.w
-= (this.options
.yAxisLabelWidth
+ 2 * this.options
.axisTickSize
);
319 } else if (this.dygraph_
.numAxes() > 2) {
320 this.dygraph_
.error("Only two y-axes are supported at this time. (Trying " +
321 "to use " + this.dygraph_
.numAxes() + ")");
324 this.container
.style
.position
= "relative";
325 this.container
.style
.width
= this.width
+ "px";
327 // Set up a clipping area for the canvas (and the interaction canvas).
328 // This ensures that we don't overdraw.
329 var ctx
= this.dygraph_
.canvas_
.getContext("2d");
331 ctx
.rect(this.area
.x
, this.area
.y
, this.area
.w
, this.area
.h
);
334 ctx
= this.dygraph_
.hidden_
.getContext("2d");
336 ctx
.rect(this.area
.x
, this.area
.y
, this.area
.w
, this.area
.h
);
340 DygraphCanvasRenderer
.prototype.attr_
= function(x
) {
341 return this.dygraph_
.attr_(x
);
344 DygraphCanvasRenderer
.prototype.clear
= function() {
346 // VML takes a while to start up, so we just poll every this.IEDelay
348 if (this.clearDelay
) {
349 this.clearDelay
.cancel();
350 this.clearDelay
= null;
352 var context
= this.element
.getContext("2d");
355 // TODO(danvk): this is broken, since MochiKit.Async is gone.
356 this.clearDelay
= MochiKit
.Async
.wait(this.IEDelay
);
357 this.clearDelay
.addCallback(bind(this.clear
, this));
362 var context
= this.element
.getContext("2d");
363 context
.clearRect(0, 0, this.width
, this.height
);
365 for (var i
= 0; i
< this.xlabels
.length
; i
++) {
366 var el
= this.xlabels
[i
];
367 if (el
.parentNode
) el
.parentNode
.removeChild(el
);
369 for (var i
= 0; i
< this.ylabels
.length
; i
++) {
370 var el
= this.ylabels
[i
];
371 if (el
.parentNode
) el
.parentNode
.removeChild(el
);
373 for (var i
= 0; i
< this.annotations
.length
; i
++) {
374 var el
= this.annotations
[i
];
375 if (el
.parentNode
) el
.parentNode
.removeChild(el
);
377 this.xlabels
= new Array();
378 this.ylabels
= new Array();
379 this.annotations
= new Array();
383 DygraphCanvasRenderer
.isSupported
= function(canvasName
) {
386 if (typeof(canvasName
) == 'undefined' || canvasName
== null)
387 canvas
= document
.createElement("canvas");
390 var context
= canvas
.getContext("2d");
393 var ie
= navigator
.appVersion
.match(/MSIE (\d\.\d)/);
394 var opera
= (navigator
.userAgent
.toLowerCase().indexOf("opera") != -1);
395 if ((!ie
) || (ie
[1] < 6) || (opera
))
403 * Draw an X/Y grid on top of the existing plot
405 DygraphCanvasRenderer
.prototype.render
= function() {
406 // Draw the new X/Y grid
. Lines appear crisper when pixels are rounded to
407 // half-integers. This prevents them from drawing in two rows/cols.
408 var ctx
= this.element
.getContext("2d");
409 function halfUp(x
){return Math
.round(x
)+0.5};
410 function halfDown(y
){return Math
.round(y
)-0.5};
412 if (this.options
.underlayCallback
) {
413 // NOTE: we pass the dygraph object to this callback twice to avoid breaking
414 // users who expect a deprecated form of this callback.
415 this.options
.underlayCallback(ctx
, this.area
, this.dygraph_
, this.dygraph_
);
418 if (this.options
.drawYGrid
) {
419 var ticks
= this.layout
.yticks
;
421 ctx
.strokeStyle
= this.options
.gridLineColor
;
422 ctx
.lineWidth
= this.options
.axisLineWidth
;
423 for (var i
= 0; i
< ticks
.length
; i
++) {
424 // TODO(danvk): allow secondary axes to draw a grid, too.
425 if (ticks
[i
][0] != 0) continue;
426 var x
= halfUp(this.area
.x
);
427 var y
= halfDown(this.area
.y
+ ticks
[i
][1] * this.area
.h
);
430 ctx
.lineTo(x
+ this.area
.w
, y
);
436 if (this.options
.drawXGrid
) {
437 var ticks
= this.layout
.xticks
;
439 ctx
.strokeStyle
= this.options
.gridLineColor
;
440 ctx
.lineWidth
= this.options
.axisLineWidth
;
441 for (var i
=0; i
<ticks
.length
; i
++) {
442 var x
= halfUp(this.area
.x
+ ticks
[i
][0] * this.area
.w
);
443 var y
= halfDown(this.area
.y
+ this.area
.h
);
446 ctx
.lineTo(x
, this.area
.y
);
452 // Do the ordinary rendering, as before
453 this._renderLineChart();
455 this._renderAnnotations();
459 DygraphCanvasRenderer
.prototype._renderAxis
= function() {
460 if (!this.options
.drawXAxis
&& !this.options
.drawYAxis
)
463 // Round pixels to half-integer boundaries for crisper drawing.
464 function halfUp(x
){return Math
.round(x
)+0.5};
465 function halfDown(y
){return Math
.round(y
)-0.5};
467 var context
= this.element
.getContext("2d");
470 "position": "absolute",
471 "fontSize": this.options
.axisLabelFontSize
+ "px",
473 "color": this.options
.axisLabelColor
,
474 "width": this.options
.axisLabelWidth
+ "px",
477 var makeDiv
= function(txt
) {
478 var div
= document
.createElement("div");
479 for (var name
in labelStyle
) {
480 if (labelStyle
.hasOwnProperty(name
)) {
481 div
.style
[name
] = labelStyle
[name
];
484 div
.appendChild(document
.createTextNode(txt
));
490 context
.strokeStyle
= this.options
.axisLineColor
;
491 context
.lineWidth
= this.options
.axisLineWidth
;
493 if (this.options
.drawYAxis
) {
494 if (this.layout
.yticks
&& this.layout
.yticks
.length
> 0) {
495 for (var i
= 0; i
< this.layout
.yticks
.length
; i
++) {
496 var tick
= this.layout
.yticks
[i
];
497 if (typeof(tick
) == "function") return;
500 if (tick
[0] == 1) { // right-side y-axis
501 x
= this.area
.x
+ this.area
.w
;
504 var y
= this.area
.y
+ tick
[1] * this.area
.h
;
506 context
.moveTo(halfUp(x
), halfDown(y
));
507 context
.lineTo(halfUp(x
- sgn
* this.options
.axisTickSize
), halfDown(y
));
511 var label
= makeDiv(tick
[2]);
512 var top
= (y
- this.options
.axisLabelFontSize
/ 2);
513 if (top
< 0) top
= 0;
515 if (top
+ this.options
.axisLabelFontSize
+ 3 > this.height
) {
516 label
.style
.bottom
= "0px";
518 label
.style
.top
= top
+ "px";
521 label
.style
.left
= "0px";
522 label
.style
.textAlign
= "right";
523 } else if (tick
[0] == 1) {
524 label
.style
.left
= (this.area
.x
+ this.area
.w
+
525 this.options
.axisTickSize
) + "px";
526 label
.style
.textAlign
= "left";
528 label
.style
.width
= this.options
.yAxisLabelWidth
+ "px";
529 this.container
.appendChild(label
);
530 this.ylabels
.push(label
);
533 // The lowest tick on the y-axis often overlaps with the leftmost
534 // tick on the x-axis. Shift the bottom tick up a little bit to
535 // compensate if necessary.
536 var bottomTick
= this.ylabels
[0];
537 var fontSize
= this.options
.axisLabelFontSize
;
538 var bottom
= parseInt(bottomTick
.style
.top
) + fontSize
;
539 if (bottom
> this.height
- fontSize
) {
540 bottomTick
.style
.top
= (parseInt(bottomTick
.style
.top
) -
541 fontSize
/ 2) + "px";
545 // draw a vertical line on the left to separate the chart from the labels.
547 context
.moveTo(halfUp(this.area
.x
), halfDown(this.area
.y
));
548 context
.lineTo(halfUp(this.area
.x
), halfDown(this.area
.y
+ this.area
.h
));
552 // if there's a secondary y-axis, draw a vertical line for that, too.
553 if (this.dygraph_
.numAxes() == 2) {
555 context
.moveTo(halfDown(this.area
.x
+ this.area
.w
), halfDown(this.area
.y
));
556 context
.lineTo(halfDown(this.area
.x
+ this.area
.w
), halfDown(this.area
.y
+ this.area
.h
));
562 if (this.options
.drawXAxis
) {
563 if (this.layout
.xticks
) {
564 for (var i
= 0; i
< this.layout
.xticks
.length
; i
++) {
565 var tick
= this.layout
.xticks
[i
];
566 if (typeof(dataset
) == "function") return;
568 var x
= this.area
.x
+ tick
[0] * this.area
.w
;
569 var y
= this.area
.y
+ this.area
.h
;
571 context
.moveTo(halfUp(x
), halfDown(y
));
572 context
.lineTo(halfUp(x
), halfDown(y
+ this.options
.axisTickSize
));
576 var label
= makeDiv(tick
[1]);
577 label
.style
.textAlign
= "center";
578 label
.style
.bottom
= "0px";
580 var left
= (x
- this.options
.axisLabelWidth
/2);
581 if (left
+ this.options
.axisLabelWidth
> this.width
) {
582 left
= this.width
- this.options
.xAxisLabelWidth
;
583 label
.style
.textAlign
= "right";
587 label
.style
.textAlign
= "left";
590 label
.style
.left
= left
+ "px";
591 label
.style
.width
= this.options
.xAxisLabelWidth
+ "px";
592 this.container
.appendChild(label
);
593 this.xlabels
.push(label
);
598 context
.moveTo(halfUp(this.area
.x
), halfDown(this.area
.y
+ this.area
.h
));
599 context
.lineTo(halfUp(this.area
.x
+ this.area
.w
), halfDown(this.area
.y
+ this.area
.h
));
608 DygraphCanvasRenderer
.prototype._renderAnnotations
= function() {
609 var annotationStyle
= {
610 "position": "absolute",
611 "fontSize": this.options
.axisLabelFontSize
+ "px",
616 var bindEvt
= function(eventName
, classEventName
, p
, self
) {
618 var a
= p
.annotation
;
619 if (a
.hasOwnProperty(eventName
)) {
620 a
[eventName
](a
, p
, self
.dygraph_
, e
);
621 } else if (self
.dygraph_
.attr_(classEventName
)) {
622 self
.dygraph_
.attr_(classEventName
)(a
, p
, self
.dygraph_
,e
);
627 // Get a list of point with annotations.
628 var points
= this.layout
.annotated_points
;
629 for (var i
= 0; i
< points
.length
; i
++) {
631 if (p
.canvasx
< this.area
.x
|| p
.canvasx
> this.area
.x
+ this.area
.w
) {
635 var a
= p
.annotation
;
637 if (a
.hasOwnProperty("tickHeight")) {
638 tick_height
= a
.tickHeight
;
641 var div
= document
.createElement("div");
642 for (var name
in annotationStyle
) {
643 if (annotationStyle
.hasOwnProperty(name
)) {
644 div
.style
[name
] = annotationStyle
[name
];
647 if (!a
.hasOwnProperty('icon')) {
648 div
.className
= "dygraphDefaultAnnotation";
650 if (a
.hasOwnProperty('cssClass')) {
651 div
.className
+= " " + a
.cssClass
;
654 var width
= a
.hasOwnProperty('width') ? a
.width
: 16;
655 var height
= a
.hasOwnProperty('height') ? a
.height
: 16;
656 if (a
.hasOwnProperty('icon')) {
657 var img
= document
.createElement("img");
661 div
.appendChild(img
);
662 } else if (p
.annotation
.hasOwnProperty('shortText')) {
663 div
.appendChild(document
.createTextNode(p
.annotation
.shortText
));
665 div
.style
.left
= (p
.canvasx
- width
/ 2) + "px";
666 if (a
.attachAtBottom
) {
667 div
.style
.top
= (this.area
.h
- height
- tick_height
) + "px";
669 div
.style
.top
= (p
.canvasy
- height
- tick_height
) + "px";
671 div
.style
.width
= width
+ "px";
672 div
.style
.height
= height
+ "px";
673 div
.title
= p
.annotation
.text
;
674 div
.style
.color
= this.colors
[p
.name
];
675 div
.style
.borderColor
= this.colors
[p
.name
];
678 Dygraph
.addEvent(div
, 'click',
679 bindEvt('clickHandler', 'annotationClickHandler', p
, this));
680 Dygraph
.addEvent(div
, 'mouseover',
681 bindEvt('mouseOverHandler', 'annotationMouseOverHandler', p
, this));
682 Dygraph
.addEvent(div
, 'mouseout',
683 bindEvt('mouseOutHandler', 'annotationMouseOutHandler', p
, this));
684 Dygraph
.addEvent(div
, 'dblclick',
685 bindEvt('dblClickHandler', 'annotationDblClickHandler', p
, this));
687 this.container
.appendChild(div
);
688 this.annotations
.push(div
);
690 var ctx
= this.element
.getContext("2d");
691 ctx
.strokeStyle
= this.colors
[p
.name
];
693 if (!a
.attachAtBottom
) {
694 ctx
.moveTo(p
.canvasx
, p
.canvasy
);
695 ctx
.lineTo(p
.canvasx
, p
.canvasy
- 2 - tick_height
);
697 ctx
.moveTo(p
.canvasx
, this.area
.h
);
698 ctx
.lineTo(p
.canvasx
, this.area
.h
- 2 - tick_height
);
707 * Overrides the CanvasRenderer method to draw error bars
709 DygraphCanvasRenderer
.prototype._renderLineChart
= function() {
710 // TODO(danvk): use this.attr_ for many of these.
711 var context
= this.element
.getContext("2d");
712 var colorCount
= this.options
.colorScheme
.length
;
713 var colorScheme
= this.options
.colorScheme
;
714 var fillAlpha
= this.options
.fillAlpha
;
715 var errorBars
= this.layout
.options
.errorBars
;
716 var fillGraph
= this.attr_("fillGraph");
717 var stackedGraph
= this.layout
.options
.stackedGraph
;
718 var stepPlot
= this.layout
.options
.stepPlot
;
721 for (var name
in this.layout
.datasets
) {
722 if (this.layout
.datasets
.hasOwnProperty(name
)) {
726 var setCount
= setNames
.length
;
729 for (var i
= 0; i
< setCount
; i
++) {
730 this.colors
[setNames
[i
]] = colorScheme
[i
% colorCount
];
735 for (var i
= 0; i
< this.layout
.points
.length
; i
++) {
736 var point
= this.layout
.points
[i
];
737 point
.canvasx
= this.area
.w
* point
.x
+ this.area
.x
;
738 point
.canvasy
= this.area
.h
* point
.y
+ this.area
.y
;
745 this.dygraph_
.warn("Can't use fillGraph option with error bars");
748 for (var i
= 0; i
< setCount
; i
++) {
749 var setName
= setNames
[i
];
750 var axis
= this.layout
.options
.yAxes
[
751 this.layout
.options
.seriesToAxisMap
[setName
]];
752 var color
= this.colors
[setName
];
754 // setup graphics context
758 var prevYs
= [-1, -1];
759 var yscale
= axis
.yscale
;
760 // should be same color as the lines but only 15% opaque.
761 var rgb
= new RGBColor(color
);
762 var err_color
= 'rgba(' + rgb
.r
+ ',' + rgb
.g
+ ',' + rgb
.b
+ ',' +
764 ctx
.fillStyle
= err_color
;
766 for (var j
= 0; j
< this.layout
.points
.length
; j
++) {
767 var point
= this.layout
.points
[j
];
768 if (point
.name
== setName
) {
769 if (!Dygraph
.isOK(point
.y
)) {
776 var newYs
= [ prevY
- point
.errorPlus
* yscale
,
777 prevY
+ point
.errorMinus
* yscale
];
780 var newYs
= [ point
.y
- point
.errorPlus
* yscale
,
781 point
.y
+ point
.errorMinus
* yscale
];
783 newYs
[0] = this.area
.h
* newYs
[0] + this.area
.y
;
784 newYs
[1] = this.area
.h
* newYs
[1] + this.area
.y
;
787 ctx
.moveTo(prevX
, newYs
[0]);
789 ctx
.moveTo(prevX
, prevYs
[0]);
791 ctx
.lineTo(point
.canvasx
, newYs
[0]);
792 ctx
.lineTo(point
.canvasx
, newYs
[1]);
794 ctx
.lineTo(prevX
, newYs
[1]);
796 ctx
.lineTo(prevX
, prevYs
[1]);
801 prevX
= point
.canvasx
;
806 } else if (fillGraph
) {
807 var baseline
= [] // for stacked graphs: baseline for filling
809 // process sets in reverse order (needed for stacked graphs)
810 for (var i
= setCount
- 1; i
>= 0; i
--) {
811 var setName
= setNames
[i
];
812 var color
= this.colors
[setName
];
813 var axis
= this.layout
.options
.yAxes
[
814 this.layout
.options
.seriesToAxisMap
[setName
]];
815 var axisY
= 1.0 + axis
.minyval
* axis
.yscale
;
816 if (axisY
< 0.0) axisY
= 0.0;
817 else if (axisY
> 1.0) axisY
= 1.0;
818 axisY
= this.area
.h
* axisY
+ this.area
.y
;
820 // setup graphics context
823 var prevYs
= [-1, -1];
824 var yscale
= axis
.yscale
;
825 // should be same color as the lines but only 15% opaque.
826 var rgb
= new RGBColor(color
);
827 var err_color
= 'rgba(' + rgb
.r
+ ',' + rgb
.g
+ ',' + rgb
.b
+ ',' +
829 ctx
.fillStyle
= err_color
;
831 for (var j
= 0; j
< this.layout
.points
.length
; j
++) {
832 var point
= this.layout
.points
[j
];
833 if (point
.name
== setName
) {
834 if (!Dygraph
.isOK(point
.y
)) {
840 lastY
= baseline
[point
.canvasx
];
841 if (lastY
=== undefined
) lastY
= axisY
;
842 baseline
[point
.canvasx
] = point
.canvasy
;
843 newYs
= [ point
.canvasy
, lastY
];
845 newYs
= [ point
.canvasy
, axisY
];
848 ctx
.moveTo(prevX
, prevYs
[0]);
850 ctx
.lineTo(point
.canvasx
, prevYs
[0]);
852 ctx
.lineTo(point
.canvasx
, newYs
[0]);
854 ctx
.lineTo(point
.canvasx
, newYs
[1]);
855 ctx
.lineTo(prevX
, prevYs
[1]);
859 prevX
= point
.canvasx
;
866 for (var i
= 0; i
< setCount
; i
++) {
867 var setName
= setNames
[i
];
868 var color
= this.colors
[setName
];
869 var strokeWidth
= this.dygraph_
.attr_("strokeWidth", setName
);
871 // setup graphics context
873 var point
= this.layout
.points
[0];
874 var pointSize
= this.dygraph_
.attr_("pointSize", setName
);
875 var prevX
= null, prevY
= null;
876 var drawPoints
= this.dygraph_
.attr_("drawPoints", setName
);
877 var points
= this.layout
.points
;
878 for (var j
= 0; j
< points
.length
; j
++) {
879 var point
= points
[j
];
880 if (point
.name
== setName
) {
881 if (!Dygraph
.isOK(point
.canvasy
)) {
882 if (stepPlot
&& prevX
!= null) {
883 // Draw a horizontal line to the start of the missing data
885 ctx
.strokeStyle
= color
;
886 ctx
.lineWidth
= this.options
.strokeWidth
;
887 ctx
.moveTo(prevX
, prevY
);
888 ctx
.lineTo(point
.canvasx
, prevY
);
891 // this will make us move to the next point, not draw a line to it.
892 prevX
= prevY
= null;
894 // A point is "isolated" if it is non-null but both the previous
895 // and next points are null.
896 var isIsolated
= (!prevX
&& (j
== points
.length
- 1 ||
897 !Dygraph
.isOK(points
[j
+1].canvasy
)));
900 prevX
= point
.canvasx
;
901 prevY
= point
.canvasy
;
903 // TODO(danvk): figure out why this conditional is necessary.
906 ctx
.strokeStyle
= color
;
907 ctx
.lineWidth
= strokeWidth
;
908 ctx
.moveTo(prevX
, prevY
);
910 ctx
.lineTo(point
.canvasx
, prevY
);
912 prevX
= point
.canvasx
;
913 prevY
= point
.canvasy
;
914 ctx
.lineTo(prevX
, prevY
);
919 if (drawPoints
|| isIsolated
) {
921 ctx
.fillStyle
= color
;
922 ctx
.arc(point
.canvasx
, point
.canvasy
, pointSize
,
923 0, 2 * Math
.PI
, false);