b71044a88f1168cf7a1c8962de7baf709e185b73
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 // TODO(danvk): CONTRACT remove
34 DygraphLayout
.prototype.setAnnotations
= function(ann
) {
35 // The Dygraph object's annotations aren't parsed. We parse them here and
37 var parse
= this.attr_('xValueParser');
38 for (var i
= 0; i
< ann
.length
; i
++) {
41 this.dygraph_
.error("Annotations must have an 'x' property");
45 !(ann
[i
].hasOwnProperty('iconWidth') &&
46 ann
[i
].hasOwnProperty('iconHeight'))) {
47 this.dygraph_
.error("Must set iconWidth and iconHeight when setting " +
48 "annotation.icon property");
51 Dygraph
.update(a
, ann
[i
]);
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 var x1
= series
[0][0];
74 if (!this.minxval
|| x1
< this.minxval
) this.minxval
= x1
;
76 var x2
= series
[series
.length
- 1][0];
77 if (!this.maxxval
|| x2
> this.maxxval
) this.maxxval
= x2
;
80 this.xrange
= this.maxxval
- this.minxval
;
81 this.xscale
= (this.xrange
!= 0 ? 1/this.xrange
: 1.0);
83 this.minyval
= this.options
.yAxis
[0];
84 this.maxyval
= this.options
.yAxis
[1];
85 this.yrange
= this.maxyval
- this.minyval
;
86 this.yscale
= (this.yrange
!= 0 ? 1/this.yrange
: 1.0);
89 DygraphLayout
.prototype._evaluateLineCharts
= function() {
91 this.points
= new Array();
92 for (var setName
in this.datasets
) {
93 if (!this.datasets
.hasOwnProperty(setName
)) continue;
95 var dataset
= this.datasets
[setName
];
96 for (var j
= 0; j
< dataset
.length
; j
++) {
97 var item
= dataset
[j
];
100 x
: ((parseFloat(item
[0]) - this.minxval
) * this.xscale
),
101 y
: 1.0 - ((parseFloat(item
[1]) - this.minyval
) * this.yscale
),
102 xval
: parseFloat(item
[0]),
103 yval
: parseFloat(item
[1]),
107 // limit the x, y values so they do not overdraw
108 if (point
.y
<= 0.0) {
111 if (point
.y
>= 1.0) {
114 this.points
.push(point
);
119 DygraphLayout
.prototype._evaluateLineTicks
= function() {
120 this.xticks
= new Array();
121 for (var i
= 0; i
< this.options
.xTicks
.length
; i
++) {
122 var tick
= this.options
.xTicks
[i
];
123 var label
= tick
.label
;
124 var pos
= this.xscale
* (tick
.v
- this.minxval
);
125 if ((pos
>= 0.0) && (pos
<= 1.0)) {
126 this.xticks
.push([pos
, label
]);
130 this.yticks
= new Array();
131 for (var i
= 0; i
< this.options
.yTicks
.length
; i
++) {
132 var tick
= this.options
.yTicks
[i
];
133 var label
= tick
.label
;
134 var pos
= 1.0 - (this.yscale
* (tick
.v
- this.minyval
));
135 if ((pos
>= 0.0) && (pos
<= 1.0)) {
136 this.yticks
.push([pos
, label
]);
143 * Behaves the same way as PlotKit.Layout, but also copies the errors
146 DygraphLayout
.prototype.evaluateWithError
= function() {
148 if (!this.options
.errorBars
) return;
150 // Copy over the error terms
151 var i
= 0; // index in this.points
152 for (var setName
in this.datasets
) {
153 if (!this.datasets
.hasOwnProperty(setName
)) continue;
155 var dataset
= this.datasets
[setName
];
156 for (var j
= 0; j
< dataset
.length
; j
++, i
++) {
157 var item
= dataset
[j
];
158 var xv
= parseFloat(item
[0]);
159 var yv
= parseFloat(item
[1]);
161 if (xv
== this.points
[i
].xval
&&
162 yv
== this.points
[i
].yval
) {
163 this.points
[i
].errorMinus
= parseFloat(item
[2]);
164 this.points
[i
].errorPlus
= parseFloat(item
[3]);
170 DygraphLayout
.prototype._evaluateAnnotations
= function() {
171 // Add the annotations to the point to which they belong.
172 // Make a map from (setName, xval) to annotation for quick lookups.
173 var annotations
= {};
174 for (var i
= 0; i
< this.annotations
.length
; i
++) {
175 var a
= this.annotations
[i
];
176 annotations
[a
.xval
+ "," + a
.series
] = a
;
179 this.annotated_points
= [];
180 for (var i
= 0; i
< this.points
.length
; i
++) {
181 var p
= this.points
[i
];
182 var k
= p
.xval
+ "," + p
.name
;
183 if (k
in annotations
) {
184 p
.annotation
= annotations
[k
];
185 this.annotated_points
.push(p
);
191 * Convenience function to remove all the data sets from a graph
193 DygraphLayout
.prototype.removeAllDatasets
= function() {
194 delete this.datasets
;
195 this.datasets
= new Array();
199 * Change the values of various layout options
200 * @param {Object} new_options an associative array of new properties
202 DygraphLayout
.prototype.updateOptions
= function(new_options
) {
203 Dygraph
.update(this.options
, new_options
? new_options
: {});
206 // Subclass PlotKit.CanvasRenderer to add:
207 // 1. X/Y grid overlay
208 // 2. Ability to draw error bars (if required)
211 * Sets some PlotKit.CanvasRenderer options
212 * @param {Object} element The canvas to attach to
213 * @param {Layout} layout The DygraphLayout object for this graph.
214 * @param {Object} options Options to pass on to CanvasRenderer
216 DygraphCanvasRenderer
= function(dygraph
, element
, layout
, options
) {
217 // TODO(danvk): remove options, just use dygraph.attr_.
218 this.dygraph_
= dygraph
;
225 "axisLineColor": "black",
226 "axisLineWidth": 0.5,
228 "axisLabelColor": "black",
229 "axisLabelFont": "Arial",
230 "axisLabelFontSize": 9,
231 "axisLabelWidth": 50,
234 "gridLineColor": "rgb(128,128,128)",
236 "underlayCallback": null
238 Dygraph
.update(this.options
, options
);
240 this.layout
= layout
;
241 this.element
= element
;
242 this.container
= this.element
.parentNode
;
244 this.height
= this.element
.height
;
245 this.width
= this.element
.width
;
247 // --- check whether everything is ok before we return
248 if (!this.isIE
&& !(DygraphCanvasRenderer
.isSupported(this.element
)))
249 throw "Canvas is not supported.";
252 this.xlabels
= new Array();
253 this.ylabels
= new Array();
254 this.annotations
= new Array();
257 x
: this.options
.yAxisLabelWidth
+ 2 * this.options
.axisTickSize
,
260 this.area
.w
= this.width
- this.area
.x
- this.options
.rightGap
;
261 this.area
.h
= this.height
- this.options
.axisLabelFontSize
-
262 2 * this.options
.axisTickSize
;
264 this.container
.style
.position
= "relative";
265 this.container
.style
.width
= this.width
+ "px";
268 DygraphCanvasRenderer
.prototype.clear
= function() {
270 // VML takes a while to start up, so we just poll every this.IEDelay
272 if (this.clearDelay
) {
273 this.clearDelay
.cancel();
274 this.clearDelay
= null;
276 var context
= this.element
.getContext("2d");
279 // TODO(danvk): this is broken, since MochiKit.Async is gone.
280 this.clearDelay
= MochiKit
.Async
.wait(this.IEDelay
);
281 this.clearDelay
.addCallback(bind(this.clear
, this));
286 var context
= this.element
.getContext("2d");
287 context
.clearRect(0, 0, this.width
, this.height
);
289 for (var i
= 0; i
< this.xlabels
.length
; i
++) {
290 var el
= this.xlabels
[i
];
291 el
.parentNode
.removeChild(el
);
293 for (var i
= 0; i
< this.ylabels
.length
; i
++) {
294 var el
= this.ylabels
[i
];
295 el
.parentNode
.removeChild(el
);
297 for (var i
= 0; i
< this.annotations
.length
; i
++) {
298 var el
= this.annotations
[i
];
299 el
.parentNode
.removeChild(el
);
301 this.xlabels
= new Array();
302 this.ylabels
= new Array();
303 this.annotations
= new Array();
307 DygraphCanvasRenderer
.isSupported
= function(canvasName
) {
310 if (typeof(canvasName
) == 'undefined' || canvasName
== null)
311 canvas
= document
.createElement("canvas");
314 var context
= canvas
.getContext("2d");
317 var ie
= navigator
.appVersion
.match(/MSIE (\d\.\d)/);
318 var opera
= (navigator
.userAgent
.toLowerCase().indexOf("opera") != -1);
319 if ((!ie
) || (ie
[1] < 6) || (opera
))
327 * Draw an X/Y grid on top of the existing plot
329 DygraphCanvasRenderer
.prototype.render
= function() {
330 // Draw the new X/Y grid
331 var ctx
= this.element
.getContext("2d");
333 if (this.options
.underlayCallback
) {
334 this.options
.underlayCallback(ctx
, this.area
, this.layout
, this.dygraph_
);
337 if (this.options
.drawYGrid
) {
338 var ticks
= this.layout
.yticks
;
340 ctx
.strokeStyle
= this.options
.gridLineColor
;
341 ctx
.lineWidth
= this.options
.axisLineWidth
;
342 for (var i
= 0; i
< ticks
.length
; i
++) {
344 var y
= this.area
.y
+ ticks
[i
][0] * this.area
.h
;
347 ctx
.lineTo(x
+ this.area
.w
, y
);
353 if (this.options
.drawXGrid
) {
354 var ticks
= this.layout
.xticks
;
356 ctx
.strokeStyle
= this.options
.gridLineColor
;
357 ctx
.lineWidth
= this.options
.axisLineWidth
;
358 for (var i
=0; i
<ticks
.length
; i
++) {
359 var x
= this.area
.x
+ ticks
[i
][0] * this.area
.w
;
360 var y
= this.area
.y
+ this.area
.h
;
363 ctx
.lineTo(x
, this.area
.y
);
369 // Do the ordinary rendering, as before
370 this._renderLineChart();
372 this._renderAnnotations();
376 DygraphCanvasRenderer
.prototype._renderAxis
= function() {
377 if (!this.options
.drawXAxis
&& !this.options
.drawYAxis
)
380 var context
= this.element
.getContext("2d");
383 "position": "absolute",
384 "fontSize": this.options
.axisLabelFontSize
+ "px",
386 "color": this.options
.axisLabelColor
,
387 "width": this.options
.axisLabelWidth
+ "px",
390 var makeDiv
= function(txt
) {
391 var div
= document
.createElement("div");
392 for (var name
in labelStyle
) {
393 if (labelStyle
.hasOwnProperty(name
)) {
394 div
.style
[name
] = labelStyle
[name
];
397 div
.appendChild(document
.createTextNode(txt
));
403 context
.strokeStyle
= this.options
.axisLineColor
;
404 context
.lineWidth
= this.options
.axisLineWidth
;
406 if (this.options
.drawYAxis
) {
407 if (this.layout
.yticks
&& this.layout
.yticks
.length
> 0) {
408 for (var i
= 0; i
< this.layout
.yticks
.length
; i
++) {
409 var tick
= this.layout
.yticks
[i
];
410 if (typeof(tick
) == "function") return;
412 var y
= this.area
.y
+ tick
[0] * this.area
.h
;
414 context
.moveTo(x
, y
);
415 context
.lineTo(x
- this.options
.axisTickSize
, y
);
419 var label
= makeDiv(tick
[1]);
420 var top
= (y
- this.options
.axisLabelFontSize
/ 2);
421 if (top
< 0) top
= 0;
423 if (top
+ this.options
.axisLabelFontSize
+ 3 > this.height
) {
424 label
.style
.bottom
= "0px";
426 label
.style
.top
= top
+ "px";
428 label
.style
.left
= "0px";
429 label
.style
.textAlign
= "right";
430 label
.style
.width
= this.options
.yAxisLabelWidth
+ "px";
431 this.container
.appendChild(label
);
432 this.ylabels
.push(label
);
435 // The lowest tick on the y-axis often overlaps with the leftmost
436 // tick on the x-axis. Shift the bottom tick up a little bit to
437 // compensate if necessary.
438 var bottomTick
= this.ylabels
[0];
439 var fontSize
= this.options
.axisLabelFontSize
;
440 var bottom
= parseInt(bottomTick
.style
.top
) + fontSize
;
441 if (bottom
> this.height
- fontSize
) {
442 bottomTick
.style
.top
= (parseInt(bottomTick
.style
.top
) -
443 fontSize
/ 2) + "px";
448 context
.moveTo(this.area
.x
, this.area
.y
);
449 context
.lineTo(this.area
.x
, this.area
.y
+ this.area
.h
);
454 if (this.options
.drawXAxis
) {
455 if (this.layout
.xticks
) {
456 for (var i
= 0; i
< this.layout
.xticks
.length
; i
++) {
457 var tick
= this.layout
.xticks
[i
];
458 if (typeof(dataset
) == "function") return;
460 var x
= this.area
.x
+ tick
[0] * this.area
.w
;
461 var y
= this.area
.y
+ this.area
.h
;
463 context
.moveTo(x
, y
);
464 context
.lineTo(x
, y
+ this.options
.axisTickSize
);
468 var label
= makeDiv(tick
[1]);
469 label
.style
.textAlign
= "center";
470 label
.style
.bottom
= "0px";
472 var left
= (x
- this.options
.axisLabelWidth
/2);
473 if (left
+ this.options
.axisLabelWidth
> this.width
) {
474 left
= this.width
- this.options
.xAxisLabelWidth
;
475 label
.style
.textAlign
= "right";
479 label
.style
.textAlign
= "left";
482 label
.style
.left
= left
+ "px";
483 label
.style
.width
= this.options
.xAxisLabelWidth
+ "px";
484 this.container
.appendChild(label
);
485 this.xlabels
.push(label
);
490 context
.moveTo(this.area
.x
, this.area
.y
+ this.area
.h
);
491 context
.lineTo(this.area
.x
+ this.area
.w
, this.area
.y
+ this.area
.h
);
500 DygraphCanvasRenderer
.prototype._renderAnnotations
= function() {
501 var annotationStyle
= {
502 "position": "absolute",
503 "fontSize": this.options
.axisLabelFontSize
+ "px",
505 "overflow": "hidden",
508 var bindEvt
= function(eventName
, classEventName
, p
, self
) {
510 var a
= p
.annotation
;
511 if (a
.hasOwnProperty(eventName
)) {
512 a
[eventName
](a
, p
, self
.dygraph_
, e
);
513 } else if (self
.dygraph_
.attr_(classEventName
)) {
514 self
.dygraph_
.attr_(classEventName
)(a
, p
, self
.dygraph_
,e
);
519 // Get a list of point with annotations.
520 var points
= this.layout
.annotated_points
;
521 for (var i
= 0; i
< points
.length
; i
++) {
523 if (p
.canvasx
< this.area
.x
|| p
.canvasx
> this.area
.x
+ this.area
.w
) {
527 var a
= p
.annotation
;
529 if (a
.hasOwnProperty("tickHeight")) {
530 tick_height
= a
.tickHeight
;
533 var div
= document
.createElement("div");
534 for (var name
in annotationStyle
) {
535 if (annotationStyle
.hasOwnProperty(name
)) {
536 div
.style
[name
] = annotationStyle
[name
];
539 if (!a
.hasOwnProperty('icon')) {
540 div
.className
= "dygraphDefaultAnnotation";
542 if (a
.hasOwnProperty('cssClass')) {
543 div
.className
+= " " + a
.cssClass
;
546 var width
= a
.hasOwnProperty('height') ? a
.height
: 20;
547 var height
= a
.hasOwnProperty('width') ? a
.width
: 16;
548 if (a
.hasOwnProperty('icon')) {
549 var img
= document
.createElement("img");
551 img
.width
= width
= a
.iconWidth
;
552 img
.height
= height
= a
.iconHeight
;
553 div
.appendChild(img
);
554 } else if (p
.annotation
.hasOwnProperty('shortText')) {
555 div
.appendChild(document
.createTextNode(p
.annotation
.shortText
));
557 div
.style
.left
= (p
.canvasx
- width
/ 2) + "px";
558 div
.style
.top
= (p
.canvasy
- height
- tick_height
) + "px";
559 div
.style
.width
= width
+ "px";
560 div
.style
.height
= height
+ "px";
561 div
.title
= p
.annotation
.text
;
562 div
.style
.color
= this.colors
[p
.name
];
563 div
.style
.borderColor
= this.colors
[p
.name
];
566 Dygraph
.addEvent(div
, 'click',
567 bindEvt('clickHandler', 'annotationClickHandler', p
, this));
568 Dygraph
.addEvent(div
, 'mouseover',
569 bindEvt('mouseOverHandler', 'annotationMouseOverHandler', p
, this));
570 Dygraph
.addEvent(div
, 'mouseout',
571 bindEvt('mouseOutHandler', 'annotationMouseOutHandler', p
, this));
572 Dygraph
.addEvent(div
, 'dblclick',
573 bindEvt('dblClickHandler', 'annotationDblClickHandler', p
, this));
575 this.container
.appendChild(div
);
576 this.annotations
.push(div
);
578 var ctx
= this.element
.getContext("2d");
579 ctx
.strokeStyle
= this.colors
[p
.name
];
581 ctx
.moveTo(p
.canvasx
, p
.canvasy
);
582 ctx
.lineTo(p
.canvasx
, p
.canvasy
- 2 - tick_height
);
590 * Overrides the CanvasRenderer method to draw error bars
592 DygraphCanvasRenderer
.prototype._renderLineChart
= function() {
593 var context
= this.element
.getContext("2d");
594 var colorCount
= this.options
.colorScheme
.length
;
595 var colorScheme
= this.options
.colorScheme
;
596 var fillAlpha
= this.options
.fillAlpha
;
597 var errorBars
= this.layout
.options
.errorBars
;
598 var fillGraph
= this.layout
.options
.fillGraph
;
599 var stackedGraph
= this.layout
.options
.stackedGraph
;
600 var stepPlot
= this.layout
.options
.stepPlot
;
603 for (var name
in this.layout
.datasets
) {
604 if (this.layout
.datasets
.hasOwnProperty(name
)) {
608 var setCount
= setNames
.length
;
611 for (var i
= 0; i
< setCount
; i
++) {
612 this.colors
[setNames
[i
]] = colorScheme
[i
% colorCount
];
617 for (var i
= 0; i
< this.layout
.points
.length
; i
++) {
618 var point
= this.layout
.points
[i
];
619 point
.canvasx
= this.area
.w
* point
.x
+ this.area
.x
;
620 point
.canvasy
= this.area
.h
* point
.y
+ this.area
.y
;
624 var isOK
= function(x
) { return x
&& !isNaN(x
); };
629 this.dygraph_
.warn("Can't use fillGraph option with error bars");
632 for (var i
= 0; i
< setCount
; i
++) {
633 var setName
= setNames
[i
];
634 var color
= this.colors
[setName
];
636 // setup graphics context
640 var prevYs
= [-1, -1];
641 var yscale
= this.layout
.yscale
;
642 // should be same color as the lines but only 15% opaque.
643 var rgb
= new RGBColor(color
);
644 var err_color
= 'rgba(' + rgb
.r
+ ',' + rgb
.g
+ ',' + rgb
.b
+ ',' +
646 ctx
.fillStyle
= err_color
;
648 for (var j
= 0; j
< this.layout
.points
.length
; j
++) {
649 var point
= this.layout
.points
[j
];
650 if (point
.name
== setName
) {
651 if (!isOK(point
.y
)) {
658 var newYs
= [ prevY
- point
.errorPlus
* yscale
,
659 prevY
+ point
.errorMinus
* yscale
];
662 var newYs
= [ point
.y
- point
.errorPlus
* yscale
,
663 point
.y
+ point
.errorMinus
* yscale
];
665 newYs
[0] = this.area
.h
* newYs
[0] + this.area
.y
;
666 newYs
[1] = this.area
.h
* newYs
[1] + this.area
.y
;
669 ctx
.moveTo(prevX
, newYs
[0]);
671 ctx
.moveTo(prevX
, prevYs
[0]);
673 ctx
.lineTo(point
.canvasx
, newYs
[0]);
674 ctx
.lineTo(point
.canvasx
, newYs
[1]);
676 ctx
.lineTo(prevX
, newYs
[1]);
678 ctx
.lineTo(prevX
, prevYs
[1]);
683 prevX
= point
.canvasx
;
688 } else if (fillGraph
) {
689 var axisY
= 1.0 + this.layout
.minyval
* this.layout
.yscale
;
690 if (axisY
< 0.0) axisY
= 0.0;
691 else if (axisY
> 1.0) axisY
= 1.0;
692 axisY
= this.area
.h
* axisY
+ this.area
.y
;
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
];
701 // setup graphics context
704 var prevYs
= [-1, -1];
705 var yscale
= this.layout
.yscale
;
706 // should be same color as the lines but only 15% opaque.
707 var rgb
= new RGBColor(color
);
708 var err_color
= 'rgba(' + rgb
.r
+ ',' + rgb
.g
+ ',' + rgb
.b
+ ',' +
710 ctx
.fillStyle
= err_color
;
712 for (var j
= 0; j
< this.layout
.points
.length
; j
++) {
713 var point
= this.layout
.points
[j
];
714 if (point
.name
== setName
) {
715 if (!isOK(point
.y
)) {
721 lastY
= baseline
[point
.canvasx
];
722 if (lastY
=== undefined
) lastY
= axisY
;
723 baseline
[point
.canvasx
] = point
.canvasy
;
724 newYs
= [ point
.canvasy
, lastY
];
726 newYs
= [ point
.canvasy
, axisY
];
729 ctx
.moveTo(prevX
, prevYs
[0]);
731 ctx
.lineTo(point
.canvasx
, prevYs
[0]);
733 ctx
.lineTo(point
.canvasx
, newYs
[0]);
735 ctx
.lineTo(point
.canvasx
, newYs
[1]);
736 ctx
.lineTo(prevX
, prevYs
[1]);
740 prevX
= point
.canvasx
;
747 for (var i
= 0; i
< setCount
; i
++) {
748 var setName
= setNames
[i
];
749 var color
= this.colors
[setName
];
751 // setup graphics context
753 var point
= this.layout
.points
[0];
754 var pointSize
= this.dygraph_
.attr_("pointSize");
755 var prevX
= null, prevY
= null;
756 var drawPoints
= this.dygraph_
.attr_("drawPoints");
757 var points
= this.layout
.points
;
758 for (var j
= 0; j
< points
.length
; j
++) {
759 var point
= points
[j
];
760 if (point
.name
== setName
) {
761 if (!isOK(point
.canvasy
)) {
762 // this will make us move to the next point, not draw a line to it.
763 prevX
= prevY
= null;
765 // A point is "isolated" if it is non-null but both the previous
766 // and next points are null.
767 var isIsolated
= (!prevX
&& (j
== points
.length
- 1 ||
768 !isOK(points
[j
+1].canvasy
)));
771 prevX
= point
.canvasx
;
772 prevY
= point
.canvasy
;
775 ctx
.strokeStyle
= color
;
776 ctx
.lineWidth
= this.options
.strokeWidth
;
777 ctx
.moveTo(prevX
, prevY
);
779 ctx
.lineTo(point
.canvasx
, prevY
);
781 prevX
= point
.canvasx
;
782 prevY
= point
.canvasy
;
783 ctx
.lineTo(prevX
, prevY
);
787 if (drawPoints
|| isIsolated
) {
789 ctx
.fillStyle
= color
;
790 ctx
.arc(point
.canvasx
, point
.canvasy
, pointSize
,
791 0, 2 * Math
.PI
, false);