1 // Copyright 2006 Dan Vanderkam (danvdk@gmail.com)
2 // All Rights Reserved.
5 * @fileoverview Subclasses various parts of PlotKit to meet the additional
6 * needs of Dygraph: grid overlays and error bars
9 // Subclass PlotKit.Layout to add:
10 // 1. Sigma/errorBars properties
11 // 2. Copy error terms for PlotKit.CanvasRenderer._renderLineChart
14 * Creates a new DygraphLayout object. Options are the same as those allowed
15 * by the PlotKit.Layout constructor.
16 * @param {Object} options Options for PlotKit.Layout
17 * @return {Object} The DygraphLayout object
19 DygraphLayout
= function(options
) {
20 PlotKit
.Layout
.call(this, "line", options
);
22 DygraphLayout
.prototype = new PlotKit
.Layout();
25 * Behaves the same way as PlotKit.Layout, but also copies the errors
28 DygraphLayout
.prototype.evaluateWithError
= function() {
30 if (!this.options
.errorBars
) return;
32 // Copy over the error terms
33 var i
= 0; // index in this.points
34 for (var setName
in this.datasets
) {
36 var dataset
= this.datasets
[setName
];
37 if (PlotKit
.Base
.isFuncLike(dataset
)) continue;
38 for (var j
= 0; j
< dataset
.length
; j
++, i
++) {
39 var item
= dataset
[j
];
40 var xv
= parseFloat(item
[0]);
41 var yv
= parseFloat(item
[1]);
43 if (xv
== this.points
[i
].xval
&&
44 yv
== this.points
[i
].yval
) {
45 this.points
[i
].errorMinus
= parseFloat(item
[2]);
46 this.points
[i
].errorPlus
= parseFloat(item
[3]);
53 * Convenience function to remove all the data sets from a graph
55 DygraphLayout
.prototype.removeAllDatasets
= function() {
57 this.datasets
= new Array();
61 * Change the values of various layout options
62 * @param {Object} new_options an associative array of new properties
64 DygraphLayout
.prototype.updateOptions
= function(new_options
) {
65 MochiKit
.Base
.update(this.options
, new_options
? new_options
: {});
68 // Subclass PlotKit.CanvasRenderer to add:
69 // 1. X/Y grid overlay
70 // 2. Ability to draw error bars (if required)
73 * Sets some PlotKit.CanvasRenderer options
74 * @param {Object} element The canvas to attach to
75 * @param {Layout} layout The DygraphLayout object for this graph.
76 * @param {Object} options Options to pass on to CanvasRenderer
78 DygraphCanvasRenderer
= function(dygraph
, element
, layout
, options
) {
79 // TODO(danvk): remove options, just use dygraph.attr_.
80 PlotKit
.CanvasRenderer
.call(this, element
, layout
, options
);
81 this.dygraph_
= dygraph
;
82 this.options
.shouldFill
= false;
83 this.options
.shouldStroke
= true;
84 this.options
.drawYGrid
= true;
85 this.options
.drawXGrid
= true;
86 this.options
.gridLineColor
= MochiKit
.Color
.Color
.grayColor();
87 MochiKit
.Base
.update(this.options
, options
);
89 // TODO(danvk) This shouldn't be necessary: effects should be overlaid
90 this.options
.drawBackground
= false;
92 DygraphCanvasRenderer
.prototype = new PlotKit
.CanvasRenderer();
95 * Draw an X/Y grid on top of the existing plot
97 DygraphCanvasRenderer
.prototype.render
= function() {
98 // Draw the new X/Y grid
99 var ctx
= this.element
.getContext("2d");
100 if (this.options
.drawYGrid
) {
101 var ticks
= this.layout
.yticks
;
103 ctx
.strokeStyle
= this.options
.gridLineColor
.toRGBString();
104 ctx
.lineWidth
= this.options
.axisLineWidth
;
105 for (var i
= 0; i
< ticks
.length
; i
++) {
107 var y
= this.area
.y
+ ticks
[i
][0] * this.area
.h
;
110 ctx
.lineTo(x
+ this.area
.w
, y
);
116 if (this.options
.drawXGrid
) {
117 var ticks
= this.layout
.xticks
;
119 ctx
.strokeStyle
= this.options
.gridLineColor
.toRGBString();
120 ctx
.lineWidth
= this.options
.axisLineWidth
;
121 for (var i
=0; i
<ticks
.length
; i
++) {
122 var x
= this.area
.x
+ ticks
[i
][0] * this.area
.w
;
123 var y
= this.area
.y
+ this.area
.h
;
126 ctx
.lineTo(x
, this.area
.y
);
132 // Do the ordinary rendering, as before
133 // TODO(danvk) Call super.render()
134 this._renderLineChart();
135 this._renderLineAxis();
139 * Overrides the CanvasRenderer method to draw error bars
141 DygraphCanvasRenderer
.prototype._renderLineChart
= function() {
142 var context
= this.element
.getContext("2d");
143 var colorCount
= this.options
.colorScheme
.length
;
144 var colorScheme
= this.options
.colorScheme
;
145 var setNames
= MochiKit
.Base
.keys(this.layout
.datasets
);
146 var errorBars
= this.layout
.options
.errorBars
;
147 var setCount
= setNames
.length
;
148 var bind
= MochiKit
.Base
.bind
;
149 var partial
= MochiKit
.Base
.partial
;
152 var updatePoint
= function(point
) {
153 point
.canvasx
= this.area
.w
* point
.x
+ this.area
.x
;
154 point
.canvasy
= this.area
.h
* point
.y
+ this.area
.y
;
156 MochiKit
.Iter
.forEach(this.layout
.points
, updatePoint
, this);
159 var isOK
= function(x
) { return x
&& !isNaN(x
); };
160 var makePath
= function(ctx
) {
161 for (var i
= 0; i
< setCount
; i
++) {
162 var setName
= setNames
[i
];
163 var color
= colorScheme
[i
%colorCount
];
164 var strokeX
= this.options
.strokeColorTransform
;
166 // setup graphics context
168 context
.strokeStyle
= color
.toRGBString();
169 context
.lineWidth
= this.options
.strokeWidth
;
170 var point
= this.layout
.points
[0];
171 var pointSize
= this.dygraph_
.attr_("pointSize");
172 var prevX
= null, prevY
= null;
173 var drawPoints
= this.dygraph_
.attr_("drawPoints");
174 var points
= this.layout
.points
;
175 for (var j
= 0; j
< points
.length
; j
++) {
176 var point
= points
[j
];
177 if (point
.name
== setName
) {
178 if (!isOK(point
.canvasy
)) {
179 // this will make us move to the next point, not draw a line to it.
180 prevX
= prevY
= null;
182 // A point is "isolated" if it is non-null but both the previous
183 // and next points are null.
184 var isIsolated
= (!prevX
&& (j
== points
.length
- 1 ||
185 !isOK(points
[j
+1].canvasy
)));
188 prevX
= point
.canvasx
;
189 prevY
= point
.canvasy
;
192 ctx
.moveTo(prevX
, prevY
);
193 prevX
= point
.canvasx
;
194 prevY
= point
.canvasy
;
195 ctx
.lineTo(prevX
, prevY
);
199 if (drawPoints
|| isIsolated
) {
201 ctx
.fillStyle
= color
.toRGBString();
202 ctx
.arc(point
.canvasx
, point
.canvasy
, pointSize
, 0, 360, false);
211 var makeErrorBars
= function(ctx
) {
212 for (var i
= 0; i
< setCount
; i
++) {
213 var setName
= setNames
[i
];
214 var color
= colorScheme
[i
% colorCount
];
215 var strokeX
= this.options
.strokeColorTransform
;
217 // setup graphics context
219 context
.strokeStyle
= color
.toRGBString();
220 context
.lineWidth
= this.options
.strokeWidth
;
222 var prevYs
= [-1, -1];
224 var yscale
= this.layout
.yscale
;
225 var errorTrapezoid
= function(ctx_
,point
) {
227 if (point
.name
== setName
) {
228 if (!point
.y
|| isNaN(point
.y
)) {
232 var newYs
= [ point
.y
- point
.errorPlus
* yscale
,
233 point
.y
+ point
.errorMinus
* yscale
];
234 newYs
[0] = this.area
.h
* newYs
[0] + this.area
.y
;
235 newYs
[1] = this.area
.h
* newYs
[1] + this.area
.y
;
237 ctx_
.moveTo(prevX
, prevYs
[0]);
238 ctx_
.lineTo(point
.canvasx
, newYs
[0]);
239 ctx_
.lineTo(point
.canvasx
, newYs
[1]);
240 ctx_
.lineTo(prevX
, prevYs
[1]);
243 prevYs
[0] = newYs
[0];
244 prevYs
[1] = newYs
[1];
245 prevX
= point
.canvasx
;
248 // should be same color as the lines
249 var err_color
= color
.colorWithAlpha(0.15);
250 ctx
.fillStyle
= err_color
.toRGBString();
252 MochiKit
.Iter
.forEach(this.layout
.points
, partial(errorTrapezoid
, ctx
), this);
258 bind(makeErrorBars
, this)(context
);
259 bind(makePath
, this)(context
);