add comment about the "remove canvasx/y" optimization
[dygraphs.git] / dashed-canvas.js
CommitLineData
fb63bf1b
DV
1/**
2 * @license
3 * Copyright 2012 Dan Vanderkam (danvdk@gmail.com)
4 * MIT-licensed (http://opensource.org/licenses/MIT)
5 */
6
7/**
8 * @fileoverview Adds support for dashed lines to the HTML5 canvas.
9 *
10 * Usage:
11 * var ctx = canvas.getContext("2d");
12 * ctx.installPattern([10, 5]) // draw 10 pixels, skip 5 pixels, repeat.
13 * ctx.beginPath();
14 * ctx.moveTo(100, 100); // start the first line segment.
15 * ctx.lineTo(150, 200);
16 * ctx.lineTo(200, 100);
17 * ctx.moveTo(300, 150); // start a second, unconnected line
18 * ctx.lineTo(400, 250);
19 * ...
20 * ctx.stroke(); // draw the dashed line.
21 * ctx.uninstallPattern();
22 *
23 * This is designed to leave the canvas untouched when it's not used.
24 * If you never install a pattern, or call uninstallPattern(), then the canvas
25 * will be exactly as it would have if you'd never used this library. The only
26 * difference from the standard canvas will be the "installPattern" method of
27 * the drawing context.
28 */
29
30CanvasRenderingContext2D.prototype.installPattern = function(pattern) {
31 if (typeof(this.isPatternInstalled) !== 'undefined') {
32 throw "Must un-install old line pattern before installing a new one.";
33 }
34 this.isPatternInstalled = true;
35
36 var dashedLineToHistory = [0, 0];
37
38 // list of connected line segements:
39 // [ [x1, y1], ..., [xn, yn] ], [ [x1, y1], ..., [xn, yn] ]
40 var segments = [];
41
42 // Stash away copies of the unmodified line-drawing functions.
43 var realBeginPath = this.beginPath;
44 var realLineTo = this.lineTo;
45 var realMoveTo = this.moveTo;
46 var realStroke = this.stroke;
47
48 this.uninstallPattern = function() {
49 this.beginPath = realBeginPath;
50 this.lineTo = realLineTo;
51 this.moveTo = realMoveTo;
52 this.stroke = realStroke;
53 this.uninstallPattern = undefined;
54 this.isPatternInstalled = undefined;
55 };
56
57 // Keep our own copies of the line segments as they're drawn.
58 this.beginPath = function() {
59 segments = [];
60 realBeginPath.call(this);
61 };
62 this.moveTo = function(x, y) {
63 segments.push([[x, y]]);
64 realMoveTo.call(this, x, y);
65 };
66 this.lineTo = function(x, y) {
67 var last = segments[segments.length - 1];
68 last.push([x, y]);
69 };
70
71 this.stroke = function() {
72 if (segments.length === 0) {
73 // Maybe the user is drawing something other than a line.
74 // TODO(danvk): test this case.
75 realStroke.call(this);
76 return;
77 }
78
79 for (var i = 0; i < segments.length; i++) {
80 var seg = segments[i];
81 var x1 = seg[0][0], y1 = seg[0][1];
82 for (var j = 1; j < seg.length; j++) {
83 // Draw a dashed line from (x1, y1) - (x2, y2)
84 var x2 = seg[j][0], y2 = seg[j][1];
85 this.save();
86
87 // Calculate transformation parameters
88 var dx = (x2-x1);
89 var dy = (y2-y1);
90 var len = Math.sqrt(dx*dx + dy*dy);
91 var rot = Math.atan2(dy, dx);
92
93 // Set transformation
94 this.translate(x1, y1);
95 realMoveTo.call(this, 0, 0);
96 this.rotate(rot);
97
98 // Set last pattern index we used for this pattern.
99 var patternIndex = dashedLineToHistory[0];
100 x = 0;
101 while (len > x) {
102 // Get the length of the pattern segment we are dealing with.
103 segment = pattern[patternIndex];
104 // If our last draw didn't complete the pattern segment all the way
105 // we will try to finish it. Otherwise we will try to do the whole
106 // segment.
107 if (dashedLineToHistory[1]) {
108 x += dashedLineToHistory[1];
109 } else {
110 x += segment;
111 }
112
113 if (x > len) {
114 // We were unable to complete this pattern index all the way, keep
115 // where we are the history so our next draw continues where we
116 // left off in the pattern.
117 dashedLineToHistory = [patternIndex, x-len];
118 x = len;
119 } else {
120 // We completed this patternIndex, we put in the history that we
121 // are on the beginning of the next segment.
122 dashedLineToHistory = [(patternIndex+1)%pattern.length, 0];
123 }
124
125 // We do a line on a even pattern index and just move on a odd
126 // pattern index. The move is the empty space in the dash.
127 if (patternIndex % 2 === 0) {
128 realLineTo.call(this, x, 0);
129 } else {
130 realMoveTo.call(this, x, 0);
131 }
132
133 // If we are not done, next loop process the next pattern segment, or
134 // the first segment again if we are at the end of the pattern.
135 patternIndex = (patternIndex+1) % pattern.length;
136 }
137
138 this.restore();
139 x1 = x2, y1 = y2;
140 }
141 }
142 realStroke.call(this);
143 segments = [];
144 };
145};