Home | History | Annotate | Download | only in docs
      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 &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">
    131 X &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">
    133 X &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" >
    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 &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">
    145 X &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">
    147 X &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">
    149 X &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">
    151 X &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">
    153 X &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">
    155 X &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">
    157 X &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" >
    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 &gt; 0&nbsp;&nbsp;&nbsp;Y == 0</text>
    186 <text id="sectorDescXY10" x="500" y="460" fill="rgb(31,92,192)" fill-opacity="0">
    187 Y &gt; 0&nbsp;&nbsp;&nbsp;0 == X</text>
    188 <text id="sectorDescXY11" x="500" y="460" fill="rgb(127,63,127)" fill-opacity="0">
    189 X &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" >
    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>