1<!DOCTYPE html>
2<html lang="en">
3<head>
4
5
6<style>
7html {
8  font-family: Helvetica, Arial, sans-serif;
9  font-size: 100%;
10}
11
12.controls {
13  margin: 1em 0;
14}
15
16button {
17  display: inline-block;
18  border-radius: 3px;
19  border: none;
20  font-size: 0.9rem;
21  padding: 0.4rem 0.8em;
22  background: #69c773;
23  border-bottom: 1px solid #498b50;
24  color: white;
25  -webkit-font-smoothing: antialiased;
26  font-weight: bold;
27  margin: 0 0.25rem;
28  text-align: center;
29}
30
31button:hover, button:focus {
32  opacity: 0.75;
33  cursor: pointer;
34}
35
36button:active {
37  opacity: 1;
38  box-shadow: 0 -3px 10px rgba(0, 0, 0, 0.1) inset;
39}
40
41</style>
42
43<! set height back to 500 />
44<svg id="svg" width="800" height="500"
45    xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
46
47<defs>
48    <radialGradient id="grad1" cx="200" cy="200" r="300" gradientUnits="userSpaceOnUse">
49      <stop offset="0%"   style="stop-color:rgb(0,0,255); stop-opacity:0.3" />
50      <stop offset="100%" style="stop-color:rgb(0,0,255); stop-opacity:0" />
51    </radialGradient>
52    <radialGradient id="grad2" cx="200" cy="200" r="300" gradientUnits="userSpaceOnUse">
53      <stop offset="0%"   style="stop-color:rgb(0,255,0); stop-opacity:0.3" />
54      <stop offset="100%" style="stop-color:rgb(0,255,0); stop-opacity:0" />
55    </radialGradient>
56    <radialGradient id="grad3" cx="200" cy="200" r="300" gradientUnits="userSpaceOnUse">
57      <stop offset="0%"   style="stop-color:rgb(255,0,0); stop-opacity:0.3" />
58      <stop offset="100%" style="stop-color:rgb(255,0,0); stop-opacity:0" />
59    </radialGradient>
60    <radialGradient id="grad4" cx="200" cy="200" r="300" gradientUnits="userSpaceOnUse">
61      <stop offset="0%"   style="stop-color:rgb(192,63,192); stop-opacity:0.3" />
62      <stop offset="100%" style="stop-color:rgb(192,63,192); stop-opacity:0" />
63    </radialGradient>
64    <radialGradient id="grad5" cx="200" cy="200" r="300" gradientUnits="userSpaceOnUse">
65      <stop offset="0%"   style="stop-color:rgb(127,127,0); stop-opacity:0.3" />
66      <stop offset="100%" style="stop-color:rgb(127,127,0); stop-opacity:0" />
67    </radialGradient>
68    <radialGradient id="grad6" cx="200" cy="200" r="300" gradientUnits="userSpaceOnUse">
69      <stop offset="0%"   style="stop-color:rgb(127,0,127); stop-opacity:0.3" />
70      <stop offset="100%" style="stop-color:rgb(127,0,127); stop-opacity:0" />
71    </radialGradient>
72    <radialGradient id="grad7" cx="200" cy="200" r="300" gradientUnits="userSpaceOnUse">
73      <stop offset="0%"   style="stop-color:rgb(0,127,127); stop-opacity:0.3" />
74      <stop offset="100%" style="stop-color:rgb(0,127,127); stop-opacity:0" />
75    </radialGradient>
76    <radialGradient id="grad8" cx="200" cy="200" r="300" gradientUnits="userSpaceOnUse">
77      <stop offset="0%"   style="stop-color:rgb(63,192,63); stop-opacity:0.3" />
78      <stop offset="100%" style="stop-color:rgb(63,192,63); stop-opacity:0" />
79    </radialGradient>
80</defs>
81
82<path id="circleFill" d="M300,200 A 100,100 0,0,0 300,200" fill="#777" fill-opacity="0" />
83<path id="circle" d="M300,200 A 100,100 0,0,0 300,200" fill="none" stroke="black" />
84
85<! elements for keyframe 1 />
86<text id="spanWedgeDesc" fill-opacity="0" >
87All spans are contained by a wedge.
88</text>
89<path id="span1" d="M200,200 Q300,300 200,300" fill="none" stroke="black" stroke-opacity="0"/>
90<path id="span2" d="M200,200 C100,300 100,400 200,300" fill="none" stroke="black" stroke-opacity="0"/>
91<path id="span3" d="M200,200 C300,100 100,400 300,200" fill="none" stroke="black" stroke-opacity="0"/>
92<path id="wedge1" d="M200,200 L500,500 A 424.26,424.26 0,0,1 200,624.26 z" fill="url(#grad1)" fill-opacity="0"/>
93<path id="wedge2" d="M200,200 L200,624.26 A 424.26,424.26 0,0,1 -100,500 z" fill="url(#grad2)" fill-opacity="0"/>
94<path id="wedge3" d="M200,200 L500,-100 A 424.26,424.26 0,0,1 240,622.5 z" fill="url(#grad3)" fill-opacity="0"/>
95
96<! keyframe 2 />
97<text id="trivialWedgeDesc1" fill-opacity="0" >
98Wedges that don't overlap can be
99</text>
100<text id="trivialWedgeDesc2" y="240" fill-opacity="0" >
101easily sorted.
102</text>
103<path id="span4" d="M200,200 Q300,300 400,300" fill="none" stroke="black" stroke-opacity="0"/>
104<path id="span5" d="M200,200 Q280,320 200,400" fill="none" stroke="black" stroke-opacity="0"/>
105<path id="span6" d="M200,200 Q60,340 100,400" fill="none" stroke="black" stroke-opacity="0"/>
106<path id="wedge4" d="M200,200 L500,500 A 424.26,424.26 0,0,1 579.47,389.74 z" fill="url(#grad1)" fill-opacity="0"/>
107<path id="wedge5" d="M200,200 L389.74,579.47 A 424.26,424.26 0,0,1 200,500 z" fill="url(#grad2)" fill-opacity="0"/>
108<path id="wedge6" d="M200,200 L10.26,579.47 A 424.26,424.26 0,0,1 -100,500 z" fill="url(#grad3)" fill-opacity="0"/>
109
110
111<! keyframe 3 />
112<text id="sectorDesc1" fill-opacity="0" >
113A sector is a wedge of a circle
114</text>
115<text id="sectorDesc2" y="240" fill-opacity="0" >
116containing a range of points.
117</text>
118<g id="xaxis" stroke-opacity="0" fill-opacity="0">
119    <path d="M100,200 L300,200" fill="none" stroke="rgb(191,191,191)"/>
120    <text x="100" y="220" fill="rgb(191,191,191)">-X</text>
121    <text x="300" y="220" text-anchor="end" fill="rgb(191,191,191)">+X</text>
122</g>
123<g id="yaxis" stroke-opacity="0" fill-opacity="0">
124    <path d="M200,100 L200,300" fill="none" stroke="rgb(191,191,191)"/>
125    <text x="205" y="100" alignment-baseline="hanging" fill="rgb(191,191,191)">-Y</text>
126    <text x="205" y="300" fill="rgb(191,191,191)">+Y</text>
127</g>
128<text id="sectorDescXYA" x="500" y="310" fill="rgb(0,0,255)" fill-opacity="0">
129X &gt; 0>&nbsp;&nbsp;Y &gt; 0&nbsp;&nbsp;&nbsp;&nbsp;Y &lt; X</text>
130<text id="sectorDescXYB" x="500" y="360" fill="rgb(0,127,0)" fill-opacity="0">
131X &lt; 0&nbsp;&nbsp;&nbsp;Y &gt; 0&nbsp;&nbsp;&nbsp;-Y &lt; X</text>
132<text id="sectorDescXYC" x="500" y="410" fill="rgb(255,0,0)" fill-opacity="0">
133X &lt; 0&nbsp;&nbsp;&nbsp;Y &lt; 0&nbsp;&nbsp;&nbsp;&nbsp;Y &lt; X</text>
134<path id="wedgeXY8" d="M200,200 L500,500 A 424.26,424.26 0,0,1 624.26,200 z" fill="url(#grad1)" fill-opacity="0"/>
135<path id="wedgeXY6" d="M200,200 L-100,500 A 424.26,424.26 0,0,1 200,624.26 z" fill="url(#grad2)" fill-opacity="0"/>
136<path id="wedgeXY3" d="M200,200 L-100,-100 A 424.26,424.26 0,0,1 200,-175.74 z" fill="url(#grad3)" fill-opacity="0"/>
137
138<! keyframe 4 />
139<text id="lineSingleDesc" fill-opacity="0" >
140Line spans are contained by a single sector.
141</text>
142<text id="sectorDescXY1" x="500" y="460" fill="rgb(192,63,192)" fill-opacity="0">
143X &gt; 0&nbsp;&nbsp;&nbsp;Y &lt; 0&nbsp;&nbsp;&nbsp;-Y &lt; X</text>
144<text id="sectorDescXY2" x="500" y="460" fill="rgb(127,127,0)" fill-opacity="0">
145X &gt; 0&nbsp;&nbsp;&nbsp;Y &lt; 0&nbsp;&nbsp;&nbsp;-Y &gt; X</text>
146<text id="sectorDescXY3" x="500" y="460" fill="rgb(255,0,0)" fill-opacity="0">
147X &lt; 0&nbsp;&nbsp;&nbsp;Y &lt; 0&nbsp;&nbsp;&nbsp;&nbsp;Y &lt; X</text>
148<text id="sectorDescXY4" x="500" y="460" fill="rgb(127,0,127)" fill-opacity="0">
149X &lt; 0&nbsp;&nbsp;&nbsp;Y &lt; 0&nbsp;&nbsp;&nbsp;&nbsp;Y &gt; X</text>
150<text id="sectorDescXY5" x="500" y="460" fill="rgb(0,127,127)" fill-opacity="0">
151X &lt; 0&nbsp;&nbsp;&nbsp;Y &gt; 0&nbsp;&nbsp;&nbsp;-Y &lt; X</text>
152<text id="sectorDescXY6" x="500" y="460" fill="rgb(0,127,0)" fill-opacity="0">
153X &lt; 0&nbsp;&nbsp;&nbsp;Y &gt; 0&nbsp;&nbsp;&nbsp;-Y &lt; X</text>
154<text id="sectorDescXY7" x="500" y="460" fill="rgb(63,192,63)" fill-opacity="0">
155X &gt; 0&nbsp;&nbsp;&nbsp;Y &gt; 0&nbsp;&nbsp;&nbsp;&nbsp;Y &gt; X</text>
156<text id="sectorDescXY8" x="500" y="460" fill="rgb(0,0,255)" fill-opacity="0">
157X &gt; 0&nbsp;&nbsp;&nbsp;Y &gt; 0&nbsp;&nbsp;&nbsp;&nbsp;Y &lt; X</text>
158<path id="wedgeXY1" d="M200,200 L500,-100 A 424.26,424.26 0,0,1 624.26,200 z" fill="url(#grad4)" fill-opacity="0"/>
159<path id="wedgeXY2" d="M200,200 L200,-175.74 A 424.26,424.26 0,0,1 500,-100 z" fill="url(#grad5)" fill-opacity="0"/>
160<path id="wedgeXY4" d="M200,200 L-175.74,200 A 424.26,424.26 0,0,1 -100,-100 z" fill="url(#grad6)" fill-opacity="0"/>
161<path id="wedgeXY5" d="M200,200 L-100,500 A 424.26,424.26 0,0,1 -175.74,200 z" fill="url(#grad7)" fill-opacity="0"/>
162<path id="wedgeXY7" d="M200,200 L200,624.26 A 424.26,424.26 0,0,1 500,500 z" fill="url(#grad8)" fill-opacity="0"/>
163<path id="lineSegment" d="M200,200 L200,624.26" fill="none" stroke="black" stroke-opacity="0"/>
164
165<! keyframe 5 />
166<text id="curveMultipleDesc1" fill-opacity="0" >
167A curve span may cover more
168</text>
169<text id="curveMultipleDesc2" y="240" fill-opacity="0" >
170than one sector.
171</text>
172<path id="curveSegment" d="M200,200 C250,200 300,150 300,100" fill="none" stroke="black" stroke-opacity="0"/>
173<path id="curveSegment1" d="M200,200 C250,200 300,150 300,100" fill="none"/>
174<path id="curveSegment2" d="M200,200 C250,200 300,150 200,100" fill="none"/>
175<path id="curveSegment3" d="M200,200 C350,200 250,-150 170,300" fill="none"/>
176
177<! keyframe 6 />
178<text id="line1DDest1" fill-opacity="0" >
179Some lines occupy one-dimensional
180</text>
181<text id="line1DDest2" y="240" fill-opacity="0" >
182sectors.
183</text>
184<text id="sectorDescXY9" x="500" y="460" fill="rgb(192,92,31)" fill-opacity="0">
185X &gt; 0&nbsp;&nbsp;&nbsp;Y == 0</text>
186<text id="sectorDescXY10" x="500" y="460" fill="rgb(31,92,192)" fill-opacity="0">
187Y &gt; 0&nbsp;&nbsp;&nbsp;0 == X</text>
188<text id="sectorDescXY11" x="500" y="460" fill="rgb(127,63,127)" fill-opacity="0">
189X &lt; 0&nbsp;&nbsp;&nbsp;Y == X</text>
190<path id="horzSegment" d="M200,200 L341.4,200" fill="none" stroke="rgb(192,92,31)" stroke-width="2" stroke-opacity="0"/>
191<path id="vertSegment" d="M200,200 L200,341.4" fill="none" stroke="rgb(31,92,192)" stroke-width="2" stroke-opacity="0"/>
192<path id="diagSegment" d="M200,200 L100,100"   fill="none" stroke="rgb(127,63,127)" stroke-width="2" stroke-opacity="0"/>
193
194<! keyframe 7 />
195<text id="curve1dDesc1" fill-opacity="0" >
196Some curves initially occupy
197</text>
198<text id="curve1dDesc2" y="240" fill-opacity="0" >
199one-dimensional sectors, then diverge.
200</text>
201<path id="cubicSegment" fill="none" stroke="black" />
202<path id="cubicSegment1" d="M200,200 C200,200 200,200 200,200" fill="none" />
203<path id="cubicSegment2" d="M200,200 C250,200 300,200 300,100" fill="none"/>
204
205<text id="sectorNumberDesc" fill-opacity="0" >
206Each sector is assigned a number.
207</text>
208<text id="spanSectorDesc" fill-opacity="0" >
209Each span has a bit set for one or more sectors.
210</text>
211<text id="bitOverDesc" fill-opacity="0" >
212Span sets allow rough sorting without angle computation.
213</text>
214
215</svg>
216
217<! canvas support />
218<script>
219
220var keyFrameQueue = [];
221var animationsPending = [];
222var animationsActive = [];
223var displayList = [];
224var visibleFinished = [];
225
226var animationState = {};
227animationState.reset = function () {
228    this.start = null;
229    this.time = 0;
230    this.requestID = null;
231    this.paused = false;
232    this.displayEngine = 'Canvas';
233}
234
235circle.center = { x: 200, y: 200 }
236circle.radius = 100;
237
238function assert(condition) {
239    if (!condition) debugger;
240}
241
242function CanvasGrads(ctx) {
243    var grad1 = ctx.createRadialGradient(200, 200, 0, 200, 200, 300);
244    grad1.addColorStop(0, "rgba(0,0,255, 0.3)");
245    grad1.addColorStop(1, "rgba(0,0,255, 0)");
246    var grad2 = ctx.createRadialGradient(200, 200, 0, 200, 200, 300);
247    grad2.addColorStop(0, "rgba(0,255,0, 0.3)");
248    grad2.addColorStop(1, "rgba(0,255,0, 0)");
249    var grad3 = ctx.createRadialGradient(200, 200, 0, 200, 200, 300);
250    grad3.addColorStop(0, "rgba(255,0,0, 0.3)");
251    grad3.addColorStop(1, "rgba(255,0,0, 0)");
252    var grad4 = ctx.createRadialGradient(200, 200, 0, 200, 200, 300);
253    grad4.addColorStop(0, "rgba(192,63,192, 0.3)");
254    grad4.addColorStop(1, "rgba(192,63,192, 0)");
255    var grad5 = ctx.createRadialGradient(200, 200, 0, 200, 200, 300);
256    grad5.addColorStop(0, "rgba(127,127,0, 0.3)");
257    grad5.addColorStop(1, "rgba(127,127,0, 0)");
258    var grad6 = ctx.createRadialGradient(200, 200, 0, 200, 200, 300);
259    grad6.addColorStop(0, "rgba(127,0,127, 0.3)");
260    grad6.addColorStop(1, "rgba(127,0,127, 0)");
261    var grad7 = ctx.createRadialGradient(200, 200, 0, 200, 200, 300);
262    grad7.addColorStop(0, "rgba(0,127,127, 0.3)");
263    grad7.addColorStop(1, "rgba(0,127,127, 0)");
264    var grad8 = ctx.createRadialGradient(200, 200, 0, 200, 200, 300);
265    grad8.addColorStop(0, "rgba(63,192,63, 0.3)");
266    grad8.addColorStop(1, "rgba(63,192,63, 0)");
267    var data = {
268        grad1: grad1,
269        grad2: grad2,
270        grad3: grad3,
271        grad4: grad4,
272        grad5: grad5,
273        grad6: grad6,
274        grad7: grad7,
275        grad8: grad8,
276    };
277    return data;
278}
279
280function skip_sep(data) {
281    if (!data.length) {
282        return data;
283    }
284    while (data[0] == ' ' || data[0] == ',') {
285        data = data.substring(1);
286    }
287    return data;
288}
289
290function find_points(str, value, count, isRelative, relative) {
291    var numRegEx = /-?\d+\.?\d*(?:e-?\d+)?/g;
292    var match;
293    for (var index = 0; index < count; ++index) {
294        str = skip_sep(str);
295        match = numRegEx.exec(str);
296        assert(match);
297        var x = Number(match[0]);
298        str = skip_sep(str);
299        match = numRegEx.exec(str);
300        assert(match);
301        var y = Number(match[0]);
302        value[index] = { x: x, y : y };
303    }
304    if (isRelative) {
305        for (var index = 0; index < count; index++) {
306            value[index].x += relative.x;
307            value[index].y += relative.y;
308        }
309    }
310    return str.substring(match.index + match[0].length);
311}
312
313function find_scalar(str, obj, isRelative, relative) {
314    var numRegEx = /-?\d+\.?\d*(?:e-?\d+)?/g;
315    str = skip_sep(str);
316    var match = numRegEx.exec(str);
317    obj.value = Number(match[0]);
318    if (isRelative) {
319        obj.value += relative;
320    }
321    return str.substring(match.index + match[0].length);
322}
323
324function parse_path(data) {
325    var path = "ctx.beginPath();\n";
326    var f = {x:0, y:0};
327    var c = {x:0, y:0};
328    var lastc = {x:0, y:0};
329    var points = [];
330    var op = '\0';
331    var previousOp = '\0';
332    var relative = false;
333    for (;;) {
334        data = skip_sep(data);
335        if (!data.length) {
336            break;
337        }
338        var ch = data[0];
339        if (('0' <= ch && ch <= '9') || ch == '-' || ch == '+') {
340            assert(op != '\0');
341        } else if (ch == ' ' || ch == ',') {
342            data = skip_sep(data);
343        } else {
344            op = ch;
345            relative = false;
346            if ('a' <= op && op <= 'z') {
347                op = op.toUpperCase();
348                relative = true;
349            }
350            data = data.substring(1);
351            data = skip_sep(data);
352        }
353        switch (op) {
354            case 'A':
355                var radii = [];
356                data = find_points(data, radii, 1, false, null);
357                var xaxisObj = {};
358                data = find_scalar(data, xaxisObj, false, null);
359                var largeArcObj = {};
360                data = find_scalar(data, largeArcObj, false, null);
361                var sweepObj = {};
362                data = find_scalar(data, sweepObj, false, null);
363                data = find_points(data, points, 1, relative, c);
364                var mid = { x: (c.x + points[0].x) / 2, y: (c.y + points[0].y) / 2 };
365                var midVec = { x: mid.x - c.x, y: mid.y - c.y };
366                var midLenSqr = midVec.x * midVec.x + midVec.y * midVec.y;
367                var radius = radii[0].x;
368                var scale = Math.sqrt(midLenSqr) / Math.sqrt(radius * radius - midLenSqr);
369                var tangentPt = { x: mid.x + midVec.y * scale,
370                                  y: mid.y - midVec.x * scale };
371                path += "ctx.arcTo(" + tangentPt.x + "," + tangentPt.y + ","
372                    + points[0].x + "," + points[0].y + "," + radius + ");\n";
373                c = points[0];
374                break;
375            case 'M':
376                data = find_points(data, points, 1, relative, c);
377                path += "ctx.moveTo(" + points[0].x + "," + points[0].y + ");\n";
378                op = 'L';
379                c = points[0];
380                break;
381            case 'L':
382                data = find_points(data, points, 1, relative, c);
383                path += "ctx.lineTo(" + points[0].x + "," + points[0].y + ");\n";
384                c = points[0];
385                break;
386            case 'H': {
387                var xObj = {};
388                data = find_scalar(data, xObj, relative, c.x);
389                path += "ctx.lineTo(" + xObj.value + "," + c.y + ");\n";
390                c.x = xObj.value;
391            } break;
392            case 'V': {
393                var yObj = {};
394                data = find_scalar(data, y, relative, c.y);
395                path += "ctx.lineTo(" + c.x + "," + yObj.value+ ");\n";
396                c.y = yObj.value;
397            } break;
398            case 'C':
399                data = find_points(data, points, 3, relative, c);
400                path += "ctx.bezierCurveTo(" + points[0].x + "," + points[0].y + ","
401                    + points[1].x + "," + points[1].y + ","
402                    + points[2].x + "," + points[2].y + ");\n";
403                lastc = points[1];
404                c = points[2];
405                break;
406            case 'S':
407                var pts2_3 = [];
408                data = find_points(data, pts2_3, 2, relative, c);
409                points[0] = c;
410                points[1] = pts2_3[0];
411                points[2] = pts2_3[1];
412                if (previousOp == 'C' || previousOp == 'S') {
413                    points[0].x -= lastc.x - c.x;
414                    points[0].y -= lastc.y - c.y;
415                }
416                path += "ctx.bezierCurveTo(" + points[0].x + "," + points[0].y + ","
417                    + points[1].x + "," + points[1].y + ","
418                    + points[2].x + "," + points[2].y + ");\n";
419                lastc = points[1];
420                c = points[2];
421                break;
422            case 'Q':  // Quadratic Bezier Curve
423                data = find_points(data, points, 2, relative, c);
424                path += "ctx.quadraticCurveTo(" + points[0].x + "," + points[0].y + ","
425                    + points[1].x + "," + points[1].y + ");\n";
426                lastc = points[0];
427                c = points[1];
428                break;
429            case 'T':
430                var pts2 = [];
431                data = find_points(data, pts2, 1, relative, c);
432                points[0] = pts2[0];
433                points[1] = pts2[0];
434                if (previousOp == 'Q' || previousOp == 'T') {
435                    points[0].x = c.x * 2 - lastc.x;
436                    points[0].y = c.y * 2 - lastc.y;
437                }
438                path += "ctx.quadraticCurveTo(" + points[0].x + "," + points[0].y + ","
439                    + points[1].x + "," + points[1].y + ");\n";
440                path.quadTo(points[0], points[1]);
441                lastc = points[0];
442                c = points[1];
443                break;
444            case 'Z':
445                path += "ctx.closePath();\n";
446                c = f;
447                op = '\0';
448                break;
449            case '~':
450                var args = [];
451                data = find_points(data, args, 2, false, null);
452                path += "moveTo(" + args[0].x + "," + args[0].y + ");\n";
453                path += "lineTo(" + args[1].x + "," + args[1].y + ");\n";
454                break;
455            default:
456                return false;
457        }
458        if (previousOp == 0) {
459            f = c;
460        }
461        previousOp = op;
462    }
463    return path;
464}
465
466function CanvasPaths(ctx) {
467    var svgStrs = {
468    // keyframe 1
469        span1:   "M200,200 Q300,300 200,300",
470        span2:   "M200,200 C100,300 100,400 200,300",
471        span3:   "M200,200 C300,100 100,400 300,200",
472        wedge1:  "M200,200 L500,500 A 424.26,424.26 0,0,1 200,624.26 z",
473        wedge2:  "M200,200 L200,624.26 A 424.26,424.26 0,0,1 -100,500 z",
474        wedge3:  "M200,200 L500,-100 A 424.26,424.26 0,0,1 240,622.5 z",
475    // keyframe 2
476        span4:   "M200,200 Q300,300 400,300",
477        span5:   "M200,200 Q280,320 200,400",
478        span6:   "M200,200 Q60,340 100,400",
479        wedge4:  "M200,200 L500,500 A 424.26,424.26 0,0,1 579.47,389.74 z",
480        wedge5:  "M200,200 L389.74,579.47 A 424.26,424.26 0,0,1 200,500 z",
481        wedge6:  "M200,200 L10.26,579.47 A 424.26,424.26 0,0,1 -100,500 z",
482    // keyframe 3
483        xaxis:    "M100,200 L300,200",
484        yaxis:    "M200,100 L200,300",
485        wedgeXY8: "M200,200 L500,500 A 424.26,424.26 0,0,1 624.26,200 z",
486        wedgeXY6: "M200,200 L-100,500 A 424.26,424.26 0,0,1 200,624.26 z",
487        wedgeXY3: "M200,200 L-100,-100 A 424.26,424.26 0,0,1 200,-175.74 z",
488    // keyframe 4
489        wedgeXY1: "M200,200 L500,-100 A 424.26,424.26 0,0,1 624.26,200 z",
490        wedgeXY2: "M200,200 L200,-175.74 A 424.26,424.26 0,0,1 500,-100 z",
491        wedgeXY4: "M200,200 L-175.74,200 A 424.26,424.26 0,0,1 -100,-100 z",
492        wedgeXY5: "M200,200 L-100,500 A 424.26,424.26 0,0,1 -175.74,200 z",
493        wedgeXY7: "M200,200 L200,624.26 A 424.26,424.26 0,0,1 500,500 z",
494        lineSegment: "M200,200 L200,624.26",
495    // keyframe 5
496        curveSegment:  "M200,200 C250,200 300,150 300,100",
497        curveSegment1: "M200,200 C250,200 300,150 300,100",
498        curveSegment2: "M200,200 C250,200 300,150 200,100",
499        curveSegment3: "M200,200 C350,200 250,-150 170,300",
500    // keyframe 6
501        horzSegment: "M200,200 L341.4,200",
502        vertSegment: "M200,200 L200,341.4",
503        diagSegment: "M200,200 L100,100",
504    // keyframe 7
505        cubicSegment:  "M200,200 C200,200 200,200 200,200",
506        cubicSegment1: "M200,200 C200,200 200,200 200,200",
507        cubicSegment2: "M200,200 C250,200 300,200 300,100",
508    };
509    var paths = [];
510    var keys = Object.keys(svgStrs);
511    for (var index in keys) {
512        var key = keys[index];
513        var str = svgStrs[key];
514        var path = parse_path(str);
515        var record = [];
516        paths[key] = {
517            str: str,
518            funcBody: path,
519        };
520    }
521    return paths;
522}
523
524function canvas_fill_font(record) {
525    assert(record);
526    var str = 'ctx.font = "normal 1.3rem Helvetica,Arial";\n';
527    if (record.fillStyle) {
528        str += 'ctx.fillStyle = ' + record.fillStyle + ';\n';
529    }
530    return str;
531}
532
533function canvas_fill_text(record) {
534    assert(record);
535    assert(typeof record.fillText == 'string');
536    return 'ctx.fillText("' + record.fillText + '"';
537}
538
539function canvas_xy(record) {
540    var x = typeof record.x == "number" ? record.x : 400;
541    var y = typeof record.y == "number" ? record.y : 200;
542    return ', ' + x + ', ' + y + ');\n';
543}
544
545function canvas_text_xy(record) {
546    return canvas_fill_text(record) + canvas_xy(record);
547}
548
549function add_canvas_stroke(paths, data, id, strokeStyle) {
550    var record = {};
551    record.data = paths[id].funcBody;
552    record.style = 'ctx.strokeStyle = ' + (strokeStyle ? strokeStyle : '"black"') + ';\n';
553    record.draw = 'ctx.stroke();\n';
554    record.func = new Function('ctx', record.data + record.style + record.draw);
555    return data[id] = record;
556}
557
558function add_canvas_style(record, style) {
559    record.style += style;
560    record.func = new Function('ctx', record.data + record.style + record.draw);
561}
562
563function add_canvas_fill(paths, data, id, fillStyle) {
564    var record = {};
565    record.data = paths[id].funcBody;
566    record.style = 'ctx.fillStyle = ' + (fillStyle ? fillStyle : '"black"') + ';\n';
567    record.draw = 'ctx.fill();\n';
568    record.func = new Function('ctx', record.data + record.style + record.draw);
569    return data[id] = record;
570}
571
572function add_canvas_text(data, id, params) {
573    var record = {};
574    record.style = canvas_fill_font(params);
575    record.draw = canvas_fill_text(params);
576    record.position = canvas_xy(params);
577    record.x = params.x;
578    record.y = params.y;
579    record.func = new Function('ctx', record.style + record.draw + record.position);
580    return data[id] = record;
581}
582
583function keyframe1(grads, paths) {
584    var data = [];
585    add_canvas_text(data, "spanWedgeDesc", { fillText:"All spans are contained by a wedge" } );
586    add_canvas_stroke(paths, data, "span1");
587    add_canvas_stroke(paths, data, "span2");
588    add_canvas_stroke(paths, data, "span3");
589    add_canvas_fill(paths, data, "wedge1", "grads.grad1");
590    add_canvas_fill(paths, data, "wedge2", "grads.grad2");
591    add_canvas_fill(paths, data, "wedge3", "grads.grad3");
592    return data;
593}
594
595function keyframe2(grads, paths) {
596    var data = [];
597    add_canvas_text(data, "trivialWedgeDesc1", { fillText:"Wedges that don't overlap can be" } );
598    add_canvas_text(data, "trivialWedgeDesc2", { fillText:"easily sorted.", y:240 } );
599    add_canvas_stroke(paths, data, "span4").debug = true;
600    add_canvas_stroke(paths, data, "span5");
601    add_canvas_stroke(paths, data, "span6");
602    add_canvas_fill(paths, data, "wedge4", "grads.grad1");
603    add_canvas_fill(paths, data, "wedge5", "grads.grad2");
604    add_canvas_fill(paths, data, "wedge6", "grads.grad3");
605    return data;
606}
607
608function setup_axes(paths, data) {
609    var color = '"rgb(191,191,191)"';
610    var xaxis = add_canvas_stroke(paths, data, "xaxis", color);
611    xaxis.funcBody = canvas_fill_font( { fillStyle:color } );
612    xaxis.funcBody += canvas_text_xy( { fillText:"-X", x:100, y:220 } );
613    xaxis.funcBody += "ctx.textAlign = 'right';\n";
614    xaxis.funcBody += canvas_text_xy( { fillText:"+X", x:300, y:220 } );
615    xaxis.func = new Function('ctx', xaxis.data + xaxis.style + xaxis.draw + xaxis.funcBody);
616    var yaxis = add_canvas_stroke(paths, data, "yaxis", color);
617    yaxis.funcBody = canvas_fill_font( { fillStyle:color } );
618    yaxis.funcBody += "ctx.textBaseline = 'hanging';\n";
619    yaxis.funcBody += canvas_text_xy( { fillText:"-Y", x:205, y:100 } );
620    yaxis.funcBody += "ctx.textBaseline = 'alphabetic';\n";
621    yaxis.funcBody += canvas_text_xy( { fillText:"+Y", x:205, y:300 } );
622    yaxis.func = new Function('ctx', yaxis.data + yaxis.style + yaxis.draw + yaxis.funcBody);
623}
624
625function keyframe3(grads, paths) {
626    var data = [];
627    add_canvas_text(data, "sectorDesc1", { fillText:"A sector is a wedge of a circle" } );
628    add_canvas_text(data, "sectorDesc2", { fillText:"containing a range of points.", y:240 } );
629    setup_axes(paths, data);
630    add_canvas_text(data, "sectorDescXYA",
631        { fillText:"X > 0   Y > 0    Y < X", x:500, y:310, fillStyle:'"rgb(0,0,255)"'} );
632    add_canvas_text(data, "sectorDescXYB",
633        { fillText:"X < 0   Y > 0   -Y < X", x:500, y:360, fillStyle:'"rgb(0,127,0)"'} );
634    add_canvas_text(data, "sectorDescXYC",
635        { fillText:"X < 0   Y < 0    Y < X", x:500, y:410, fillStyle:'"rgb(255,0,0)"'} );
636    add_canvas_fill(paths, data, "wedgeXY8", "grads.grad1");
637    add_canvas_fill(paths, data, "wedgeXY6", "grads.grad2");
638    add_canvas_fill(paths, data, "wedgeXY3", "grads.grad3");
639    return data;
640}
641
642function keyframe4(grads, paths) {
643    var data = [];
644    setup_axes(paths, data);
645    add_canvas_text(data, "lineSingleDesc",
646        { fillText:"Line spans are contained by a single sector." } );
647    add_canvas_text(data, "sectorDescXY1",
648        { fillText:"X > 0   Y < 0   -Y < X", x:500, y:460, fillStyle:'"rgb(192,63,192)"'} );
649    add_canvas_text(data, "sectorDescXY2",
650        { fillText:"X > 0   Y < 0   -Y > X", x:500, y:460, fillStyle:'"rgb(127,127,0)"'} );
651    add_canvas_text(data, "sectorDescXY3",
652        { fillText:"X < 0   Y < 0    Y < X", x:500, y:460, fillStyle:'"rgb(255,0,0)"'} );
653    add_canvas_text(data, "sectorDescXY4",
654        { fillText:"X < 0   Y < 0    Y > X", x:500, y:460, fillStyle:'"rgb(127,0,127)"'} );
655    add_canvas_text(data, "sectorDescXY5",
656        { fillText:"X < 0   Y > 0   -Y < X", x:500, y:460, fillStyle:'"rgb(0,127,127)"'} );
657    add_canvas_text(data, "sectorDescXY6",
658        { fillText:"X < 0   Y > 0   -Y < X", x:500, y:460, fillStyle:'"rgb(0,127,0)"'} );
659    add_canvas_text(data, "sectorDescXY7",
660        { fillText:"X > 0   Y > 0    Y > X", x:500, y:460, fillStyle:'"rgb(63,192,63)"'} );
661    add_canvas_text(data, "sectorDescXY8",
662        { fillText:"X > 0   Y > 0    Y < X", x:500, y:460, fillStyle:'"rgb(0,0,255)"'} );
663    add_canvas_fill(paths, data, "wedgeXY1", "grads.grad4");
664    add_canvas_fill(paths, data, "wedgeXY2", "grads.grad5");
665    add_canvas_fill(paths, data, "wedgeXY3", "grads.grad3");
666    add_canvas_fill(paths, data, "wedgeXY4", "grads.grad6");
667    add_canvas_fill(paths, data, "wedgeXY5", "grads.grad7");
668    add_canvas_fill(paths, data, "wedgeXY6", "grads.grad2");
669    add_canvas_fill(paths, data, "wedgeXY7", "grads.grad8");
670    add_canvas_fill(paths, data, "wedgeXY8", "grads.grad1");
671    add_canvas_stroke(paths, data, "lineSegment");
672    return data;
673}
674
675function keyframe5(grads, paths) {
676    var data = [];
677    setup_axes(paths, data);
678    add_canvas_text(data, "curveMultipleDesc1",
679        { fillText:"A curve span may cover more" } );
680    add_canvas_text(data, "curveMultipleDesc2",
681        { fillText:"than one sector.", y:240 } );
682    add_canvas_stroke(paths, data, "curveSegment");
683    add_canvas_text(data, "sectorDescXY1",
684        { fillText:"X > 0   Y < 0   -Y < X", x:500, y:460, fillStyle:'"rgb(192,63,192)"'} );
685    add_canvas_text(data, "sectorDescXY2",
686        { fillText:"X > 0   Y < 0   -Y > X", x:500, y:460, fillStyle:'"rgb(127,127,0)"'} );
687    add_canvas_text(data, "sectorDescXY3",
688        { fillText:"X < 0   Y < 0    Y < X", x:500, y:460, fillStyle:'"rgb(255,0,0)"'} );
689    add_canvas_text(data, "sectorDescXY4",
690        { fillText:"X < 0   Y < 0    Y > X", x:500, y:460, fillStyle:'"rgb(127,0,127)"'} );
691    add_canvas_text(data, "sectorDescXY5",
692        { fillText:"X < 0   Y > 0   -Y < X", x:500, y:460, fillStyle:'"rgb(0,127,127)"'} );
693    add_canvas_text(data, "sectorDescXY6",
694        { fillText:"X < 0   Y > 0   -Y < X", x:500, y:460, fillStyle:'"rgb(0,127,0)"'} );
695    add_canvas_fill(paths, data, "wedgeXY1", "grads.grad4");
696    add_canvas_fill(paths, data, "wedgeXY2", "grads.grad5");
697    add_canvas_fill(paths, data, "wedgeXY3", "grads.grad3");
698    add_canvas_fill(paths, data, "wedgeXY4", "grads.grad6");
699    add_canvas_fill(paths, data, "wedgeXY5", "grads.grad7");
700    add_canvas_fill(paths, data, "wedgeXY6", "grads.grad2");
701    return data;
702}
703
704function keyframe6(grads, paths) {
705    var data = [];
706    setup_axes(paths, data);
707
708    add_canvas_text(data, "line1DDest1",
709        { fillText:"Some lines occupy one-dimensional" } );
710    add_canvas_text(data, "line1DDest2",
711        { fillText:"sectors.", y:240 } );
712    add_canvas_text(data, "sectorDescXY9",
713        { fillText:"X > 0   Y == 0", x:500, y:460, fillStyle:'"rgb(192,92,31)"' } );
714    add_canvas_text(data, "sectorDescXY10",
715        { fillText:"Y > 0   0 == X", x:500, y:460, fillStyle:'"rgb(31,92,192)"' } );
716    add_canvas_text(data, "sectorDescXY11",
717        { fillText:"X < 0   Y == X", x:500, y:460, fillStyle:'"rgb(127,63,127)"' } );
718    var horz = add_canvas_stroke(paths, data, "horzSegment", '"rgb(192,92,31)"');
719    add_canvas_style(horz, "ctx.lineWidth = " + 2 * animationState.resScale + ";\n");
720    var vert = add_canvas_stroke(paths, data, "vertSegment", '"rgb(31,92,192)"');
721    add_canvas_style(vert, "ctx.lineWidth = " + 2 * animationState.resScale + ";\n");
722    var diag = add_canvas_stroke(paths, data, "diagSegment", '"rgb(127,63,127)"');
723    add_canvas_style(diag, "ctx.lineWidth = " + 2 * animationState.resScale + ";\n");
724    return data;
725}
726
727function keyframe7(grads, paths) {
728    var data = [];
729    setup_axes(paths, data);
730    add_canvas_text(data, "curve1dDesc1",
731        { fillText:"Some curves initially occupy" } );
732    add_canvas_text(data, "curve1dDesc2",
733        { fillText:"one-dimensional sectors, then diverge.", y:240 } );
734    add_canvas_stroke(paths, data, "cubicSegment");
735    add_canvas_text(data, "sectorDescXY1",
736        { fillText:"X > 0   Y < 0   -Y < X", x:500, y:460, fillStyle:'"rgb(192,63,192)"'} );
737    add_canvas_text(data, "sectorDescXY9",
738        { fillText:"X > 0   Y == 0", x:500, y:460, fillStyle:'"rgb(192,92,31)"' } );
739    var horz = add_canvas_stroke(paths, data, "horzSegment", '"rgb(192,92,31)"');
740    add_canvas_style(horz, "ctx.lineWidth = " + 2 * animationState.resScale + ";\n");
741    add_canvas_fill(paths, data, "wedgeXY1", "grads.grad4");
742    return data;
743}
744
745var canvasData = null;
746
747function CanvasInit(keyframe) {
748    canvasData = window[keyframe](grads, paths);
749}
750
751</script>
752
753<script>
754
755function interp(A, B, t) {
756    return A + (B - A) * t;
757}
758
759function interp_cubic_coords(x1, x2, x3, x4, t)
760{
761    var ab = interp(x1, x2, t);
762    var bc = interp(x2, x3, t);
763    var cd = interp(x3, x4, t);
764    var abc = interp(ab, bc, t);
765    var bcd = interp(bc, cd, t);
766    var abcd = interp(abc, bcd, t);
767    return abcd;
768}
769
770function cubic_partial(value, p) {
771    var x1 = p[0], y1 = p[1], x2 = p[2], y2 = p[3];
772    var x3 = p[4], y3 = p[5], x4 = p[6], y4 = p[7];
773    var t1 = 0, t2 = value;
774    var ax = interp_cubic_coords(x1, x2, x3, x4, t1);
775    var ay = interp_cubic_coords(y1, y2, y3, y4, t1);
776    var ex = interp_cubic_coords(x1, x2, x3, x4, (t1*2+t2)/3);
777    var ey = interp_cubic_coords(y1, y2, y3, y4, (t1*2+t2)/3);
778    var fx = interp_cubic_coords(x1, x2, x3, x4, (t1+t2*2)/3);
779    var fy = interp_cubic_coords(y1, y2, y3, y4, (t1+t2*2)/3);
780    var dx = interp_cubic_coords(x1, x2, x3, x4, t2);
781    var dy = interp_cubic_coords(y1, y2, y3, y4, t2);
782    var mx = ex * 27 - ax * 8 - dx;
783    var my = ey * 27 - ay * 8 - dy;
784    var nx = fx * 27 - ax - dx * 8;
785    var ny = fy * 27 - ay - dy * 8;
786    var bx = (mx * 2 - nx) / 18;
787    var by = (my * 2 - ny) / 18;
788    var cx = (nx * 2 - mx) / 18;
789    var cy = (ny * 2 - my) / 18;
790    var array = [
791        ax, ay, bx, by, cx, cy, dx, dy
792    ];
793    return array;
794}
795
796function evaluate_at(value, p) {
797    var array = [];
798    for (var index = 0; index < p.length; ++index) {
799        var func = new Function('value', 'return ' + p[index] + ';');
800        array[index] = func(value);
801    }
802    return array;
803}
804
805function interpolate_at(value, p) {
806    var array = [];
807    var start = p[0];
808    var end = p[1];
809    assert(typeof end == typeof start);
810    switch (typeof start) {
811        case 'object':
812            for (var index = 0; index < start.length; ++index) {
813                array[index] = interp(start[index], end[index], value);
814            }
815            break;
816        case 'number':
817            array[index] = interp(start, end, value);
818            break;
819        default:
820            debugger;
821    }
822    return array;
823}
824
825function AnimationAddCommon(timing, range, attr, inParams) {
826    var animation = {
827        timing: timing,
828        range: range,
829        attr: attr,
830        inParams: inParams,
831        duration: timing[1] - timing[0],
832        remaining: timing[1] - timing[0],
833        firstStep: true,
834    }
835    animationsPending.push(animation);
836    return animation;
837}
838
839function AnimationAddSVG(timing, element, range, attr, inParams) {
840    var animation = AnimationAddCommon(timing, range, attr, inParams);
841    animation.element = element;
842    return animation;
843}
844
845function AnimationAddCanvas(timing, element, range, attr, inParams) {
846    var animation = AnimationAddCommon(timing, range, attr, inParams);
847    animation.element = canvasData[element];
848    assert(animation.element);
849    animation.firstElement = null;
850    return animation;
851}
852
853function AnimationAdd(timing, e, range, attr, funct, inParams) {
854    if (!range) {
855        range = [0, 1];
856    }
857    if (!attr) {
858        attr = 'opacity';
859    }
860    if (!funct) {
861        funct = interpolate_at;
862    }
863    var element;
864    switch (animationState.displayEngine) {
865        case 'SVG':
866            element = typeof e == 'string' ? document.getElementById(e) : e;
867            break;
868        case 'Canvas':
869            element = typeof e == 'string' ? e : e.id;
870            break;
871        default:
872            debugger;
873    }
874    assert(element);
875    switch (attr) {
876        case 'path':
877            if (!inParams) {
878                inParams = PathDataArray(element);
879            }
880            break;
881        case 'opacity':
882            if (!inParams) {
883                inParams = [0, 1];
884            }
885            break;
886        default:
887            debugger;
888    }
889    var funcBody = 'var outParams = '  + funct.name + '(value, inParams);\n';
890    switch (animationState.displayEngine) {
891        case 'SVG':
892            switch (attr) {
893                case 'path':
894                    var verbArray = PathVerbArray(element);
895                    funcBody += 'return ';
896                    for (var index = 0; index < inParams.length; ++index) {
897                        funcBody += '"' + verbArray[index] + '"';
898                        funcBody += 'outParams[' + index + '];\n';
899                    }
900                    if (verbArray.length > inParams.length) {
901                        funcBody += '"' + verbArray[verbArray.length - 1] + '"';
902                    }
903                    funcBody += ';\n';
904                    var animation = AnimationAddSVG(timing, element, range, "d", inParams);
905                    animation.func = new Function('value', 'inParams', funcBody);
906                    break;
907               case 'opacity':
908                    if (animation.element.getAttribute("stroke-opacity")) {
909                        animation = AnimationAddSVG(timing, element, range, "stroke-opacity", inParams);
910                    }
911                    if (animation.element.getAttribute("fill-opacity")) {
912                        animation = AnimationAddSVG(timing, element, range, "fill-opacity", inParams);
913                    }
914                    break;
915                default:
916                    debugger;
917            }
918        case 'Canvas':
919            switch (attr) {
920                case 'path':
921                    var verbArray = PathVerbArray(element);
922                    for (var index = 0; index < inParams.length; ++index) {
923                        funcBody += verbArray[index];
924                        funcBody += 'outParams[' + index + ']';
925                    }
926                    if (verbArray.length > inParams.length) {
927                        funcBody += verbArray[verbArray.length - 1];
928                    }
929                    animation = AnimationAddCanvas(timing, element, range, attr, inParams);
930                    funcBody += animation.element.style + animation.element.draw;
931                    animation.func = new Function('ctx', 'value', 'inParams', funcBody);
932                    break;
933               case 'opacity':
934                    animation = AnimationAddCanvas(timing, element, range, attr, inParams);
935                    break;
936                default:
937                    debugger;
938            }
939            break;
940        default:
941            debugger;
942    }
943    return animation;
944}
945
946function path_data_common(element, getValues) {
947    var numRegEx = /-?\d+\.?\d*(?:e-?\d+)?/g;
948    var data = [];
949    var match;
950    var path;
951    switch (animationState.displayEngine) {
952        case 'SVG': path = element.getAttribute("d"); break;
953        case 'Canvas': path = paths[element].funcBody; break;
954        default: debugger;
955    }
956    if (getValues) {
957        while ((match = numRegEx.exec(path))) {
958            data.push(Number(match[0]));
959        }
960    } else {
961        var sIndex = 0;
962        while ((match = numRegEx.exec(path))) {
963            if (sIndex < match.index) {
964                data.push(path.substring(sIndex, match.index));
965            }
966            sIndex = match.index + match[0].length;
967        }
968        if (sIndex < path.length) {
969            data.push(path.substring(sIndex, path.length));
970        }
971    }
972    return data;
973}
974
975function PathDataArray(element) {
976    return path_data_common(element, true);
977}
978
979function PathVerbArray(element) {
980    return path_data_common(element, false);
981}
982
983function PathSet(element, funct, value, params) {
984    var pathVerbs = PathVerbArray(element);
985    if (funct) {
986        params = funct(value, params);
987    }
988   var setValue = '';
989    for (var index = 0; index < params.length; ++index) {
990        setValue += pathVerbs[index];
991        setValue += params[index];
992    }
993    if (pathVerbs.length > params.length) {
994        setValue += pathVerbs[pathVerbs.length - 1];
995    }
996    switch (animationState.displayEngine) {
997        case 'SVG':
998            element.setAttribute('d', setValue);
999            break;
1000        case 'Canvas':
1001            element.func = new Function('ctx', setValue + element.style + element.draw);
1002            break;
1003        default:
1004            debugger;
1005    }
1006}
1007
1008function RemoveFromArray(array, element) {
1009    for (var index in array) {
1010        var record = array[index];
1011        if (record.element == element) {
1012            array.splice(index, 1);
1013            break;
1014        }
1015    }
1016}
1017
1018function EndAnimationCanvas(animation, visibleFinished) {
1019    var changeAlpha = "opacity" == animation.attr;
1020    if (!changeAlpha || animation.range[1] > 0) {
1021        if (changeAlpha) {
1022            ctx.save();
1023            ctx.globalAlpha = animation.range[1];
1024        }
1025        if (animation.func) {
1026            animation.func(ctx, animation.range[animation.range.length - 1], animation.inParams);
1027        } else {
1028            animation.element.func(ctx);
1029        }
1030        if (changeAlpha) {
1031            ctx.restore();
1032        }
1033//        if (visibleFinished) {
1034//            visibleFinished.push(animation);
1035//        }
1036    } else {
1037 //       if (visibleFinished) {
1038 //           RemoveFromArray(visibleFinished, animation.element);
1039 //       }
1040    }
1041}
1042
1043/* start here
1044canvas:
1045
1046display list :
1047    for each element (canvas)
1048        save
1049        set global alpha (override)
1050        create geometry (override)
1051        create style (override)
1052        draw
1053        restore
1054
1055maybe each action should have an override slot
1056animations write to the slot
1057each element in display list then iterates overrides once the animations complete the frame
1058
1059so, first --
1060    active animations update the display list
1061
1062next --
1063    active animations install themselves in override slots
1064
1065finally
1066    display list is iterated, calling override slots
1067
1068----------------
1069
1070svg:
1071    display list is implicit
1072
1073    active animations write element attributes
1074 */
1075
1076function EndAnimationSVG(animation, visibleFinished) {
1077    switch (animation.attr) {
1078        case "opacity":
1079            animation.element.setAttribute(animation.attribute, animation.range[1]);
1080            if (animation.range[1] > 0) {
1081                visibleFinished.push(animation);
1082            } else {
1083                RemoveFromArray(visibleFinished, animation.element);
1084            }
1085            break;
1086        case "path":
1087            var attrStr = animation.func(animation.range[1], animation.inParams);
1088            animation.element.setAttribute(animation.attribute, attrStr);
1089            break;
1090        default:
1091            debugger;
1092    }
1093}
1094
1095function StepAnimationCanvas(animation, value) {
1096    var endValue = animation.range[animation.range.length - 1];
1097    var interp = animation.range[0] + (endValue - animation.range[0]) * (1 - value);
1098    if (animation.firstStep) {
1099        RemoveFromArray(visibleFinished, animation.element);
1100        animation.firstStep = false;
1101    }
1102    var changeAlpha = "opacity" == animation.attr;
1103    if (changeAlpha) {
1104        ctx.save();
1105        ctx.globalAlpha = interp;
1106    }
1107    if (animation.func) {
1108        animation.func(ctx, interp, animation.inParams);
1109    } else {
1110        animation.element.func(ctx);
1111    }
1112    if (changeAlpha) {
1113        ctx.restore();
1114    }
1115}
1116
1117function StepAnimationSVG(animation, value) {
1118    var interp = animation.range[0] + (animation.range[1] - animation.range[0]) * (1 - value);
1119    switch (animation.attr) {
1120        case "opacity":
1121            animation.element.setAttribute(animation.attribute, interp);
1122            break;
1123        case "path":
1124            var attrStr = animation.func(interp, animation.inParams);
1125            animation.element.setAttribute(animation.attribute, attrStr);
1126            break;
1127        default:
1128            debugger;
1129    }
1130}
1131
1132var animate_frame = 0;
1133
1134function AnimateList(now) {
1135    ++animate_frame;
1136    if (animationState.paused) {
1137        return;
1138    }
1139    if (animationState.start == null) {
1140        animationState.start = now - animationState.time;
1141    }
1142    animationState.time = now - animationState.start;
1143    var stillPending = [];
1144    for (var index in animationsPending) {
1145        var animation = animationsPending[index];
1146        var interval = animationState.time - animation.timing[0];
1147        if (interval <= 0) {
1148            stillPending.push(animation);
1149            continue;
1150        }
1151        animationsActive.push(animation);
1152        var inList = false;
1153        for (var dlIndex in displayList) {
1154            var displayable = displayList[dlIndex];
1155            if (displayable == animation.element) {
1156                inList = true;
1157                break;
1158            }
1159        }
1160        if (!inList) {
1161            displayList.push(animation.element);
1162        }
1163    }
1164    animationsPending = stillPending;
1165    var stillAnimating = [];
1166    if ('Canvas' == animationState.displayEngine) {
1167        ctx.clearRect(0, 0, canvas.width, canvas.height);
1168//        for (var index in visibleFinished) {
1169//           var animation = visibleFinished[index];
1170//           animation.endAnimation = false;
1171//        }
1172    }
1173    for (var index in animationsActive) {
1174        var animation = animationsActive[index];
1175        var interval = animationState.time - animation.timing[0];
1176        animation.remaining = animation.duration > interval ? animation.duration - interval : 0;
1177        animation.endAnimation = animation.remaining <= 0;
1178        if (animation.endAnimation) {
1179            switch (animationState.displayEngine) {
1180                case 'SVG':  EndAnimationSVG(animation, visibleFinished); break;
1181                case 'Canvas': EndAnimationCanvas(animation, visibleFinished); break;
1182                default: debugger;
1183            }
1184            continue;
1185        }
1186        var value = animation.remaining / animation.duration;
1187        switch (animationState.displayEngine) {
1188            case 'SVG': StepAnimationSVG(animation, value); break;
1189            case 'Canvas':
1190                if (!animation.firstElement || !animation.firstElement.endAnimation) {
1191                    StepAnimationCanvas(animation, value);
1192                }
1193            break;
1194            default: debugger;
1195        }
1196        stillAnimating.push(animation);
1197    }
1198    if ('Canvas' == animationState.displayEngine) {
1199        for (var index in visibleFinished) {
1200            var animation = visibleFinished[index];
1201            if (!animation.endAnimation) {
1202                EndAnimationCanvas(animation, null);
1203            }
1204        }
1205    }
1206    animationsActive = stillAnimating;
1207    if (animationsPending.length || animationsActive.length) {
1208        animationState.requestID = requestAnimationFrame(AnimateList);
1209    }
1210}
1211
1212function CancelAnimate(now) {
1213    if (animationState.start == null) {
1214        animationState.start = now;
1215    }
1216    var time = now - animationState.start;
1217    var stillAnimating = [];
1218    for (var index in animationsActive) {
1219        var animation = animationsActive[index];
1220        var remaining = animation.remaining - time;
1221        var value = remaining / animation.duration;
1222        switch (animationState.displayEngine) {
1223            case 'SVG': animation.element.setAttribute(animation.attribute, value); break;
1224            case 'Canvas': break;
1225        }
1226        if (remaining <= 0) {
1227            continue;
1228        }
1229        stillAnimating.push(animation);
1230    }
1231    animationsActive = stillAnimating;
1232    if (animationsActive.length) {
1233        animationState.requestID = requestAnimationFrame(CancelAnimate);
1234        return;
1235    }
1236    animationsPending = [];
1237    animationState.reset();
1238    if (keyFrameQueue.length > 0) {
1239        var animationFunc = keyFrameQueue.pop();
1240        animationFunc();
1241    }
1242}
1243
1244function CancelAnimation() {
1245    cancelAnimationFrame(animationState.requestID);
1246    for (var index in animationsActive) {
1247        var animation = animationsActive[index];
1248        switch (animation.attr) {
1249            case "opacity":
1250                var tmp = animation.range[0]; animation.range[0] = animation.range[1]; animation[1] = tmp;
1251                animation.remaining = animation.duration - animation.remaining;
1252                animation.remaining /= animation.duration / 1000;
1253                animation.duration = 1000;
1254                break;
1255            case "fadeOut":
1256                RemoveFromArray(visibleFinished, animation.element);
1257                break;
1258            case "path":
1259                break;
1260            default:
1261                debugger;
1262
1263        }
1264    }
1265    for (var index in visibleFinished) {
1266        var animation = visibleFinished[index];
1267        animation.action = "fadeOut";
1268        animation.remaining = animation.duration = 1000;
1269        animationsActive.push(animation);
1270    }
1271    visibleFinished = [];
1272    animationState.reset();
1273    animationState.requestID = requestAnimationFrame(CancelAnimate);
1274}
1275
1276function PauseAnimation() {
1277    animationState.paused = true;
1278}
1279
1280function QueueAnimation(animationFunc) {
1281    if (null == animationState.requestID) {
1282        animationFunc();
1283        return;
1284    }
1285    keyFrameQueue.push(animationFunc);
1286}
1287
1288function UnpauseAnimation() {
1289    animationState.paused = false;
1290    animationState.start = performance.now() - animationState.time;
1291    animationState.requestID = requestAnimationFrame(AnimateList);
1292}
1293
1294function SetupTextSVG(t, x, y) {
1295    var text;
1296    if (typeof t == "string") {
1297        text = document.getElementById(t);
1298    } else {
1299        text = t;
1300    }
1301    text.setAttribute("font-family", "Helvetica,Arial");
1302    text.setAttribute("font-size", "1.3rem");
1303    if (typeof x == 'number') {
1304        text.setAttribute("x", x);
1305    } else if (null == text.getAttribute("x")) {
1306        text.setAttribute("x", 400);
1307    }
1308    if (typeof y == 'number') {
1309        text.setAttribute("y", y);
1310    } else if (null == text.getAttribute("y")) {
1311        text.setAttribute("y", 200);
1312    }
1313}
1314
1315function SetupTextCanvas(t, x, y) {
1316    var text = typeof t == 'string' ? t : t.id;
1317    var record = canvasData[text];
1318    if (typeof x == 'number') {
1319        record.x = x;
1320    }
1321    if (typeof y == 'number') {
1322        record.y = y;
1323    }
1324    record.position = canvas_xy(record);
1325    record.func = new Function('ctx', record.style + record.draw + record.position);
1326}
1327
1328function SetupText(t, x, y) {
1329    switch (animationState.displayEngine) {
1330        case 'SVG':
1331            SetupTextSVG(t, x, y);
1332            break;
1333        case 'Canvas':
1334            SetupTextCanvas(t, x, y);
1335            break;
1336        default:
1337            debugger;
1338    }
1339}
1340
1341function FirstText(text) {
1342    SetupText(text);
1343    AnimationAdd([0, 1000], text);
1344}
1345
1346
1347function EngineInit(keyframe) {
1348    displayList = [];
1349    switch (animationState.displayEngine) {
1350        case 'SVG': break;
1351        case 'Canvas': CanvasInit(keyframe); break;
1352        default: debugger;
1353    }
1354}
1355
1356function EngineStart() {
1357    switch (animationState.displayEngine) {
1358        case 'SVG': break;
1359        case 'Canvas':
1360            // associate fadeIn and fadeOut
1361            for (var outerIndex in animationsPending) {
1362                var outer = animationsPending[outerIndex];
1363                for (var innerIndex in animationsPending) {
1364                    if (outerIndex == innerIndex) {
1365                        continue;
1366                    }
1367                    var inner = animationsPending[innerIndex];
1368                    if (inner.element == outer.element) {
1369                        inner.firstElement = outer;
1370                        continue;
1371                    }
1372                }
1373            }
1374            break;
1375        default: debugger;
1376    }
1377    animationState.reset();
1378    animationState.requestID = requestAnimationFrame(AnimateList);
1379}
1380
1381function AnimateSpanWedge() {
1382    EngineInit('keyframe1');
1383    FirstText(spanWedgeDesc);
1384    AnimationAdd([1000, 2000], span1);
1385    AnimationAdd([1500, 3000], wedge1);
1386    AnimationAdd([3500, 4000], span1,  [1, 0]);
1387    AnimationAdd([3500, 4000], wedge1, [1, 0]);
1388    AnimationAdd([4000, 5000], span2);
1389    AnimationAdd([4500, 6000], wedge2);
1390    AnimationAdd([6500, 7000], span2,  [1, 0]);
1391    AnimationAdd([6500, 7000], wedge2, [1, 0]);
1392    AnimationAdd([7000, 8000], span3);
1393    AnimationAdd([7500, 9000], wedge3);
1394    EngineStart();
1395}
1396
1397function AnimateTrivialWedge() {
1398    EngineInit('keyframe2');
1399    FirstText(trivialWedgeDesc1);
1400    FirstText(trivialWedgeDesc2);
1401    AnimationAdd([2000, 3500], span4);
1402    AnimationAdd([2000, 3500], wedge4);
1403    AnimationAdd([2000, 3500], span5);
1404    AnimationAdd([2000, 3500], wedge5);
1405    AnimationAdd([2000, 3500], span6);
1406    AnimationAdd([2000, 3500], wedge6);
1407    EngineStart();
1408}
1409
1410function AnimateSectorDesc() {
1411    EngineInit('keyframe3');
1412    FirstText(sectorDesc1);
1413    FirstText(sectorDesc2);
1414    AnimationAdd([   0, 1000], xaxis);
1415    AnimationAdd([ 500, 1500], yaxis);
1416    AnimationAdd([2000, 3500], sectorDescXYA);
1417    AnimationAdd([2000, 3500], wedgeXY8);
1418    AnimationAdd([3000, 4500], sectorDescXYB);
1419    AnimationAdd([3000, 4500], wedgeXY6);
1420    AnimationAdd([4000, 5500], sectorDescXYC);
1421    AnimationAdd([4000, 5500], wedgeXY3);
1422    EngineStart();
1423}
1424
1425function AnimateLineSingle() {
1426    EngineInit('keyframe4');
1427    FirstText(lineSingleDesc);
1428    for (var i = 1; i <= 8; ++i) {
1429        SetupText("sectorDescXY" + i, 500, 260);
1430    }
1431    AnimationAdd([   0, 1000], xaxis);
1432    AnimationAdd([   0, 1000], yaxis);
1433    AnimationAdd([1000, 2000], lineSegment);
1434    AnimationAdd([1000, 3000], lineSegment, [-22.5 * Math.PI / 180], "path", evaluate_at,
1435            [ circle.center.x, circle.center.y,
1436              circle.center.x + " + " + circle.radius + " * Math.cos(value)",
1437              circle.center.y + " + " + circle.radius + " * Math.sin(value)",
1438            ]);
1439    AnimationAdd([2000, 3000], sectorDescXY1);
1440    AnimationAdd([2000, 3000], wedgeXY1);
1441    AnimationAdd([3000, 7000], lineSegment, [-22.5 * Math.PI / 180, (-22.5 - 360) * Math.PI / 180],
1442            "path", evaluate_at,
1443            [ circle.center.x, circle.center.y,
1444              circle.center.x + " + " + circle.radius + " * Math.cos(value)",
1445              circle.center.y + " + " + circle.radius + " * Math.sin(value)",
1446            ]);
1447    for (var i = 1; i < 8; ++i) {
1448        AnimationAdd([2500 + 500 * i, 3000 + 500 * i], "sectorDescXY" + (i + 1));
1449        AnimationAdd([2500 + 500 * i, 3000 + 500 * i], "wedgeXY" + (i + 1));
1450        AnimationAdd([2500 + 500 * i, 3000 + 500 * i], "sectorDescXY" + i,       [1, 0]);
1451        AnimationAdd([2500 + 500 * i, 3000 + 500 * i], "wedgeXY" + i,            [1, 0]);
1452    }
1453    AnimationAdd([2500 + 500 * 8, 3000 + 500 * 8], sectorDescXY1);
1454    AnimationAdd([2500 + 500 * 8, 3000 + 500 * 8], wedgeXY1);
1455    AnimationAdd([2500 + 500 * 8, 3000 + 500 * 8], sectorDescXY8, [1, 0]);
1456    AnimationAdd([2500 + 500 * 8, 3000 + 500 * 8], wedgeXY8,      [1, 0]);
1457    EngineStart();
1458}
1459
1460function AnimateCurveMultiple() {
1461    EngineInit('keyframe5');
1462    var cubicStart = PathDataArray(curveSegment1);
1463    var cubicMid = PathDataArray(curveSegment2);
1464    var cubicEnd = PathDataArray(curveSegment3);
1465    FirstText(curveMultipleDesc1);
1466    FirstText(curveMultipleDesc2);
1467    for (var i = 1; i <= 6; ++i) {
1468        SetupText("sectorDescXY" + i, 500, 260 + i * 25);
1469    }
1470    AnimationAdd([   0, 1000], xaxis);
1471    AnimationAdd([   0, 1000], yaxis);
1472    AnimationAdd([1000, 2000], curveSegment);
1473    AnimationAdd([2000, 3000], sectorDescXY1);
1474    AnimationAdd([2000, 3000], wedgeXY1);
1475    AnimationAdd([3000, 4000], curveSegment, [0, 1], "path", interpolate_at, [cubicStart, cubicMid]);
1476    AnimationAdd([4000, 5000], sectorDescXY2);
1477    AnimationAdd([4000, 5000], wedgeXY2);
1478    AnimationAdd([5000, 6000], curveSegment, [0, 1], "path", interpolate_at, [cubicMid, cubicEnd]);
1479    AnimationAdd([6000, 7000], sectorDescXY3);
1480    AnimationAdd([6000, 7000], wedgeXY3);
1481    AnimationAdd([6000, 7000], sectorDescXY4);
1482    AnimationAdd([6000, 7000], wedgeXY4);
1483    AnimationAdd([6000, 7000], sectorDescXY5);
1484    AnimationAdd([6000, 7000], wedgeXY5);
1485    AnimationAdd([6000, 7000], sectorDescXY6);
1486    AnimationAdd([6000, 7000], wedgeXY6);
1487    EngineStart();
1488}
1489
1490function AnimateOneDLines() {
1491    EngineInit('keyframe6');
1492    FirstText(line1DDest1);
1493    FirstText(line1DDest2);
1494    for (var i = 9; i <= 11; ++i) {
1495        SetupText("sectorDescXY" + i, 500, 260 + (i - 8) * 25);
1496    }
1497    AnimationAdd([   0, 1000], xaxis);
1498    AnimationAdd([   0, 1000], yaxis);
1499    AnimationAdd([2000, 3000], sectorDescXY9);
1500    AnimationAdd([2000, 3000], horzSegment);
1501    AnimationAdd([3000, 4000], sectorDescXY10);
1502    AnimationAdd([3000, 4000], vertSegment);
1503    AnimationAdd([4000, 5000], sectorDescXY11);
1504    AnimationAdd([4000, 5000], diagSegment);
1505    EngineStart();
1506}
1507
1508function AnimateDiverging() {
1509    EngineInit('keyframe7');
1510    var cubicData = PathDataArray(cubicSegment2);
1511    FirstText(curve1dDesc1);
1512    FirstText(curve1dDesc2);
1513    SetupText("sectorDescXY9", 500, 285);
1514    SetupText("sectorDescXY1", 500, 320);
1515    AnimationAdd([   0, 1000], xaxis);
1516    AnimationAdd([   0, 1000], yaxis);
1517    AnimationAdd([1900, 1900], cubicSegment);
1518    AnimationAdd([2000, 3000], cubicSegment, [0, 1], "path", cubic_partial, cubicData);
1519    AnimationAdd([2000, 3000], sectorDescXY9);
1520    AnimationAdd([2000, 3000], horzSegment);
1521    AnimationAdd([3000, 4000], sectorDescXY1);
1522    AnimationAdd([3000, 4000], wedgeXY1);
1523    EngineStart();
1524}
1525
1526circle.animate = AnimateCircle;
1527circle.start = null;
1528
1529function AngleToPt(center, radius, degrees) {
1530    var radians = degrees * Math.PI / 180.0;
1531    return {
1532        x: center.x + (radius * Math.cos(radians)),
1533        y: center.y - (radius * Math.sin(radians))
1534    };
1535}
1536
1537function PtsToSweep(pt1, pt2, center) {  // unused
1538    return {
1539     start: 180 / Math.PI * Math.atan2(pt1.y - center.y, pt1.x - center.x),
1540     end:   180 / Math.PI * Math.atan2(pt2.y - center.y, pt2.x - center.x)
1541    };
1542}
1543
1544
1545function ArcStr(center, radius, startAngle, endAngle) {
1546    var endPt = AngleToPt(center, radius, endAngle);
1547    var arcSweep = endAngle - startAngle <= 180 ? "0" : "1";
1548    return ["A", radius, radius, 0, arcSweep, 0, endPt.x, endPt.y].join(" ");
1549}
1550
1551function ArcStart(center, radius, startAngle, endAngle) {
1552    var startPt = AngleToPt(center, radius, startAngle);
1553    return [ startPt.x, startPt.y, ArcStr(center, radius, startAngle, endAngle) ].join(" ");
1554}
1555
1556function MakeArc(arcStart) {
1557    return "M" + arcStart;
1558}
1559
1560function MakeWedge(center, arcStart) {
1561    return ["M", center.x, center.y, "L", arcStart, "z"].join(" ");
1562}
1563
1564function Animate(path, now, dur) {
1565    if (path.start == null) {
1566        path.start = now;
1567//        console.log("start=" + now);
1568    }
1569    if (now - path.start < dur) {
1570        requestAnimationFrame(path.animate);
1571        return true;
1572    }
1573    return false;
1574}
1575
1576function AnimateCircle(now) {
1577    if (circle.start == null) {
1578        circleFill.setAttribute("fill-opacity", "0.3");
1579    }
1580    var dur = 2 * 1000;
1581    var animating = Animate(circle, now, dur);
1582//    console.log("now=" + now + "circle.start=" + circle.start )
1583    var pathStr = ArcStart(circle.center, circle.radius, 0, (now - circle.start) / (dur / 359.9));
1584
1585    circle.setAttribute("d", MakeArc(pathStr));
1586    circleFill.setAttribute("d", MakeWedge(circle.center, pathStr));
1587    if (!animating) {
1588        var delay = dur - (now - circle.start);
1589        setTimeout(CircleFinal, delay);
1590    }
1591}
1592
1593function CircleFinal() {
1594    var firstHalf = ArcStart(circle.center, circle.radius, 0, 180);
1595    var secondHalf = ArcStr(circle.center, circle.radius, 180, 360);
1596    circle.setAttribute("d", "M" + firstHalf + secondHalf + "z");
1597    circleFill.setAttribute("d", "M" + firstHalf + secondHalf + "z");
1598}
1599
1600var svgNS = "http://www.w3.org/2000/svg";
1601
1602function CreateTextLabels()
1603{
1604    for (var i = 0; i < 32; ++i) {
1605        var text = document.createElementNS(svgNS, "text");
1606        var pt = AngleToPt(circle.center, circle.radius + 80, i * 360 / 32);
1607        text.setAttribute("id", "t" + i);
1608        text.setAttribute("x", pt.x);
1609        text.setAttribute("y", pt.y);
1610        text.setAttribute("text-anchor", "middle");
1611        text.setAttribute("alignment-baseline", "mathematical");
1612        var textNode = document.createTextNode(i);
1613        text.appendChild(textNode);
1614        document.getElementById("svg").appendChild(text);
1615    }
1616}
1617
1618// CreateTextLabels();
1619
1620var keyframeArray = [
1621    AnimateSpanWedge,
1622    AnimateTrivialWedge,
1623    AnimateSectorDesc,
1624    AnimateLineSingle,
1625    AnimateCurveMultiple,
1626    AnimateOneDLines,
1627    AnimateDiverging,
1628];
1629
1630var keyframeIndex = 3; // keyframeArray.length - 1;  // normally 0 ; set to debug a particular frame
1631
1632function QueueKeyframe() {
1633    QueueAnimation(keyframeArray[keyframeIndex]);
1634    if (keyframeIndex < keyframeArray.length - 1) {
1635        ++keyframeIndex;
1636    }
1637}
1638
1639var grads;
1640var paths;
1641var canvas;
1642var ctx;
1643
1644function canvasSetup() {
1645    canvas = document.getElementById("canvas");
1646    ctx = canvas ? canvas.getContext("2d") : null;
1647    assert(ctx);
1648    var resScale = animationState.resScale = window.devicePixelRatio ? window.devicePixelRatio : 1;
1649    var unscaledWidth = canvas.width;
1650    var unscaledHeight = canvas.height;
1651    canvas.width = unscaledWidth * resScale;
1652    canvas.height = unscaledHeight * resScale;
1653    canvas.style.width = unscaledWidth + 'px';
1654    canvas.style.height = unscaledHeight + 'px';
1655    if (resScale != 1) {
1656        ctx.scale(resScale, resScale);
1657    }
1658
1659    grads = CanvasGrads(ctx);
1660    paths = CanvasPaths(ctx);
1661}
1662
1663function Onload() {
1664    canvasSetup();
1665    var startBtn = document.getElementById('startBtn');
1666    var stopBtn = document.getElementById('stopBtn');
1667    var resetBtn = document.getElementById('resetBtn');
1668
1669    startBtn.addEventListener('click', function(e) {
1670        e.preventDefault();
1671        e.srcElement.innerText = "Next";
1672        CancelAnimation();
1673        QueueKeyframe();
1674    });
1675
1676    stopBtn.addEventListener('click', function(e) {
1677      e.preventDefault();
1678
1679      if (!animationState.paused) {
1680        PauseAnimation();
1681        e.srcElement.innerText = "Resume";
1682      } else {
1683        UnpauseAnimation();
1684        e.srcElement.innerText = "Pause";
1685      }
1686    });
1687
1688    resetBtn.addEventListener('click', function(e) {
1689        e.preventDefault();
1690        CancelAnimation();
1691        keyframeIndex = 0;
1692        startBtn.innerText = "Start";
1693        QueueKeyframe();
1694    });
1695}
1696
1697</script>
1698
1699</head>
1700
1701<body onLoad="Onload()">
1702
1703<div class="controls">
1704      <button type="button" id="startBtn">Start</button>
1705      <button type="button" id="stopBtn">Pause</button>
1706      <button type="button" id="resetBtn">Restart</button>
1707</div>
1708
1709<canvas id="canvas" width="800" height="500" />
1710
1711</body>
1712</html>