1 <!DOCTYPE html> 2 <html lang="en"> 3 <head> 4 5 6 <style> 7 html { 8 font-family: Helvetica, Arial, sans-serif; 9 font-size: 100%; 10 } 11 12 .controls { 13 margin: 1em 0; 14 } 15 16 button { 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 31 button:hover, button:focus { 32 opacity: 0.75; 33 cursor: pointer; 34 } 35 36 button: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" > 87 All 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" > 98 Wedges that don't overlap can be 99 </text> 100 <text id="trivialWedgeDesc2" y="240" fill-opacity="0" > 101 easily 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" > 113 A sector is a wedge of a circle 114 </text> 115 <text id="sectorDesc2" y="240" fill-opacity="0" > 116 containing 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"> 129 X > 0> Y > 0 Y < X</text> 130 <text id="sectorDescXYB" x="500" y="360" fill="rgb(0,127,0)" fill-opacity="0"> 131 X < 0 Y > 0 -Y < X</text> 132 <text id="sectorDescXYC" x="500" y="410" fill="rgb(255,0,0)" fill-opacity="0"> 133 X < 0 Y < 0 Y < 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" > 140 Line 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"> 143 X > 0 Y < 0 -Y < X</text> 144 <text id="sectorDescXY2" x="500" y="460" fill="rgb(127,127,0)" fill-opacity="0"> 145 X > 0 Y < 0 -Y > X</text> 146 <text id="sectorDescXY3" x="500" y="460" fill="rgb(255,0,0)" fill-opacity="0"> 147 X < 0 Y < 0 Y < X</text> 148 <text id="sectorDescXY4" x="500" y="460" fill="rgb(127,0,127)" fill-opacity="0"> 149 X < 0 Y < 0 Y > X</text> 150 <text id="sectorDescXY5" x="500" y="460" fill="rgb(0,127,127)" fill-opacity="0"> 151 X < 0 Y > 0 -Y < X</text> 152 <text id="sectorDescXY6" x="500" y="460" fill="rgb(0,127,0)" fill-opacity="0"> 153 X < 0 Y > 0 -Y < X</text> 154 <text id="sectorDescXY7" x="500" y="460" fill="rgb(63,192,63)" fill-opacity="0"> 155 X > 0 Y > 0 Y > X</text> 156 <text id="sectorDescXY8" x="500" y="460" fill="rgb(0,0,255)" fill-opacity="0"> 157 X > 0 Y > 0 Y < 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" > 167 A curve span may cover more 168 </text> 169 <text id="curveMultipleDesc2" y="240" fill-opacity="0" > 170 than 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" > 179 Some lines occupy one-dimensional 180 </text> 181 <text id="line1DDest2" y="240" fill-opacity="0" > 182 sectors. 183 </text> 184 <text id="sectorDescXY9" x="500" y="460" fill="rgb(192,92,31)" fill-opacity="0"> 185 X > 0 Y == 0</text> 186 <text id="sectorDescXY10" x="500" y="460" fill="rgb(31,92,192)" fill-opacity="0"> 187 Y > 0 0 == X</text> 188 <text id="sectorDescXY11" x="500" y="460" fill="rgb(127,63,127)" fill-opacity="0"> 189 X < 0 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" > 196 Some curves initially occupy 197 </text> 198 <text id="curve1dDesc2" y="240" fill-opacity="0" > 199 one-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" > 206 Each sector is assigned a number. 207 </text> 208 <text id="spanSectorDesc" fill-opacity="0" > 209 Each span has a bit set for one or more sectors. 210 </text> 211 <text id="bitOverDesc" fill-opacity="0" > 212 Span sets allow rough sorting without angle computation. 213 </text> 214 215 </svg> 216 217 <! canvas support /> 218 <script> 219 220 var keyFrameQueue = []; 221 var animationsPending = []; 222 var animationsActive = []; 223 var displayList = []; 224 var visibleFinished = []; 225 226 var animationState = {}; 227 animationState.reset = function () { 228 this.start = null; 229 this.time = 0; 230 this.requestID = null; 231 this.paused = false; 232 this.displayEngine = 'Canvas'; 233 } 234 235 circle.center = { x: 200, y: 200 } 236 circle.radius = 100; 237 238 function assert(condition) { 239 if (!condition) debugger; 240 } 241 242 function 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 280 function 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 290 function 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 313 function 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 324 function 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 466 function 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 524 function 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 533 function canvas_fill_text(record) { 534 assert(record); 535 assert(typeof record.fillText == 'string'); 536 return 'ctx.fillText("' + record.fillText + '"'; 537 } 538 539 function 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 545 function canvas_text_xy(record) { 546 return canvas_fill_text(record) + canvas_xy(record); 547 } 548 549 function 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 558 function add_canvas_style(record, style) { 559 record.style += style; 560 record.func = new Function('ctx', record.data + record.style + record.draw); 561 } 562 563 function 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 572 function 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 583 function 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 595 function 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 608 function 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 625 function 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 642 function 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 675 function 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 704 function 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 727 function 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 745 var canvasData = null; 746 747 function CanvasInit(keyframe) { 748 canvasData = window[keyframe](grads, paths); 749 } 750 751 </script> 752 753 <script> 754 755 function interp(A, B, t) { 756 return A + (B - A) * t; 757 } 758 759 function 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 770 function 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 796 function 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 805 function 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 825 function 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 839 function AnimationAddSVG(timing, element, range, attr, inParams) { 840 var animation = AnimationAddCommon(timing, range, attr, inParams); 841 animation.element = element; 842 return animation; 843 } 844 845 function 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 853 function 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 946 function 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 975 function PathDataArray(element) { 976 return path_data_common(element, true); 977 } 978 979 function PathVerbArray(element) { 980 return path_data_common(element, false); 981 } 982 983 function 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 1008 function 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 1018 function 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 1044 canvas: 1045 1046 display 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 1055 maybe each action should have an override slot 1056 animations write to the slot 1057 each element in display list then iterates overrides once the animations complete the frame 1058 1059 so, first -- 1060 active animations update the display list 1061 1062 next -- 1063 active animations install themselves in override slots 1064 1065 finally 1066 display list is iterated, calling override slots 1067 1068 ---------------- 1069 1070 svg: 1071 display list is implicit 1072 1073 active animations write element attributes 1074 */ 1075 1076 function 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 1095 function 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 1117 function 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 1132 var animate_frame = 0; 1133 1134 function 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 1212 function 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 1244 function 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 1276 function PauseAnimation() { 1277 animationState.paused = true; 1278 } 1279 1280 function QueueAnimation(animationFunc) { 1281 if (null == animationState.requestID) { 1282 animationFunc(); 1283 return; 1284 } 1285 keyFrameQueue.push(animationFunc); 1286 } 1287 1288 function UnpauseAnimation() { 1289 animationState.paused = false; 1290 animationState.start = performance.now() - animationState.time; 1291 animationState.requestID = requestAnimationFrame(AnimateList); 1292 } 1293 1294 function 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 1315 function 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 1328 function 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 1341 function FirstText(text) { 1342 SetupText(text); 1343 AnimationAdd([0, 1000], text); 1344 } 1345 1346 1347 function EngineInit(keyframe) { 1348 displayList = []; 1349 switch (animationState.displayEngine) { 1350 case 'SVG': break; 1351 case 'Canvas': CanvasInit(keyframe); break; 1352 default: debugger; 1353 } 1354 } 1355 1356 function 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 1381 function 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 1397 function 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 1410 function 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 1425 function 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 1460 function 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 1490 function 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 1508 function 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 1526 circle.animate = AnimateCircle; 1527 circle.start = null; 1528 1529 function 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 1537 function 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 1545 function 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 1551 function 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 1556 function MakeArc(arcStart) { 1557 return "M" + arcStart; 1558 } 1559 1560 function MakeWedge(center, arcStart) { 1561 return ["M", center.x, center.y, "L", arcStart, "z"].join(" "); 1562 } 1563 1564 function 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 1576 function 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 1593 function 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 1600 var svgNS = "http://www.w3.org/2000/svg"; 1601 1602 function 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 1620 var keyframeArray = [ 1621 AnimateSpanWedge, 1622 AnimateTrivialWedge, 1623 AnimateSectorDesc, 1624 AnimateLineSingle, 1625 AnimateCurveMultiple, 1626 AnimateOneDLines, 1627 AnimateDiverging, 1628 ]; 1629 1630 var keyframeIndex = 3; // keyframeArray.length - 1; // normally 0 ; set to debug a particular frame 1631 1632 function QueueKeyframe() { 1633 QueueAnimation(keyframeArray[keyframeIndex]); 1634 if (keyframeIndex < keyframeArray.length - 1) { 1635 ++keyframeIndex; 1636 } 1637 } 1638 1639 var grads; 1640 var paths; 1641 var canvas; 1642 var ctx; 1643 1644 function 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 1663 function 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>