comparison test-data/krona_test1.html @ 1:09552faff9c0 draft

planemo upload for repository https://github.com/galaxyproject/tools-iuc/tree/master/tools/taxonomy_krona_chart commit e4c2b31a732b57cc0210070afeaa2dfeca1e1eb4
author iuc
date Mon, 17 Aug 2015 11:36:09 -0400
parents
children 50f204c82159
comparison
equal deleted inserted replaced
0:bd565e1e3c5f 1:09552faff9c0
1 <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
2 <html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
3 <head>
4 <meta charset="utf-8"/>
5 <link rel="shortcut icon" href="data:image/x-icon;base64,AAABAAMAEBAAAAEAIABoBAAANgAAABgYAAABACAAiAkAAJ4EAAAgIAAAAQAgAKgQAAAmDgAAKAAAABAAAAAgAAAAAQAgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAP///wCAgIAC////AP///wC0tKJHlZWSqI6OmuRxcXn9koaK9J2Ym8uNm5V73PPoFv///wAAAAAD////AP///wCAgIAC////AP//7xCHh4OodHSi/2xqw/9nZ9H/XVuU/9eFqv/njb7/yYiq/5GAiOifqKJV////AAAAAAL///8A////AO3t2w56eoHPdHLH/3Vz3vx4dtz7dXTb/mVimv/XhKn/8ZPG/fSVx/vvlcT/kG+E/32Himb///8AAAAAA////wB0dHageHbO/3585fl5d9r/dnTX/3V02/5mYpz80X2i/eaIuv/ukcD/wXak/I6Maf+OkXD9iIKjL////wCEhFo+c3Kv/4F+6P18etz/e3nd/3l33P57eeT/aWag/9F5oP/sib3+tmuZ/3uEXf/P1YX7zdKG/3x9dbX///8Ac3J9nYKA4f+CgOb7fnzf/3174P58et//bm2v8Vtgc7SdbIHNqWmM/4aLZP/L0YT/0dWH/tfdiP+go3P6YlyWJ3BvmduGg+n/g4Hk/oF+4/6AfuX/bm2k7pCUUUX///8Ajv/jCVBmXqPBxoH/1NmI/c7Uhf/b4Yn9wMWB/2BdcGBraqX6iYbs/4SB5P+Eguj7hYPm/2Vlcov///8AACRtB////wAAAIAWrLB29uHojv/X3on+3eSK+9PYh/9XWVN7bWum+ouI7/+Gg+f/hoTq+4iF6f9lZXKL////ACQAbQf///8AAABoFoF+Xva0snL/y818/uDmiPvZ34n/WVlTe3Jxm9uMiu//iofr/oiG6f6Jhu3/cnGo7oyQQ0X///8A/znjCXF8dKNotpL/Za2O/WuVe/+FkGv9mJ5w/2pqaGBzc3+djYrr/42K8fuKh+v/i4js/oqH6/93dbfxYmJztGaTec1yxJr/gOGu/4nptf+K6Lf+i+q8/2eXhfqDNG8ngIBKPnt6uP+Rjvf9jInr/4yJ7f+Miu3+jor0/25xoP920pz/gOGw/oHerv+H4rL/kPC9+5Ttvf98ioC1////AP///wB0dHSgioff/5SR+/mOi+7/jYvt/4+L8P5scJ78etKe/YXis/+H47P/kO+8/JLxv/92m4f9nXKNL////wD///8A7e3bDnt7hM+Ihdv/ko/4/JSS+PuUkPb+bnKg/4Lapv+Q7739kfO/+43puf9/qJH/mYeUZv///wAAAAADgICAAv///wD//+8QhoaBqIB/sf+Iht7/jYjw/2tvnf+D3Kf/ieS1/4bIpP+AkYfoqJOfVf///wAAAAAC////AP///wCAgIAC////AP///wCwsJdHlZWQqJORn+RzdHv9hZOK9Jecmcuej5l7/+jzFv///wAAAAAD////AP///wD4PwAA4A8AAMAHAACAAwAAgAEAAAABAAADgQAAA8EAAAPBAAADgQAAAAEAAIABAACAAwAAwAcAAOAPAAD4PwAAKAAAABgAAAAwAAAAAQAgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAP///wD///8A////AP///wEAAAAB////AP///wi6urdDubmxj6ennsuQkIzvc3Nz/YSIh/WWoZzWrrezoLe6t1X///8T////AP///wCAgIAC////AP///wD///8A////AP///wD///8A////Af///wD///8Ax8fDQJeYkLV4eH39WlmM/19dpP9dX6b/WFRr/611j/+/fJ//om2I/4N3ff+Jko7Ntri2Xv///wD///8AgICAAv///wD///8A////AP///wD///8B////AP///wOfn5t9c3N29GRjn/9oZ8P/c3LU/XVy3P9rbdH/YVt8/9eIrv/5mcz/75bE/t+Mt/67epz/fnN5/4uTkKP///8W////AAAAAAL///8A////AP///wH///8A////A5iYkZJoaH3/bWvB/3h23Px4dt3+dHPX/3Rx1v9sbcv/YFp6/82ApP/rj8D/65DA//KVxv/1mcn83424/5t1iP98gX7A////F////wCAgIAC////AAAAAAL///8AkJCHdWZlf/9zcs7/fHrj/Hh32/92ddn/dXTX/3Zz2P9vcM//YFt7/8t9of/pi73/54u7/+eNvf/vk8P/7ZbE/pxsh/xLVU7/iomJqP///wD///8A////Af///wCfn5Y4ZmV07XZ0zv9+fOX8enjc/3p43P94dtv/dnXZ/3d12f9xcdD/YFp6/8h5nf/mh7r/44e3/+qMvf/ljrv/jV59/3F+V/68wH3/gYNp/4uIlWf///8AAAAAAv///wJ3d22pc3G6/4KA5/t9e9//fHre/3t53f96eNz/eHbZ/3l23P50dNb9Ylx+/ch2m/3jg7b+44W2/+CItv+NXXz/bnpW/8XIgv/Y3Yv8ur57/3Fxbtni4uIa////AHl5XTdoZ4b4f3zb/4F+5P5+fN//fXvf/3x63v96edz/ennf/nt54P9xccb/Xlxy/7Rvj//gf7P/3IKw/olZeP9te1b/xsqD/9DVh//P1IX/09iG/pWYcf9nZ3Nt////AISEb35ycLb/hYPr/YB+4f9/feH/fnzh/3173/99e+L+enjX/2dmmfhlZXiwYGBiknJkaqGVZ37pg11x/3eBXP7GyoP/0NWH/87Uhf/R1ob/2eCK+8HFff9tbXWv////BG9vdb16eMn/hYPq/4F/4/+BfuP/gH7i/4B94v59e93/Y2KO+ISEbX7y8uQT////AP///weAjo5aT1tP5L7Cf//T2Ij+ztOF/9HWhv/S14b/2d+K/s3Sgv+BgnLcg4OSI11deeaBf9r/hYPo/4OB5P+DgeX/gX/i/4OB6v10c77/cXFjmv///wL///8A////AP///wD///8AeHN9apuecP/U2Yf/0teG/tPZhv/V24f/2uCJ/tTahv+KjGj6AABINV9egfuGg+P/hoTp/4WC5v+EgeX/goDk/4WD6/1wbq//WFgAQ////wAzMzMFAAAAAVVVVQP///8AAABjEoiLZ/Df5Y7/2d+J/dfch//W3If/2d+H/9vhif6ZnW3/AAA5P19egvuHheX/iIXq/4eE6P+Gg+f/hILl/4eE7P1xb7D/WFgAQ////wAzMzMFAAAAAVVVVQP///8AAABVEnd1WvDEx37/y9GD/dnfif/g54z/4OeL/97kiv6anWz/AAA1P15eeuaFg93/ioft/4iF6f+Hhen/hoPn/4mG7/14d8L/cXFimv///wP///8A////AP///wD///8AenV3a1dyZf9QY1//bGVW/pORY/+trnD/xch7/tTZhP+Pkmn6AABINXBwdb1/fc7/jYry/4mH6v+Jhur/iIbp/4iG6/6Gg+X/Z2aT+IKEaX7y8uQT////AP///wiXgY9bXndp5HXLnv95z6P+dsif/2Wrjv9RcWv/Z3Rk/nyBYv9xcWbbfHyDI4SEa354d73/kI31/YuI6/+LiOz/iofr/4mG6v+Lh+3+hoPi/21soPhnZ3qwYGBikmNza6JhinXpcsWa/3vdq/6C4a//iOi1/4zpt/+O6Lf/kOu8+3O1l/9jVmKv////BHl5WDdraor4iofn/4+M8v6Miez/i4nt/4uI7P+LiOv/i4jt/ouJ8P9+e83/XGBt/223jv952aj/e9uq/n3aqv+D36//huKy/4rntv+Q7r3/lvTC/nupj/9zYmxt////AP///wJ3d2qpfXvF/5KP9vyNiu7/jYru/4yJ7f+Miu7/i4jr/46L8P6FgN79XmZ5/XTMm/1+3q7+f9ys/4Lgr/+G5LP/iue2/4zot/+V9cL8iNWs/3BxcNni4uIa////AP///wCfn5I4aGd17YeE3v+Ukfj8jovu/46L7/+Oi+//jYru/46M8f+FgNv/XmV3/3jMnf+D4rH/g+Cw/4fks/+K57b/jem4/5TywP6Q57n/bYh5/5KIjWf///8AAAAAAgAAAAL///8AkJCFdWtqhf+IheL/lZL5/JCN8P+PjO//j4zv/5GO8/+Hgt3/X2Z4/33Rof+I5rb/h+Sz/4nmtf+N6rn/lPPB/pHruvxxm4T/h3yDqP///wD///8A////Af///wH///8A////A5aWj5JtbIP/g4DW/5SR9vyVkvj+kY7y/5KP9P+Ig97/X2d4/4DUpf+N6rn/jeq4/5Lwvv+U88D8h9qu/3KVgv+Lgoe/////F////wCAgIAC////AP///wD///8B////AP///wOfn5l9dHR39HVzsf+Fg97/k4/x/ZiW/v+Nh+f/YWl7/4ngrv+W+MX/kuy8/onesP52uZb/dH54/5aOk6P///8W////AAAAAAL///8A////AP///wD///8A////Af///wD///8Ax8fDQJWVjbV5eX79amic/3h2vv93crf/WV9r/3W1kP95vZn/aZ2C/3eCfP+Ui5DNu7i7Xv///wD///8AgICAAv///wD///8A////AP///wD///8A////AP///wEAAAAB////AP///wi6urdDuLitj6WmmcuPj4rvc3Nz/YiEh/Whlp3Wt66zoLq3ulX///8T////AP///wD///8C////AP///wD///8A////AP8B/wD8AH8A+AAfAOAADwDgAAcAwAAHAIAAAwCAAAMAgAABAAB8AQAAfgEAAP4BAAD+AQAAfgEAAHwBAIAAAQCAAAMAgAADAMAABwDgAAcA4AAPAPgAHwD8AH8A/wH/ACgAAAAgAAAAQAAAAAEAIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////KO3t7XXFxcWwqqqq25eXl/RwcHD9mJiY9Kurq9vHx8ew8PDwdf///yj///8A////AP///wD///8A////AP///wD///8A////AP///wD///8AAAAAAP///wD///8A////AP///wD///8A////AP///wD///8A9/f3TLS0tLpvb3H/UVFR/1VVWf9bW27/WVlz/1FRUf96ZG//c2Nr/1lWV/9RUVH/d3V2/7m5ubr5+flM////AP///wD///8A////AP///wD///8A////AP///wAAAAAA////AP///wD///8A////AP///wD///8A/f39HLW1tatbW13/V1de/2FgkP9rabv/cG7S/29u0/9paL7/UVFR/9GIrv/wlcX/75bE/8+Irf+ZcYb/XVhb/2FfYP+8vLyr/v7+HP///wD///8A////AP///wD///8A////AAAAAAD///8A////AP///wD///8A////AOvr60l2dnnpVVVZ/2ZlnP9ycdL/c3LW/3Jx1f9xcNT/cG/T/2ppvv9RUVH/zoSs/+yRwf/uk8L/7pPC/++UxP/pk8D/pXaP/1hVV/9/fn/p7+/vSf///wD///8A////AP///wD///8AAAAAAP///wD///8A////AP///wDg4OBWX19h/11dcv9zccr/d3XZ/3Z02P91c9f/dHLX/3Nx1v9xcNT/a2q//1FRUf/Ngqn/6o6+/+uQwP/tksH/7pPD/++Vxf/xl8b/3I64/3Jiav9lY2T/2dnZVv///wD///8A////AP///wAAAAAA////AP///wD///8A5OTkQ1tbXf9hYIH/eHfY/3l32/94dtr/d3ba/3Z12f90c9f/c3LW/3Nx1f9tbMH/UVFR/8l/pf/mirr/6Iy8/+mOvv/qj7//7JHB/++VxP/xl8b/04qx/1RUUv9ZWVj/6+vrQ////wD///8A////AAAAAAD///8A////APX19Q9mZmjfYF98/3t53P97ed3/enjc/3l32/93dtr/dnXZ/3Z02P91c9f/dXTY/25tw/9RUVH/yHyj/+WIuf/libn/54q7/+mNvf/rj7//7JHB/9WJsf9XVlX/Z2hb/3BxX/9vb23f+fn5D////wD///8AAAAAAP///wD///8AkZCSnFpaZ/97edn/fHre/3t53v97ed3/enjc/3l33P94dtv/dnXZ/3V02P92dNj/cG7D/1FRUf/FeaH/4oW2/+OGt//liLj/54q6/+mOvv/Sha3/V1VV/2doW//FyYL/xcqC/19fWf+fn5+c////AP///wAAAAAA////ANjY2DhRUVL/dXPA/3994f9+fOD/fXvf/3x63v97ed3/enjc/3l32/94dtr/eHba/3d12f9xb8T/UVFR/8J1nf/fgLH/4IOz/+KEtf/liLn/zoCq/1dVVf9naFr/w8eA/83Shf/P1Yb/q652/1JSUf/h4eE4////AAAAAAD///8Afn6ApWNigv+BfuL/gH3h/3584P99e9//fHre/3x63v97ed3/eXjc/3l32/95d9v/d3ba/3Fwxf9RUVH/wXSc/9x9r//fgbL/4YO0/8t9pv9XVVT/Z2ha/8LHgf/N0oX/ztOF/8/Uhv/S14b/c3Vh/42Ni6X///8AAAAAAOfn5xJRUVH/eXbG/4F/4/+AfuL/f33h/3584P99e+D/fHrf/3x63v97ed3/e3ne/3p43f9ycMH/Y2OR/1FRUf+NY3j/w3Sd/9x9rv/IeaL/V1VU/2doWv/DyIH/zNGF/87Thf/Q1Yb/0daG/9TZh/+ytnj/UlJS/+/v7xIAAAAAq6urYF1cav+DgeX/goDk/4F/4/+AfuL/f33h/3994f9+fOD/fXvf/3x63v95d9X/YF99/1FRUb9sbG+AZmVmgG1qa4BRUVG/fWBv/1dVVP9naFr/xMiB/83Shv/O04b/z9WG/9HWhv/S14b/1NqH/9TZhv9fYFn/t7e3YAAAAAB6enyeaGeV/4SC5v+DgeX/goDk/4F+4/+BfuP/f33h/3584P9+fOD/fHrb/1xcbv9sbG6g39/fQP///wD///8A////ANra2kBbWlugVVVT/8LHgf/O04X/ztOF/8/Uhf/Q1Yb/0teG/9PZh//V24f/2N6I/4aIZv+IiIeeAAAAAFpaXMx0crX/hYLm/4SC5v+DgeX/goDk/4KA5P+Bf+P/gH7i/3994f9oZ5v/X19hv/Hx8SD///8A////AP///wD///8A////AOfn5yBcXFq/m55w/9DVhv/R1ob/0teG/9LXhv/T2Yb/1dqH/9jdiP/Z34j/o6Zy/2VlY8wAAAAAUVFR7Hx7zP+GhOj/hYLm/4SB5f+Egub/g4Hl/4KA5P+Bf+P/gX/j/1paZP+xsbFA////AP///wD///8A////AP///wD///8A////AKampkBqa17/0teG/9LXhv/T2Yf/1dqH/9bciP/Y3oj/2N6H/9rgiP+5vXr/UlJS7AAAAABRUVH7gX7W/4eE6P+GhOj/hYPn/4WC5v+EgeX/g4Hl/4OB5f9/fNv/UVFR/9zc3AD///8A////AP///wD///8A////AP///wD///8A0NDQAFRUU//R14b/1dqH/9Xbh//W3If/192H/9jeh//a4Ij/3OKI/8PJfv9RUVH7AAAAAFFRUfuCgNj/iIbq/4eE6P+HhOj/hoPn/4WC5v+Egub/hILm/4B+3f9RUVH/3NzcAP///wD///8A////AP///wD///8A////AP///wDLy8sAUlJR/87ThP/W3Ij/192H/9nfiP/Z34j/2uCI/9vhiP/d44j/xMh+/1FRUfsAAAAAUVFR7H99zv+Jh+v/iIXq/4iF6v+Hhen/hoTo/4aD5/+Fgub/hYLm/1taZP+xsbFA////AP///wD///8A////AP///wD///8A////AJWVlUBSUlH/UlJR/25vXf+Ul2z/ur97/9jeh//c44j/3uWJ/97lif+8wXr/UVFR7AAAAABaWlzMd3W4/4qH6/+Kh+v/iYbq/4iF6f+HhOj/iIXp/4aE6P+GhOj/a2qe/19fYr/x8fEg////AP///wD///8A////AP///wDp6ekgW1xcv2SWe/9nm4D/Wm9k/1daVv9RUVH/VlZT/3p8Yf+ipnH/ys+A/6ircv9lZmPMAAAAAHp6fJ5rapf/i4js/4uJ7f+KiOz/iYbq/4mG6v+Ihur/iIbq/4eE6P+FguP/Xl5w/2xsb6Df399A////AP///wD///8A2traQGVnZqBccGX/d9Sk/3vZqf+B367/g+Gw/33No/9qk33/W2lh/1ZXVP9RUVH/U1NR/3d3dZ4AAAAAq6urYF5ea/+Ni+//jInt/4uI7P+LiOz/iofr/4mG6v+Jhur/iIXp/4iF6f+EguD/ZGSB/1FRUb9ra2+AZWdmgGpubIBRUVG/Xn1u/3TOn/9516f/ftyr/4Herf+G47L/iOa1/4vot/+Q7bz/k+++/4PHo/9SVFP/oaGhYAAAAADn5+cSUVFR/4F/z/+Niu7/jInt/4yJ7f+LiOz/i4nt/4qI7P+LiOz/iofr/4mG6v+Jhur/fnzN/2ZljP9RUVH/YpB5/2++lf921KT/edem/3vZqf+A3q3/hOGw/4bjs/+J5rX/juu6/5Dsu/+U8cD/g8Wi/1JTUv/t7e0SAAAAAP///wB+foClZ2aG/46L8P+Oi/D/jYru/4yJ7f+Mie3/i4js/4uI7P+LiOz/iofr/4qH6/+KiOz/eXjA/1FRUf9zyJz/eden/3zaqf982qn/ftys/4Hfrv+G47L/iOW0/4rntv+N6rn/ku++/5XxwP9le2//iYqKpf///wAAAAAA////ANjY2DhRUVL/gH7L/4+M8P+Oi+//jovv/42K7v+Niu7/jIru/4yK7v+Mie3/jInt/4uI7P96eMD/UVFR/3bLoP972an/ftyr/4Herv+D4K//heOy/4jmtf+L6Lf/juu5/5DtvP+V8cD/gcGf/1NUU//l5eU4////AAAAAAD///8A////AJGRkpxdXGn/jYrp/5CN8f+PjPD/j4zw/46L7/+Oi+//jYru/42K7v+Niu7/jYru/3x5wv9RUVH/ec6i/4Herv+B367/hOGw/4bjsv+I5bT/i+i3/43quf+Q7bz/k+++/4/jtv9bY17/nZ6enP///wD///8AAAAAAP///wD///8A9vb2D2Zmad9mZYL/kI3w/5CN8f+QjfH/j4zw/4+M8P+PjPD/j4zw/46L7/+Oi+//fHrD/1FRUf9/0qb/hOGw/4Xjsv+F47L/iOa1/4vot/+O67r/kO28/5Lvvf+S7b3/ZHht/3N1dN/6+voP////AP///wAAAAAA////AP///wD///8A5OTkQ1xcXv9oZ4j/kI3v/5GN8f+RjfH/kI3x/5CN8f+QjfH/kI3x/5CN8f99e8P/UVFR/3/UqP+I5bT/iOW0/4vot/+L6Lf/juu5/4/su/+S773/kem6/2Z9cf9gYmH/6+vrQ////wD///8A////AAAAAAD///8A////AP///wD///8A4ODgVl9fYf9iYnf/iojh/5KP8/+Sj/P/kY7y/5GO8v+RjvL/kY7y/358xf9RUVH/hdmt/4vot/+O67n/juu5/5Dtu/+Q7bv/ku++/4rXrv9hcGj/aGpp/+np6Vb///8A////AP///wD///8AAAAAAP///wD///8A////AP///wD///8A7OzsSXd3eelWVlr/dXOr/5CO7v+TkPT/k5D0/5KP8/+Sj/P/f33F/1FRUf+I267/kO28/5Dsu/+Q7Lv/ku+9/5Douv9zoon/U1VU/4GDgunx8fFJ////AP///wD///8A////AP///wAAAAAA////AP///wD///8A////AP///wD///8A/f39HLW1tatbW13/WVlg/3Bunv+Gg9T/k4/y/5SR9f+Bfsf/UVFR/4vfsv+S773/kOu7/4TKpf9ul4H/VlpY/19hYP/AwMCr////HP///wD///8A////AP///wD///8A////AAAAAAD///8A////AP///wD///8A////AP///wD///8A////APf390y0tLS6cG9y/1FRUf9WVlr/Y2J2/2Fgdv9RUVH/ZYBx/2N1a/9VV1b/UVFR/3R2df+/v7+6+vr6TP///wD///8A////AP///wD///8A////AP///wD///8AAAAAAP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8o7e3tdcXFxbCqqqrbl5eX9HBwcP2YmJj0q6ur28fHx7Dv7+91////KP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wAAAAAA//Af//+AA//+AAD//AAAf/gAAD/wAAAf4AAAD8AAAAfAAAAHgAAAA4AAAAOAAAADAAfAAQAP4AEAH/ABAB/wAQAf8AEAH/ABAA/gAQAHwAGAAAADgAAAA4AAAAPAAAAHwAAAB+AAAA/wAAAf+AAAP/wAAH/+AAD//4AD///wH/8="/>
6 <script id="notfound">window.onload=function(){document.body.innerHTML=""}</script>
7 <script language="javascript" type="text/javascript">
8 {//-----------------------------------------------------------------------------
9 //
10 // PURPOSE
11 //
12 // Krona is a flexible tool for exploring the relative proportions of
13 // hierarchical data, such as metagenomic classifications, using a
14 // radial, space-filling display. It is implemented using HTML5 and
15 // JavaScript, allowing charts to be explored locally or served over the
16 // Internet, requiring only a current version of any major web
17 // browser. Krona charts can be created using an Excel template or from
18 // common bioinformatic formats using the provided conversion scripts.
19 //
20 //
21 // COPYRIGHT LICENSE
22 //
23 // Copyright (c) 2011, Battelle National Biodefense Institute (BNBI);
24 // all rights reserved. Authored by: Brian Ondov, Nicholas Bergman, and
25 // Adam Phillippy
26 //
27 // This Software was prepared for the Department of Homeland Security
28 // (DHS) by the Battelle National Biodefense Institute, LLC (BNBI) as
29 // part of contract HSHQDC-07-C-00020 to manage and operate the National
30 // Biodefense Analysis and Countermeasures Center (NBACC), a Federally
31 // Funded Research and Development Center.
32 //
33 // Redistribution and use in source and binary forms, with or without
34 // modification, are permitted provided that the following conditions are
35 // met:
36 //
37 // * Redistributions of source code must retain the above copyright
38 // notice, this list of conditions and the following disclaimer.
39 //
40 // * Redistributions in binary form must reproduce the above copyright
41 // notice, this list of conditions and the following disclaimer in the
42 // documentation and/or other materials provided with the distribution.
43 //
44 // * Neither the name of the Battelle National Biodefense Institute nor
45 // the names of its contributors may be used to endorse or promote
46 // products derived from this software without specific prior written
47 // permission.
48 //
49 // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
50 // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
51 // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
52 // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
53 // HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
54 // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
55 // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
56 // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
57 // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
58 // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
59 // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
60 //
61 //
62 // TRADEMARK LICENSE
63 //
64 // KRONA(TM) is a trademark of the Department of Homeland Security, and use
65 // of the trademark is subject to the following conditions:
66 //
67 // * Distribution of the unchanged, official code/software using the
68 // KRONA(TM) mark is hereby permitted by the Department of Homeland
69 // Security, provided that the software is distributed without charge
70 // and modification.
71 //
72 // * Distribution of altered source code/software using the KRONA(TM) mark
73 // is not permitted unless written permission has been granted by the
74 // Department of Homeland Security.
75 //
76 //
77 // FOR MORE INFORMATION VISIT
78 //
79 // http://krona.sourceforge.net
80 //
81 //-----------------------------------------------------------------------------
82 }
83
84
85 var canvas;
86 var context;
87 var svg; // for snapshot mode
88 var collapse = true;
89 var collapseCheckBox;
90 var collapseLast;
91 var compress;
92 var compressCheckBox;
93 var maxAbsoluteDepthText;
94 var maxAbsoluteDepthButtonDecrease;
95 var maxAbsoluteDepthButtonIncrease;
96 var fontSize = 11;
97 var fontSizeText;
98 var fontSizeButtonDecrease;
99 var fontSizeButtonIncrease;
100 var fontSizeLast;
101 var radiusButtonDecrease;
102 var radiusButtonIncrease;
103 var shorten;
104 var shortenCheckBox;
105 var maxAbsoluteDepth;
106 var backButton;
107 var upButton;
108 var forwardButton;
109 var snapshotButton;
110 var snapshotMode = false;
111 var details;
112 var detailsName;
113 var search;
114 var searchResults;
115 var nSearchResults;
116 var useHueCheckBox;
117 var useHueDiv;
118 var datasetDropDown;
119 var datasetButtonLast;
120 var datasetButtonPrev;
121 var datasetButtonNext;
122 var keyControl;
123 var showKeys = true;
124 var linkButton;
125 var linkText;
126 var frame;
127
128 // Node references. Note that the meanings of 'selected' and 'focused' are
129 // swapped in the docs.
130 //
131 var head; // the root of the entire tree
132 var selectedNode = 0; // the root of the current view
133 var focusNode = 0; // a node chosen for more info (single-click)
134 var highlightedNode = 0; // mouse hover node
135 var highlightingHidden = false;
136 var nodes = new Array();
137 var currentNodeID = 0; // to iterate while loading
138
139 var nodeHistory = new Array();
140 var nodeHistoryPosition = 0;
141
142 var dataEnabled = false; // true when supplemental files are present
143
144 // store non-Krona GET variables so they can be passed on to links
145 //
146 var getVariables = new Array();
147
148 // selectedNodeLast is separate from the history, since we need to check
149 // properties of the last node viewed when browsing through the history
150 //
151 var selectedNodeLast = 0;
152 var zoomOut = false;
153
154 // temporary zoom-in while holding the mouse button on a wedge
155 //
156 var quickLook = false; // true when in quick look state
157 var mouseDown = false;
158 var mouseDownTime; // to detect mouse button hold
159 var quickLookHoldLength = 200;
160
161 var imageWidth;
162 var imageHeight;
163 var centerX;
164 var centerY;
165 var gRadius;
166 var updateViewNeeded = false;
167
168 // Determines the angle that the pie chart starts at. 90 degrees makes the
169 // center label consistent with the children.
170 //
171 var rotationOffset = Math.PI / 2;
172
173 var buffer;
174 var bufferFactor = .1;
175
176 // The maps are the small pie charts showing the current slice being viewed.
177 //
178 var mapBuffer = 10;
179 var mapRadius = 0;
180 var maxMapRadius = 25;
181 var mapWidth = 150;
182 var maxLabelOverhang = Math.PI * 4.18;
183
184 // Keys are the labeled boxes for slices in the highest level that are too thin
185 // to label.
186 //
187 var maxKeySizeFactor = 2; // will be multiplied by font size
188 var keySize;
189 var keys;
190 var keyBuffer = 10;
191 var currentKey;
192 var keyMinTextLeft;
193 var keyMinAngle;
194
195 var minRingWidthFactor = 5; // will be multiplied by font size
196 var maxPossibleDepth; // the theoretical max that can be displayed
197 var maxDisplayDepth; // the actual depth that will be displayed
198 var headerHeight = 0;//document.getElementById('options').clientHeight;
199 var historySpacingFactor = 1.6; // will be multiplied by font size
200 var historyAlphaDelta = .25;
201
202 // appearance
203 //
204 var lineOpacity = 0.3;
205 var saturation = 0.5;
206 var lightnessBase = 0.6;
207 var lightnessMax = .8;
208 var thinLineWidth = .3;
209 var highlightLineWidth = 1.5;
210 var labelBoxBuffer = 6;
211 var labelBoxRounding = 15;
212 var labelWidthFudge = 1.05; // The width of unshortened labels are set slightly
213 // longer than the name width so the animation
214 // finishes faster.
215 var fontNormal;
216 var fontBold;
217 var fontFamily = 'sans-serif';
218 //var fontFaceBold = 'bold Arial';
219 var nodeRadius;
220 var angleFactor;
221 var tickLength;
222 var compressedRadii;
223
224 // colors
225 //
226 var highlightFill = 'rgba(255, 255, 255, .3)';
227 var colorUnclassified = 'rgb(220,220,220)';
228
229 // label staggering
230 //
231 var labelOffsets; // will store the current offset at each depth
232 //
233 // This will store pointers to the last node that had a label in each offset (or "track") of a
234 // each depth. These will be used to shorten neighboring labels that would overlap.
235 // The [nLabelNodes] index will store the last node with a radial label.
236 // labelFirstNodes is the same, but to check for going all the way around and
237 // overlapping the first labels.
238 //
239 var labelLastNodes;
240 var labelFirstNodes;
241 //
242 var nLabelOffsets = 3; // the number of offsets to use
243
244 var mouseX = -1;
245 var mouseY = -1;
246
247 // tweening
248 //
249 var progress = 0; // for tweening; goes from 0 to 1.
250 var progressLast = 0;
251 var tweenFactor = 0; // progress converted by a curve for a smoother effect.
252 var tweenLength = 850; // in ms
253 var tweenCurvature = 13;
254 //
255 // tweenMax is used to scale the sigmoid function so its range is [0,1] for the
256 // domain [0,1]
257 //
258 var tweenMax = 1 / (1 + Math.exp(-tweenCurvature / 2));
259 //
260 var tweenStartTime;
261
262 // for framerate debug
263 //
264 var tweenFrames = 0;
265 var fpsDisplay = document.getElementById('frameRate');
266
267 // Arrays to translate xml attribute names into displayable attribute names
268 //
269 var attributes = new Array();
270 //
271 var magnitudeIndex; // the index of attribute arrays used for magnitude
272 var membersAssignedIndex;
273 var membersSummaryIndex;
274
275 // For defining gradients
276 //
277 var hueDisplayName;
278 var hueStopPositions;
279 var hueStopHues;
280 var hueStopText;
281
282 // multiple datasets
283 //
284 var currentDataset = 0;
285 var lastDataset = 0;
286 var datasets = 1;
287 var datasetNames;
288 var datasetSelectSize = 30;
289 var datasetAlpha = new Tween(0, 0);
290 var datasetWidths = new Array();
291 var datasetChanged;
292 var datasetSelectWidth = 50;
293
294 window.onload = load;
295
296 var image;
297 var hiddenPattern;
298 var loadingImage;
299 var logoImage;
300
301 function resize()
302 {
303 imageWidth = window.innerWidth;
304 imageHeight = window.innerHeight;
305
306 if ( ! snapshotMode )
307 {
308 context.canvas.width = imageWidth;
309 context.canvas.height = imageHeight;
310 }
311
312 if ( datasetDropDown )
313 {
314 var ratio =
315 (datasetDropDown.offsetTop + datasetDropDown.clientHeight) * 2 /
316 imageHeight;
317
318 if ( ratio > 1 )
319 {
320 ratio = 1;
321 }
322
323 ratio = Math.sqrt(ratio);
324
325 datasetSelectWidth =
326 (datasetDropDown.offsetLeft + datasetDropDown.clientWidth) * ratio;
327 }
328 var leftMargin = datasets > 1 ? datasetSelectWidth + 30 : 0;
329 var minDimension = imageWidth - mapWidth - leftMargin > imageHeight ?
330 imageHeight :
331 imageWidth - mapWidth - leftMargin;
332
333 maxMapRadius = minDimension * .03;
334 buffer = minDimension * bufferFactor;
335 margin = minDimension * .015;
336 centerX = (imageWidth - mapWidth - leftMargin) / 2 + leftMargin;
337 centerY = imageHeight / 2;
338 gRadius = minDimension / 2 - buffer;
339 //context.font = '11px sans-serif';
340 }
341
342 function handleResize()
343 {
344 updateViewNeeded = true;
345 }
346
347 function Attribute()
348 {
349 }
350
351 function Tween(start, end)
352 {
353 this.start = start;
354 this.end = end;
355 this.current = this.start;
356
357 this.current = function()
358 {
359 if ( progress == 1 || this.start == this.end )
360 {
361 return this.end;
362 }
363 else
364 {
365 return this.start + tweenFactor * (this.end - this.start);
366 }
367 };
368
369 this.setTarget = function(target)
370 {
371 this.start = this.current();
372 this.end = target;
373 }
374 }
375
376 function Node()
377 {
378 this.id = currentNodeID;
379 currentNodeID++;
380 nodes[this.id] = this;
381
382 this.angleStart = new Tween(Math.PI, 0);
383 this.angleEnd = new Tween(Math.PI, 0);
384 this.radiusInner = new Tween(1, 1);
385 this.labelRadius = new Tween(1, 1);
386 this.labelWidth = new Tween(0, 0);
387 this.scale = new Tween(1, 1); // TEMP
388 this.radiusOuter = new Tween(1, 1);
389
390 this.r = new Tween(255, 255);
391 this.g = new Tween(255, 255);
392 this.b = new Tween(255, 255);
393
394 this.alphaLabel = new Tween(0, 1);
395 this.alphaLine = new Tween(0, 1);
396 this.alphaArc = new Tween(0, 0);
397 this.alphaWedge = new Tween(0, 1);
398 this.alphaOther = new Tween(0, 1);
399 this.alphaPattern = new Tween(0, 0);
400 this.children = Array();
401 this.parent = 0;
402
403 this.attributes = new Array(attributes.length);
404
405 this.addChild = function(child)
406 {
407 this.children.push(child);
408 };
409
410 this.addLabelNode = function(depth, labelOffset)
411 {
412 if ( labelHeadNodes[depth][labelOffset] == 0 )
413 {
414 // this will become the head node for this list
415
416 labelHeadNodes[depth][labelOffset] = this;
417 this.labelPrev = this;
418 }
419
420 var head = labelHeadNodes[depth][labelOffset];
421
422 this.labelNext = head;
423 this.labelPrev = head.labelPrev;
424 head.labelPrev.labelNext = this;
425 head.labelPrev = this;
426 }
427
428 this.canDisplayDepth = function()
429 {
430 // whether this node is at a depth that can be displayed, according
431 // to the max absolute depth
432
433 return this.depth <= maxAbsoluteDepth;
434 }
435
436 this.canDisplayHistory = function()
437 {
438 var radiusInner;
439
440 if ( compress )
441 {
442 radiusInner = compressedRadii[0];
443 }
444 else
445 {
446 radiusInner = nodeRadius;
447 }
448
449 return (
450 -this.labelRadius.end * gRadius +
451 historySpacingFactor * fontSize / 2 <
452 radiusInner * gRadius
453 );
454 }
455
456 this.canDisplayLabelCurrent = function()
457 {
458 return (
459 (this.angleEnd.current() - this.angleStart.current()) *
460 (this.radiusInner.current() * gRadius + gRadius) >=
461 minWidth());
462 }
463
464 this.checkHighlight = function()
465 {
466 if ( this.children.length == 0 && this == focusNode )
467 {
468 //return false;
469 }
470
471 if ( this.hide )
472 {
473 return false;
474 }
475
476 if ( this.radiusInner.end == 1 )
477 {
478 // compressed to the outside; don't check
479
480 return false;
481 }
482
483 var highlighted = false;
484
485 var angleStartCurrent = this.angleStart.current() + rotationOffset;
486 var angleEndCurrent = this.angleEnd.current() + rotationOffset;
487 var radiusInner = this.radiusInner.current() * gRadius;
488
489 for ( var i = 0; i < this.children.length; i++ )
490 {
491 highlighted = this.children[i].checkHighlight();
492
493 if ( highlighted )
494 {
495 return true;
496 }
497 }
498
499 if ( this != selectedNode && ! this.getCollapse() )
500 {
501 context.beginPath();
502 context.arc(0, 0, radiusInner, angleStartCurrent, angleEndCurrent, false);
503 context.arc(0, 0, gRadius, angleEndCurrent, angleStartCurrent, true);
504 context.closePath();
505
506 if ( context.isPointInPath(mouseX - centerX, mouseY - centerY) )
507 {
508 highlighted = true;
509 }
510
511 if
512 (
513 ! highlighted &&
514 (angleEndCurrent - angleStartCurrent) *
515 (radiusInner + gRadius) <
516 minWidth() &&
517 this.getDepth() == selectedNode.getDepth() + 1
518 )
519 {
520 if ( showKeys && this.checkHighlightKey() )
521 {
522 highlighted = true;
523 }
524 }
525 }
526
527 if ( highlighted )
528 {
529 if ( this != highlightedNode )
530 {
531 // document.body.style.cursor='pointer';
532 }
533
534 highlightedNode = this;
535 }
536
537 return highlighted;
538 }
539
540 this.checkHighlightCenter = function()
541 {
542 if ( ! this.canDisplayHistory() )
543 {
544 return;
545 }
546
547 var cx = centerX;
548 var cy = centerY - this.labelRadius.end * gRadius;
549 //var dim = context.measureText(this.name);
550
551 var width = this.nameWidth;
552
553 if ( this.searchResultChildren() )
554 {
555 var results = searchResultString(this.searchResultChildren());
556 var dim = context.measureText(results);
557 width += dim.width;
558 }
559
560 if
561 (
562 mouseX > cx - width / 2 &&
563 mouseX < cx + width / 2 &&
564 mouseY > cy - historySpacingFactor * fontSize / 2 &&
565 mouseY < cy + historySpacingFactor * fontSize / 2
566 )
567 {
568 highlightedNode = this;
569 return;
570 }
571
572 if ( this.getParent() )
573 {
574 this.getParent().checkHighlightCenter();
575 }
576 }
577
578 this.checkHighlightKey = function()
579 {
580 var offset = keyOffset();
581
582 var xMin = imageWidth - keySize - margin - this.keyNameWidth - keyBuffer;
583 var xMax = imageWidth - margin;
584 var yMin = offset;
585 var yMax = offset + keySize;
586
587 currentKey++;
588
589 return (
590 mouseX > xMin &&
591 mouseX < xMax &&
592 mouseY > yMin &&
593 mouseY < yMax);
594 }
595
596 this.checkHighlightMap = function()
597 {
598 if ( this.parent )
599 {
600 this.parent.checkHighlightMap();
601 }
602
603 if ( this.getCollapse() || this == focusNode )
604 {
605 return;
606 }
607
608 var box = this.getMapPosition();
609
610 if
611 (
612 mouseX > box.x - mapRadius &&
613 mouseX < box.x + mapRadius &&
614 mouseY > box.y - mapRadius &&
615 mouseY < box.y + mapRadius
616 )
617 {
618 highlightedNode = this;
619 }
620 }
621
622 /* this.collapse = function()
623 {
624 for (var i = 0; i < this.children.length; i++ )
625 {
626 this.children[i] = this.children[i].collapse();
627 }
628
629 if
630 (
631 this.children.length == 1 &&
632 this.children[0].magnitude == this.magnitude
633 )
634 {
635 this.children[0].parent = this.parent;
636 this.children[0].getDepth() = this.parent.getDepth() + 1;
637 return this.children[0];
638 }
639 else
640 {
641 return this;
642 }
643 }
644 */
645 this.draw = function(labelMode, selected, searchHighlighted)
646 {
647 var depth = this.getDepth() - selectedNode.getDepth() + 1;
648 // var hidden = false;
649
650 if ( selectedNode == this )
651 {
652 selected = true;
653 }
654
655 var angleStartCurrent = this.angleStart.current() + rotationOffset;
656 var angleEndCurrent = this.angleEnd.current() + rotationOffset;
657 var radiusInner = this.radiusInner.current() * gRadius;
658 var canDisplayLabelCurrent = this.canDisplayLabelCurrent();
659 var hiddenSearchResults = false;
660
661 /* if ( ! this.hide )
662 {
663 for ( var i = 0; i < this.children.length; i++ )
664 {
665 if ( this.children[i].hide && this.children[i].searchResults )
666 {
667 hiddenSearchResults = true;
668 }
669 }
670 }
671 */
672 var drawChildren =
673 ( ! this.hide || ! this.hidePrev && progress < 1 ) &&
674 ( ! this.hideAlone || ! this.hideAlonePrev && progress < 1 );
675
676 // if ( this.alphaWedge.current() > 0 || this.alphaLabel.current() > 0 )
677 {
678 var lastChildAngleEnd;
679
680 if ( this.hasChildren() )//canDisplayChildren )
681 {
682 lastChildAngleEnd =
683 this.children[this.children.length - 1].angleEnd.current()
684 + rotationOffset;
685 }
686
687 if ( labelMode )
688 {
689 var drawRadial =
690 !(
691 this.parent &&
692 this.parent != selectedNode &&
693 angleEndCurrent == this.parent.angleEnd.current() + rotationOffset
694 );
695
696 if ( angleStartCurrent != angleEndCurrent )
697 {
698 this.drawLines(angleStartCurrent, angleEndCurrent, radiusInner, drawRadial, selected);
699 }
700
701 var alphaOtherCurrent = this.alphaOther.current();
702 var childRadiusInner;
703
704 if ( this == selectedNode || alphaOtherCurrent )
705 {
706 childRadiusInner =
707 this.children[this.children.length - 1].radiusInner.current() * gRadius;
708 }
709
710 if ( this == selectedNode )
711 {
712 this.drawReferenceRings(childRadiusInner);
713 }
714
715 if
716 (
717 selected &&
718 ! searchHighlighted &&
719 this != selectedNode &&
720 (
721 this.isSearchResult ||
722 this.hideAlone && this.searchResultChildren() ||
723 false
724 // this.hide &&
725 // this.containsSearchResult
726 )
727 )
728 {
729 context.globalAlpha = this.alphaWedge.current();
730
731 drawWedge
732 (
733 angleStartCurrent,
734 angleEndCurrent,
735 radiusInner,
736 gRadius,
737 highlightFill,
738 0,
739 true
740 );
741
742 if
743 (
744 this.keyed &&
745 ! showKeys &&
746 this.searchResults &&
747 ! searchHighlighted &&
748 this != highlightedNode &&
749 this != focusNode
750 )
751 {
752 var angle = (angleEndCurrent + angleStartCurrent) / 2;
753 this.drawLabel(angle, true, false, true, true);
754 }
755
756 //this.drawHighlight(false);
757 searchHighlighted = true;
758 }
759
760 if
761 (
762 this == selectedNode ||
763 // true
764 //(canDisplayLabelCurrent) &&
765 this != highlightedNode &&
766 this != focusNode
767 )
768 {
769 if ( this.radial != this.radialPrev && this.alphaLabel.end == 1 )
770 {
771 context.globalAlpha = tweenFactor;
772 }
773 else
774 {
775 context.globalAlpha = this.alphaLabel.current();
776 }
777
778 this.drawLabel
779 (
780 (angleStartCurrent + angleEndCurrent) / 2,
781 this.hideAlone && this.searchResultChildren() ||
782 (this.isSearchResult || hiddenSearchResults) && selected,
783 this == selectedNode && ! this.radial,
784 selected,
785 this.radial
786 );
787
788 if ( this.radial != this.radialPrev && this.alphaLabel.start == 1 && progress < 1 )
789 {
790 context.globalAlpha = 1 - tweenFactor;
791
792 this.drawLabel
793 (
794 (angleStartCurrent + angleEndCurrent) / 2,
795 (this.isSearchResult || hiddenSearchResults) && selected,
796 this == selectedNodeLast && ! this.radialPrev,
797 selected,
798 this.radialPrev
799 );
800 }
801 }
802
803 if
804 (
805 alphaOtherCurrent &&
806 lastChildAngleEnd != null
807 )
808 {
809 if
810 (
811 (angleEndCurrent - lastChildAngleEnd) *
812 (childRadiusInner + gRadius) >=
813 minWidth()
814 )
815 {
816 //context.font = fontNormal;
817 context.globalAlpha = this.alphaOther.current();
818
819 drawTextPolar
820 (
821 this.getUnclassifiedText(),
822 this.getUnclassifiedPercentage(),
823 (lastChildAngleEnd + angleEndCurrent) / 2,
824 (childRadiusInner + gRadius) / 2,
825 true,
826 false,
827 false,
828 0,
829 0
830 );
831 }
832 }
833
834 if ( this == selectedNode && this.keyUnclassified && showKeys )
835 {
836 this.drawKey
837 (
838 (lastChildAngleEnd + angleEndCurrent) / 2,
839 false,
840 false
841 );
842 }
843 }
844 else
845 {
846 var alphaWedgeCurrent = this.alphaWedge.current();
847
848 if ( alphaWedgeCurrent || this.alphaOther.current() )
849 {
850 var currentR = this.r.current();
851 var currentG = this.g.current();
852 var currentB = this.b.current();
853
854 var fill = rgbText(currentR, currentG, currentB);
855
856 var radiusOuter;
857 var lastChildAngle;
858 var truncateWedge =
859 (
860 this.hasChildren() &&
861 ! this.keyed &&
862 (compress || depth < maxDisplayDepth) &&
863 drawChildren
864 );
865
866 if ( truncateWedge )
867 {
868 radiusOuter = this.children[0].radiusInner.current() * gRadius;
869 }
870 else
871 {
872 radiusOuter = gRadius;
873 }
874 /*
875 if ( this.hasChildren() )
876 {
877 radiusOuter = this.children[0].getUncollapsed().radiusInner.current() * gRadius + 1;
878 }
879 else
880 { // TEMP
881 radiusOuter = radiusInner + nodeRadius * gRadius;
882
883 if ( radiusOuter > gRadius )
884 {
885 radiusOuter = gRadius;
886 }
887 }
888 */
889 context.globalAlpha = alphaWedgeCurrent;
890
891 if ( radiusInner != radiusOuter )
892 {
893 drawWedge
894 (
895 angleStartCurrent,
896 angleEndCurrent,
897 radiusInner,
898 radiusOuter,//this.radiusOuter.current() * gRadius,
899 //'rgba(0, 200, 0, .1)',
900 fill,
901 this.alphaPattern.current()
902 );
903
904 if ( truncateWedge )
905 {
906 // fill in the extra space if the sum of our childrens'
907 // magnitudes is less than ours
908
909 if ( lastChildAngleEnd < angleEndCurrent )//&& false) // TEMP
910 {
911 if ( radiusOuter > 1 )
912 {
913 // overlap slightly to hide the seam
914
915 // radiusOuter -= 1;
916 }
917
918 if ( alphaWedgeCurrent < 1 )
919 {
920 context.globalAlpha = this.alphaOther.current();
921 drawWedge
922 (
923 lastChildAngleEnd,
924 angleEndCurrent,
925 radiusOuter,
926 gRadius,
927 colorUnclassified,
928 0
929 );
930 context.globalAlpha = alphaWedgeCurrent;
931 }
932
933 drawWedge
934 (
935 lastChildAngleEnd,
936 angleEndCurrent,
937 radiusOuter,
938 gRadius,//this.radiusOuter.current() * gRadius,
939 //'rgba(200, 0, 0, .1)',
940 fill,
941 this.alphaPattern.current()
942 );
943 }
944 }
945
946 if ( radiusOuter < gRadius )
947 {
948 // patch up the seam
949 //
950 context.beginPath();
951 context.arc(0, 0, radiusOuter, angleStartCurrent/*lastChildAngleEnd*/, angleEndCurrent, false);
952 context.strokeStyle = fill;
953 context.lineWidth = 1;
954 context.stroke();
955 }
956 }
957
958 if ( this.keyed && selected && showKeys )//&& progress == 1 )
959 {
960 this.drawKey
961 (
962 (angleStartCurrent + angleEndCurrent) / 2,
963 (
964 this == highlightedNode ||
965 this == focusNode ||
966 this.searchResults
967 ),
968 this == highlightedNode || this == focusNode
969 );
970 }
971 }
972 }
973 }
974
975 if ( drawChildren )
976 {
977 // draw children
978 //
979 for ( var i = 0; i < this.children.length; i++ )
980 {
981 if ( this.drawHiddenChildren(i, selected, labelMode, searchHighlighted) )
982 {
983 i = this.children[i].hiddenEnd;
984 }
985 else
986 {
987 this.children[i].draw(labelMode, selected, searchHighlighted);
988 }
989 }
990 }
991 };
992
993 this.drawHiddenChildren = function
994 (
995 firstHiddenChild,
996 selected,
997 labelMode,
998 searchHighlighted
999 )
1000 {
1001 var firstChild = this.children[firstHiddenChild];
1002
1003 if ( firstChild.hiddenEnd == null || firstChild.radiusInner.current() == 1 )
1004 {
1005 return false;
1006 }
1007
1008 for ( var i = firstHiddenChild; i < firstChild.hiddenEnd; i++ )
1009 {
1010 if ( ! this.children[i].hide || ! this.children[i].hidePrev && progress < 1 )
1011 {
1012 return false;
1013 }
1014 }
1015
1016 var angleStart = firstChild.angleStart.current() + rotationOffset;
1017 var lastChild = this.children[firstChild.hiddenEnd];
1018 var angleEnd = lastChild.angleEnd.current() + rotationOffset;
1019 var radiusInner = gRadius * firstChild.radiusInner.current();
1020 var hiddenChildren = firstChild.hiddenEnd - firstHiddenChild + 1;
1021
1022 if ( labelMode )
1023 {
1024 var hiddenSearchResults = 0;
1025
1026 for ( var i = firstHiddenChild; i <= firstChild.hiddenEnd; i++ )
1027 {
1028 hiddenSearchResults += this.children[i].searchResults;
1029
1030 if ( this.children[i].magnitude == 0 )
1031 {
1032 hiddenChildren--;
1033 }
1034 }
1035
1036 if
1037 (
1038 selected &&
1039 (angleEnd - angleStart) *
1040 (gRadius + gRadius) >=
1041 minWidth() ||
1042 this == highlightedNode &&
1043 hiddenChildren ||
1044 hiddenSearchResults
1045 )
1046 {
1047 context.globalAlpha = this.alphaWedge.current();
1048
1049 this.drawHiddenLabel
1050 (
1051 angleStart,
1052 angleEnd,
1053 hiddenChildren,
1054 hiddenSearchResults
1055 );
1056 }
1057 }
1058
1059 var drawWedges = true;
1060
1061 for ( var i = firstHiddenChild; i <= firstChild.hiddenEnd; i++ )
1062 {
1063 // all hidden children must be completely hidden to draw together
1064
1065 if ( this.children[i].alphaPattern.current() != this.children[i].alphaWedge.current() )
1066 {
1067 drawWedges = false;
1068 break;
1069 }
1070 }
1071
1072 if ( labelMode )
1073 {
1074 if ( drawWedges )
1075 {
1076 var drawRadial = (angleEnd < this.angleEnd.current() + rotationOffset);
1077 this.drawLines(angleStart, angleEnd, radiusInner, drawRadial);
1078 }
1079
1080 if ( hiddenSearchResults && ! searchHighlighted )
1081 {
1082 drawWedge
1083 (
1084 angleStart,
1085 angleEnd,
1086 radiusInner,
1087 gRadius,//this.radiusOuter.current() * gRadius,
1088 highlightFill,
1089 0,
1090 true
1091 );
1092 }
1093 }
1094 else if ( drawWedges )
1095 {
1096 context.globalAlpha = this.alphaWedge.current();
1097
1098 var fill = rgbText
1099 (
1100 firstChild.r.current(),
1101 firstChild.g.current(),
1102 firstChild.b.current()
1103 );
1104
1105 drawWedge
1106 (
1107 angleStart,
1108 angleEnd,
1109 radiusInner,
1110 gRadius,//this.radiusOuter.current() * gRadius,
1111 fill,
1112 context.globalAlpha,
1113 false
1114 );
1115 }
1116
1117 return drawWedges;
1118 }
1119
1120 this.drawHiddenLabel = function(angleStart, angleEnd, value, hiddenSearchResults)
1121 {
1122 var textAngle = (angleStart + angleEnd) / 2;
1123 var labelRadius = gRadius + fontSize;//(radiusInner + radius) / 2;
1124
1125 drawTick(gRadius - fontSize * .75, fontSize * 1.5, textAngle);
1126 drawTextPolar
1127 (
1128 value.toString() + ' more',
1129 0, // inner text
1130 textAngle,
1131 labelRadius,
1132 true, // radial
1133 hiddenSearchResults, // bubble
1134 this == highlightedNode || this == focusNode, // bold
1135 false,
1136 hiddenSearchResults
1137 );
1138 }
1139
1140 this.drawHighlight = function(bold)
1141 {
1142 var angleStartCurrent = this.angleStart.current() + rotationOffset;
1143 var angleEndCurrent = this.angleEnd.current() + rotationOffset;
1144 var radiusInner = this.radiusInner.current() * gRadius;
1145
1146 //this.setHighlightStyle();
1147
1148 if ( this == focusNode && this == highlightedNode && this.hasChildren() )
1149 {
1150 // context.fillStyle = "rgba(255, 255, 255, .3)";
1151 arrow
1152 (
1153 angleStartCurrent,
1154 angleEndCurrent,
1155 radiusInner
1156 );
1157 }
1158 else
1159 {
1160 drawWedge
1161 (
1162 angleStartCurrent,
1163 angleEndCurrent,
1164 radiusInner,
1165 gRadius,
1166 highlightFill,
1167 0,
1168 true
1169 );
1170 }
1171
1172 // check if hidden children should be highlighted
1173 //
1174 for ( var i = 0; i < this.children.length; i++ )
1175 {
1176 if
1177 (
1178 this.children[i].getDepth() - selectedNode.getDepth() + 1 <=
1179 maxDisplayDepth &&
1180 this.children[i].hiddenEnd != null
1181 )
1182 {
1183 var firstChild = this.children[i];
1184 var lastChild = this.children[firstChild.hiddenEnd];
1185 var hiddenAngleStart = firstChild.angleStart.current() + rotationOffset;
1186 var hiddenAngleEnd = lastChild.angleEnd.current() + rotationOffset;
1187 var hiddenRadiusInner = gRadius * firstChild.radiusInner.current();
1188
1189 drawWedge
1190 (
1191 hiddenAngleStart,
1192 hiddenAngleEnd,
1193 hiddenRadiusInner,
1194 gRadius,
1195 'rgba(255, 255, 255, .3)',
1196 0,
1197 true
1198 );
1199
1200 if ( false && ! this.searchResults )
1201 {
1202 this.drawHiddenLabel
1203 (
1204 hiddenAngleStart,
1205 hiddenAngleEnd,
1206 firstChild.hiddenEnd - i + 1
1207 );
1208 }
1209
1210 i = firstChild.hiddenEnd;
1211 }
1212 }
1213
1214 // context.strokeStyle = 'black';
1215 context.fillStyle = 'black';
1216
1217 var highlight = ! ( progress < 1 && zoomOut && this == selectedNodeLast );
1218
1219 var angle = (angleEndCurrent + angleStartCurrent) / 2;
1220
1221 if ( ! (this.keyed && showKeys) )
1222 {
1223 this.drawLabel(angle, true, bold, true, this.radial);
1224 }
1225 }
1226
1227 this.drawHighlightCenter = function()
1228 {
1229 if ( ! this.canDisplayHistory() )
1230 {
1231 return;
1232 }
1233
1234 context.lineWidth = highlightLineWidth;
1235 context.strokeStyle = 'black';
1236 context.fillStyle = "rgba(255, 255, 255, .6)";
1237
1238 context.fillStyle = 'black';
1239 this.drawLabel(3 * Math.PI / 2, true, true, false);
1240 context.font = fontNormal;
1241 }
1242
1243 this.drawKey = function(angle, highlight, bold)
1244 {
1245 var offset = keyOffset();
1246 var color;
1247 var colorText = this.magnitude == 0 ? 'gray' : 'black';
1248 var patternAlpha = this.alphaPattern.end;
1249 var boxLeft = imageWidth - keySize - margin;
1250 var textY = offset + keySize / 2;
1251
1252 var label;
1253 var keyNameWidth;
1254
1255 if ( this == selectedNode )
1256 {
1257 color = colorUnclassified;
1258 label =
1259 this.getUnclassifiedText() +
1260 ' ' +
1261 this.getUnclassifiedPercentage();
1262 keyNameWidth = measureText(label, false);
1263 }
1264 else
1265 {
1266 label = this.keyLabel;
1267 color = rgbText(this.r.end, this.g.end, this.b.end);
1268
1269 if ( highlight )
1270 {
1271 if ( this.searchResultChildren() )
1272 {
1273 label = label + searchResultString(this.searchResultChildren());
1274 }
1275
1276 keyNameWidth = measureText(label, bold);
1277 }
1278 else
1279 {
1280 keyNameWidth = this.keyNameWidth;
1281 }
1282 }
1283
1284 var textLeft = boxLeft - keyBuffer - keyNameWidth - fontSize / 2;
1285 var labelLeft = textLeft;
1286
1287 if ( labelLeft > keyMinTextLeft - fontSize / 2 )
1288 {
1289 keyMinTextLeft -= fontSize / 2;
1290
1291 if ( keyMinTextLeft < centerX - gRadius + fontSize / 2 )
1292 {
1293 keyMinTextLeft = centerX - gRadius + fontSize / 2;
1294 }
1295
1296 labelLeft = keyMinTextLeft;
1297 }
1298
1299 var lineX = new Array();
1300 var lineY = new Array();
1301
1302 var bendRadius;
1303 var keyAngle = Math.atan((textY - centerY) / (labelLeft - centerX));
1304 var arcAngle;
1305
1306 if ( keyAngle < 0 )
1307 {
1308 keyAngle += Math.PI;
1309 }
1310
1311 if ( keyMinAngle == 0 || angle < keyMinAngle )
1312 {
1313 keyMinAngle = angle;
1314 }
1315
1316 if ( angle > Math.PI && keyMinAngle > Math.PI )
1317 {
1318 // allow lines to come underneath the chart
1319
1320 angle -= Math.PI * 2;
1321 }
1322
1323 lineX.push(Math.cos(angle) * gRadius);
1324 lineY.push(Math.sin(angle) * gRadius);
1325
1326 if ( angle < keyAngle && textY > centerY + Math.sin(angle) * (gRadius + buffer * (currentKey - 1) / (keys + 1) / 2 + buffer / 2) )
1327 {
1328 bendRadius = gRadius + buffer - buffer * currentKey / (keys + 1) / 2;
1329 }
1330 else
1331 {
1332 bendRadius = gRadius + buffer * currentKey / (keys + 1) / 2 + buffer / 2;
1333 }
1334
1335 var outside =
1336 Math.sqrt
1337 (
1338 Math.pow(labelLeft - centerX, 2) +
1339 Math.pow(textY - centerY, 2)
1340 ) > bendRadius;
1341
1342 if ( ! outside )
1343 {
1344 arcAngle = Math.asin((textY - centerY) / bendRadius);
1345
1346 keyMinTextLeft = min(keyMinTextLeft, centerX + bendRadius * Math.cos(arcAngle) - fontSize / 2);
1347
1348 if ( labelLeft < textLeft && textLeft > centerX + bendRadius * Math.cos(arcAngle) )
1349 {
1350 lineX.push(textLeft - centerX);
1351 lineY.push(textY - centerY);
1352 }
1353 }
1354 else
1355 {
1356 keyMinTextLeft = min(keyMinTextLeft, labelLeft - fontSize / 2);
1357
1358 if ( angle < keyAngle )
1359 {
1360 // flip everything over y = x
1361 //
1362 arcAngle = Math.PI / 2 - keyLineAngle
1363 (
1364 Math.PI / 2 - angle,
1365 Math.PI / 2 - keyAngle,
1366 bendRadius,
1367 textY - centerY,
1368 labelLeft - centerX,
1369 lineY,
1370 lineX
1371 );
1372
1373 }
1374 else
1375 {
1376 arcAngle = keyLineAngle
1377 (
1378 angle,
1379 keyAngle,
1380 bendRadius,
1381 labelLeft - centerX,
1382 textY - centerY,
1383 lineX,
1384 lineY
1385 );
1386 }
1387 }
1388
1389 if ( labelLeft > centerX + bendRadius * Math.cos(arcAngle) ||
1390 textY > centerY + bendRadius * Math.sin(arcAngle) + .01)
1391 // if ( outside || )
1392 {
1393 lineX.push(labelLeft - centerX);
1394 lineY.push(textY - centerY);
1395
1396 if ( textLeft != labelLeft )
1397 {
1398 lineX.push(textLeft - centerX);
1399 lineY.push(textY - centerY);
1400 }
1401 }
1402
1403 context.globalAlpha = this.alphaWedge.current();
1404
1405 if ( snapshotMode )
1406 {
1407 var labelSVG;
1408
1409 if ( this == selectedNode )
1410 {
1411 labelSVG =
1412 this.getUnclassifiedText() +
1413 spacer() +
1414 this.getUnclassifiedPercentage();
1415 }
1416 else
1417 {
1418 labelSVG = this.name + spacer() + this.getPercentage() + '%';
1419 }
1420
1421 svg +=
1422 '<rect fill="' + color + '" ' +
1423 'x="' + boxLeft + '" y="' + offset +
1424 '" width="' + keySize + '" height="' + keySize + '"/>';
1425
1426 if ( patternAlpha )
1427 {
1428 svg +=
1429 '<rect fill="url(#hiddenPattern)" style="stroke:none" ' +
1430 'x="' + boxLeft + '" y="' + offset +
1431 '" width="' + keySize + '" height="' + keySize + '"/>';
1432 }
1433
1434 svg +=
1435 '<path class="line' +
1436 (highlight ? ' highlight' : '') +
1437 '" d="M ' + (lineX[0] + centerX) + ',' +
1438 (lineY[0] + centerY);
1439
1440 if ( angle != arcAngle )
1441 {
1442 svg +=
1443 ' L ' + (centerX + bendRadius * Math.cos(angle)) + ',' +
1444 (centerY + bendRadius * Math.sin(angle)) +
1445 ' A ' + bendRadius + ',' + bendRadius + ' 0 ' +
1446 '0,' + (angle > arcAngle ? '0' : '1') + ' ' +
1447 (centerX + bendRadius * Math.cos(arcAngle)) + ',' +
1448 (centerY + bendRadius * Math.sin(arcAngle));
1449 }
1450
1451 for ( var i = 1; i < lineX.length; i++ )
1452 {
1453 svg +=
1454 ' L ' + (centerX + lineX[i]) + ',' +
1455 (centerY + lineY[i]);
1456 }
1457
1458 svg += '"/>';
1459
1460 if ( highlight )
1461 {
1462 if ( this.searchResultChildren() )
1463 {
1464 labelSVG = labelSVG + searchResultString(this.searchResultChildren());
1465 }
1466
1467 drawBubbleSVG
1468 (
1469 boxLeft - keyBuffer - keyNameWidth - fontSize / 2,
1470 textY - fontSize,
1471 keyNameWidth + fontSize,
1472 fontSize * 2,
1473 fontSize,
1474 0
1475 );
1476
1477 if ( this.isSearchResult )
1478 {
1479 drawSearchHighlights
1480 (
1481 label,
1482 boxLeft - keyBuffer - keyNameWidth,
1483 textY,
1484 0
1485 )
1486 }
1487 }
1488
1489 svg += svgText(labelSVG, boxLeft - keyBuffer, textY, 'end', bold, colorText);
1490 }
1491 else
1492 {
1493 context.fillStyle = color;
1494 context.translate(-centerX, -centerY);
1495 context.strokeStyle = 'black';
1496 context.globalAlpha = 1;//this.alphaWedge.current();
1497
1498 context.fillRect(boxLeft, offset, keySize, keySize);
1499
1500 if ( patternAlpha )
1501 {
1502 context.globalAlpha = patternAlpha;
1503 context.fillStyle = hiddenPattern;
1504
1505 // make clipping box for Firefox performance
1506 context.beginPath();
1507 context.moveTo(boxLeft, offset);
1508 context.lineTo(boxLeft + keySize, offset);
1509 context.lineTo(boxLeft + keySize, offset + keySize);
1510 context.lineTo(boxLeft, offset + keySize);
1511 context.closePath();
1512 context.save();
1513 context.clip();
1514
1515 context.fillRect(boxLeft, offset, keySize, keySize);
1516 context.fillRect(boxLeft, offset, keySize, keySize);
1517
1518 context.restore(); // remove clipping region
1519 }
1520
1521 if ( highlight )
1522 {
1523 this.setHighlightStyle();
1524 context.fillRect(boxLeft, offset, keySize, keySize);
1525 }
1526 else
1527 {
1528 context.lineWidth = thinLineWidth;
1529 }
1530
1531 context.strokeRect(boxLeft, offset, keySize, keySize);
1532
1533 if ( lineX.length )
1534 {
1535 context.beginPath();
1536 context.moveTo(lineX[0] + centerX, lineY[0] + centerY);
1537
1538 context.arc(centerX, centerY, bendRadius, angle, arcAngle, angle > arcAngle);
1539
1540 for ( var i = 1; i < lineX.length; i++ )
1541 {
1542 context.lineTo(lineX[i] + centerX, lineY[i] + centerY);
1543 }
1544
1545 context.globalAlpha = this == selectedNode ?
1546 this.children[0].alphaWedge.current() :
1547 this.alphaWedge.current();
1548 context.lineWidth = highlight ? highlightLineWidth : thinLineWidth;
1549 context.stroke();
1550 context.globalAlpha = 1;
1551 }
1552
1553 if ( highlight )
1554 {
1555 drawBubbleCanvas
1556 (
1557 boxLeft - keyBuffer - keyNameWidth - fontSize / 2,
1558 textY - fontSize,
1559 keyNameWidth + fontSize,
1560 fontSize * 2,
1561 fontSize,
1562 0
1563 );
1564
1565 if ( this.isSearchResult )
1566 {
1567 drawSearchHighlights
1568 (
1569 label,
1570 boxLeft - keyBuffer - keyNameWidth,
1571 textY,
1572 0
1573 )
1574 }
1575 }
1576
1577 drawText(label, boxLeft - keyBuffer, offset + keySize / 2, 0, 'end', bold, colorText);
1578
1579 context.translate(centerX, centerY);
1580 }
1581
1582 currentKey++;
1583 }
1584
1585 this.drawLabel = function(angle, bubble, bold, selected, radial)
1586 {
1587 if ( context.globalAlpha == 0 )
1588 {
1589 return;
1590 }
1591
1592 var innerText;
1593 var label;
1594 var radius;
1595
1596 if ( radial )
1597 {
1598 radius = (this.radiusInner.current() + 1) * gRadius / 2;
1599 }
1600 else
1601 {
1602 radius = this.labelRadius.current() * gRadius;
1603 }
1604
1605 if ( radial && (selected || bubble ) )
1606 {
1607 var percentage = this.getPercentage();
1608 innerText = percentage + '%';
1609 }
1610
1611 if
1612 (
1613 ! radial &&
1614 this != selectedNode &&
1615 ! bubble &&
1616 ( !zoomOut || this != selectedNodeLast)
1617 )
1618 {
1619 label = this.shortenLabel();
1620 }
1621 else
1622 {
1623 label = this.name;
1624 }
1625
1626 var flipped = drawTextPolar
1627 (
1628 label,
1629 innerText,
1630 angle,
1631 radius,
1632 radial,
1633 bubble,
1634 bold,
1635 // this.isSearchResult && this.shouldAddSearchResultsString() && (!selected || this == selectedNode || highlight),
1636 this.isSearchResult && (!selected || this == selectedNode || bubble),
1637 (this.hideAlone || !selected || this == selectedNode ) ? this.searchResultChildren() : 0
1638 );
1639
1640 var depth = this.getDepth() - selectedNode.getDepth() + 1;
1641
1642 if
1643 (
1644 ! radial &&
1645 ! bubble &&
1646 this != selectedNode &&
1647 this.angleEnd.end != this.angleStart.end &&
1648 nLabelOffsets[depth - 2] > 2 &&
1649 this.labelWidth.current() > (this.angleEnd.end - this.angleStart.end) * Math.abs(radius) &&
1650 ! ( zoomOut && this == selectedNodeLast ) &&
1651 this.labelRadius.end > 0
1652 )
1653 {
1654 // name extends beyond wedge; draw tick mark towards the central
1655 // radius for easier identification
1656
1657 var radiusCenter = compress ?
1658 (compressedRadii[depth - 1] + compressedRadii[depth - 2]) / 2 :
1659 (depth - .5) * nodeRadius;
1660
1661 if ( this.labelRadius.end > radiusCenter )
1662 {
1663 if ( flipped )
1664 {
1665 drawTick(radius - tickLength * 1.4 , tickLength, angle);
1666 }
1667 else
1668 {
1669 drawTick(radius - tickLength * 1.7, tickLength, angle);
1670 }
1671 }
1672 else
1673 {
1674 if ( flipped )
1675 {
1676 drawTick(radius + tickLength * .7, tickLength, angle);
1677 }
1678 else
1679 {
1680 drawTick(radius + tickLength * .4, tickLength, angle);
1681 }
1682 }
1683 }
1684 }
1685
1686 this.drawLines = function(angleStart, angleEnd, radiusInner, drawRadial, selected)
1687 {
1688 if ( snapshotMode )
1689 {
1690 if ( this != selectedNode)
1691 {
1692 if ( angleEnd == angleStart + Math.PI * 2 )
1693 {
1694 // fudge to prevent overlap, which causes arc ambiguity
1695 //
1696 angleEnd -= .1 / gRadius;
1697 }
1698
1699 var longArc = angleEnd - angleStart > Math.PI ? 1 : 0;
1700
1701 var x1 = centerX + radiusInner * Math.cos(angleStart);
1702 var y1 = centerY + radiusInner * Math.sin(angleStart);
1703
1704 var x2 = centerX + gRadius * Math.cos(angleStart);
1705 var y2 = centerY + gRadius * Math.sin(angleStart);
1706
1707 var x3 = centerX + gRadius * Math.cos(angleEnd);
1708 var y3 = centerY + gRadius * Math.sin(angleEnd);
1709
1710 var x4 = centerX + radiusInner * Math.cos(angleEnd);
1711 var y4 = centerY + radiusInner * Math.sin(angleEnd);
1712
1713 if ( this.alphaArc.end )
1714 {
1715 var dArray =
1716 [
1717 " M ", x4, ",", y4,
1718 " A ", radiusInner, ",", radiusInner, " 0 ", longArc,
1719 " 0 ", x1, ",", y1
1720 ];
1721
1722 svg += '<path class="line" d="' + dArray.join('') + '"/>';
1723 }
1724
1725 if ( drawRadial && this.alphaLine.end )
1726 {
1727 svg += '<line x1="' + x3 + '" y1="' + y3 + '" x2="' + x4 + '" y2="' + y4 + '"/>';
1728 }
1729 }
1730 }
1731 else
1732 {
1733 context.lineWidth = thinLineWidth;
1734 context.strokeStyle = 'black';
1735 context.beginPath();
1736 context.arc(0, 0, radiusInner, angleStart, angleEnd, false);
1737 context.globalAlpha = this.alphaArc.current();
1738 context.stroke();
1739
1740 if ( drawRadial )
1741 {
1742 var x1 = radiusInner * Math.cos(angleEnd);
1743 var y1 = radiusInner * Math.sin(angleEnd);
1744 var x2 = gRadius * Math.cos(angleEnd);
1745 var y2 = gRadius * Math.sin(angleEnd);
1746
1747 context.beginPath();
1748 context.moveTo(x1, y1);
1749 context.lineTo(x2, y2);
1750
1751 // if ( this.getCollapse() )//( selected && this != selectedNode )
1752 {
1753 context.globalAlpha = this.alphaLine.current();
1754 }
1755
1756 context.stroke();
1757 }
1758 }
1759 }
1760
1761 this.drawMap = function(child)
1762 {
1763 if ( this.parent )
1764 {
1765 this.parent.drawMap(child);
1766 }
1767
1768 if ( this.getCollapse() && this != child || this == focusNode )
1769 {
1770 return;
1771 }
1772
1773 var angleStart =
1774 (child.baseMagnitude - this.baseMagnitude) / this.magnitude * Math.PI * 2 +
1775 rotationOffset;
1776 var angleEnd =
1777 (child.baseMagnitude - this.baseMagnitude + child.magnitude) /
1778 this.magnitude * Math.PI * 2 +
1779 rotationOffset;
1780
1781 var box = this.getMapPosition();
1782
1783 context.save();
1784 context.fillStyle = 'black';
1785 context.textAlign = 'end';
1786 context.textBaseline = 'middle';
1787
1788 var textX = box.x - mapRadius - mapBuffer;
1789 var percentage = getPercentage(child.magnitude / this.magnitude);
1790
1791 var highlight = this == selectedNode || this == highlightedNode;
1792
1793 if ( highlight )
1794 {
1795 context.font = fontBold;
1796 }
1797 else
1798 {
1799 context.font = fontNormal;
1800 }
1801
1802 context.fillText(percentage + '% of', textX, box.y - mapRadius / 3);
1803 context.fillText(this.name, textX, box.y + mapRadius / 3);
1804
1805 if ( highlight )
1806 {
1807 context.font = fontNormal;
1808 }
1809
1810 if ( this == highlightedNode && this != selectedNode )
1811 {
1812 context.fillStyle = 'rgb(245, 245, 245)';
1813 // context.fillStyle = 'rgb(200, 200, 200)';
1814 }
1815 else
1816 {
1817 context.fillStyle = 'rgb(255, 255, 255)';
1818 }
1819
1820 context.beginPath();
1821 context.arc(box.x, box.y, mapRadius, 0, Math.PI * 2, true);
1822 context.closePath();
1823 context.fill();
1824
1825 if ( this == selectedNode )
1826 {
1827 context.lineWidth = 1;
1828 context.fillStyle = 'rgb(100, 100, 100)';
1829 }
1830 else
1831 {
1832 if ( this == highlightedNode )
1833 {
1834 context.lineWidth = .2;
1835 context.fillStyle = 'rgb(190, 190, 190)';
1836 }
1837 else
1838 {
1839 context.lineWidth = .2;
1840 context.fillStyle = 'rgb(200, 200, 200)';
1841 }
1842 }
1843
1844 var maxDepth = this.getMaxDepth();
1845
1846 if ( ! compress && maxDepth > maxPossibleDepth + this.getDepth() - 1 )
1847 {
1848 maxDepth = maxPossibleDepth + this.getDepth() - 1;
1849 }
1850
1851 if ( this.getDepth() < selectedNode.getDepth() )
1852 {
1853 if ( child.getDepth() - 1 >= maxDepth )
1854 {
1855 maxDepth = child.getDepth();
1856 }
1857 }
1858
1859 var radiusInner;
1860
1861 if ( compress )
1862 {
1863 radiusInner = 0;
1864 // Math.atan(child.getDepth() - this.getDepth()) /
1865 // Math.PI * 2 * .9;
1866 }
1867 else
1868 {
1869 radiusInner =
1870 (child.getDepth() - this.getDepth()) /
1871 (maxDepth - this.getDepth() + 1);
1872 }
1873
1874 context.stroke();
1875 context.beginPath();
1876
1877 if ( radiusInner == 0 )
1878 {
1879 context.moveTo(box.x, box.y);
1880 }
1881 else
1882 {
1883 context.arc(box.x, box.y, mapRadius * radiusInner, angleEnd, angleStart, true);
1884 }
1885
1886 context.arc(box.x, box.y, mapRadius, angleStart, angleEnd, false);
1887 context.closePath();
1888 context.fill();
1889
1890 if ( this == highlightedNode && this != selectedNode )
1891 {
1892 context.lineWidth = 1;
1893 context.stroke();
1894 }
1895
1896 context.restore();
1897 }
1898
1899 this.drawReferenceRings = function(childRadiusInner)
1900 {
1901 if ( snapshotMode )
1902 {
1903 svg +=
1904 '<circle cx="' + centerX + '" cy="' + centerY +
1905 '" r="' + childRadiusInner + '"/>';
1906 svg +=
1907 '<circle cx="' + centerX + '" cy="' + centerY +
1908 '" r="' + gRadius + '"/>';
1909 }
1910 else
1911 {
1912 context.globalAlpha = 1 - this.alphaLine.current();//this.getUncollapsed().alphaLine.current();
1913 context.beginPath();
1914 context.arc(0, 0, childRadiusInner, 0, Math.PI * 2, false);
1915 context.stroke();
1916 context.beginPath();
1917 context.arc(0, 0, gRadius, 0, Math.PI * 2, false);
1918 context.stroke();
1919 }
1920 }
1921
1922 this.getCollapse = function()
1923 {
1924 return (
1925 collapse &&
1926 this.collapse &&
1927 this.depth != maxAbsoluteDepth
1928 );
1929 }
1930
1931 this.getDepth = function()
1932 {
1933 if ( collapse )
1934 {
1935 return this.depthCollapsed;
1936 }
1937 else
1938 {
1939 return this.depth;
1940 }
1941 }
1942
1943 this.getMagnitude = function()
1944 {
1945 return this.attributes[magnitudeIndex][currentDataset];
1946 }
1947
1948 this.getMapPosition = function()
1949 {
1950 return {
1951 x : (details.offsetLeft + details.clientWidth - mapRadius),
1952 y : ((focusNode.getDepth() - this.getDepth()) *
1953 (mapBuffer + mapRadius * 2) - mapRadius) +
1954 details.clientHeight + details.offsetTop
1955 };
1956 }
1957
1958 this.getMaxDepth = function(limit)
1959 {
1960 var max;
1961
1962 if ( collapse )
1963 {
1964 return this.maxDepthCollapsed;
1965 }
1966 else
1967 {
1968 if ( this.maxDepth > maxAbsoluteDepth )
1969 {
1970 return maxAbsoluteDepth;
1971 }
1972 else
1973 {
1974 return this.maxDepth;
1975 }
1976 }
1977 }
1978
1979 this.getData = function(index, summary)
1980 {
1981 var files = new Array();
1982
1983 if
1984 (
1985 this.attributes[index] != null &&
1986 this.attributes[index][currentDataset] != null &&
1987 this.attributes[index][currentDataset] != ''
1988 )
1989 {
1990 files.push
1991 (
1992 document.location +
1993 '.files/' +
1994 this.attributes[index][currentDataset]
1995 );
1996 }
1997
1998 if ( summary )
1999 {
2000 for ( var i = 0; i < this.children.length; i++ )
2001 {
2002 files = files.concat(this.children[i].getData(index, true));
2003 }
2004 }
2005
2006 return files;
2007 }
2008
2009 this.getList = function(index, summary)
2010 {
2011 var list;
2012
2013 if
2014 (
2015 this.attributes[index] != null &&
2016 this.attributes[index][currentDataset] != null
2017 )
2018 {
2019 list = this.attributes[index][currentDataset];
2020 }
2021 else
2022 {
2023 list = new Array();
2024 }
2025
2026 if ( summary )
2027 {
2028 for ( var i = 0; i < this.children.length; i++ )
2029 {
2030 list = list.concat(this.children[i].getList(index, true));
2031 }
2032 }
2033
2034 return list;
2035 }
2036
2037 this.getParent = function()
2038 {
2039 // returns parent, accounting for collapsing or 0 if doesn't exist
2040
2041 var parent = this.parent;
2042
2043 while ( parent != 0 && parent.getCollapse() )
2044 {
2045 parent = parent.parent;
2046 }
2047
2048 return parent;
2049 }
2050
2051 this.getPercentage = function()
2052 {
2053 return getPercentage(this.magnitude / selectedNode.magnitude);
2054 }
2055
2056 this.getUnclassifiedPercentage = function()
2057 {
2058 var lastChild = this.children[this.children.length - 1];
2059
2060 return getPercentage
2061 (
2062 (
2063 this.baseMagnitude +
2064 this.magnitude -
2065 lastChild.magnitude -
2066 lastChild.baseMagnitude
2067 ) / this.magnitude
2068 ) + '%';
2069 }
2070
2071 this.getUnclassifiedText = function()
2072 {
2073 return '[unassigned '+ this.name + ']';
2074 }
2075
2076 this.getUncollapsed = function()
2077 {
2078 // recurse through collapsed children until uncollapsed node is found
2079
2080 if ( this.getCollapse() )
2081 {
2082 return this.children[0].getUncollapsed();
2083 }
2084 else
2085 {
2086 return this;
2087 }
2088 }
2089
2090 this.hasChildren = function()
2091 {
2092 return this.children.length && this.depth < maxAbsoluteDepth && this.magnitude;
2093 }
2094
2095 this.hasParent = function(parent)
2096 {
2097 if ( this.parent )
2098 {
2099 if ( this.parent == parent )
2100 {
2101 return true;
2102 }
2103 else
2104 {
2105 return this.parent.hasParent(parent);
2106 }
2107 }
2108 else
2109 {
2110 return false;
2111 }
2112 }
2113
2114 this.maxVisibleDepth = function(maxDepth)
2115 {
2116 var childInnerRadius;
2117 var depth = this.getDepth() - selectedNode.getDepth() + 1;
2118 var currentMaxDepth = depth;
2119
2120 if ( this.hasChildren() && depth < maxDepth)
2121 {
2122 var lastChild = this.children[this.children.length - 1];
2123
2124 if ( this.name == 'Pseudomonadaceae' )
2125 {
2126 var x = 3;
2127 }
2128
2129 if
2130 (
2131 lastChild.baseMagnitude + lastChild.magnitude <
2132 this.baseMagnitude + this.magnitude
2133 )
2134 {
2135 currentMaxDepth++;
2136 }
2137
2138 if ( compress )
2139 {
2140 childInnerRadius = compressedRadii[depth - 1];
2141 }
2142 else
2143 {
2144 childInnerRadius = (depth) / maxDepth;
2145 }
2146
2147 for ( var i = 0; i < this.children.length; i++ )
2148 {
2149 if
2150 (//true ||
2151 this.children[i].magnitude *
2152 angleFactor *
2153 (childInnerRadius + 1) *
2154 gRadius >=
2155 minWidth()
2156 )
2157 {
2158 var childMaxDepth = this.children[i].maxVisibleDepth(maxDepth);
2159
2160 if ( childMaxDepth > currentMaxDepth )
2161 {
2162 currentMaxDepth = childMaxDepth;
2163 }
2164 }
2165 }
2166 }
2167
2168 return currentMaxDepth;
2169 }
2170
2171 this.resetLabelWidth = function()
2172 {
2173 var nameWidthOld = this.nameWidth;
2174
2175 if ( ! this.radial )//&& fontSize != fontSizeLast )
2176 {
2177 var dim = context.measureText(this.name);
2178 this.nameWidth = dim.width;
2179 }
2180
2181 if ( fontSize != fontSizeLast && this.labelWidth.end == nameWidthOld * labelWidthFudge )
2182 {
2183 // font size changed; adjust start of tween to match
2184
2185 this.labelWidth.start = this.nameWidth * labelWidthFudge;
2186 }
2187 else
2188 {
2189 this.labelWidth.start = this.labelWidth.current();
2190 }
2191
2192 this.labelWidth.end = this.nameWidth * labelWidthFudge;
2193 }
2194
2195 this.restrictLabelWidth = function(width)
2196 {
2197 if ( width < this.labelWidth.end )
2198 {
2199 this.labelWidth.end = width;
2200 }
2201 }
2202
2203 this.search = function()
2204 {
2205 this.isSearchResult = false;
2206 this.searchResults = 0;
2207
2208 if
2209 (
2210 ! this.getCollapse() &&
2211 search.value != '' &&
2212 this.name.toLowerCase().indexOf(search.value.toLowerCase()) != -1
2213 )
2214 {
2215 this.isSearchResult = true;
2216 this.searchResults = 1;
2217 nSearchResults++;
2218 }
2219
2220 for ( var i = 0; i < this.children.length; i++ )
2221 {
2222 this.searchResults += this.children[i].search();
2223 }
2224
2225 return this.searchResults;
2226 }
2227
2228 this.searchResultChildren = function()
2229 {
2230 if ( this.isSearchResult )
2231 {
2232 return this.searchResults - 1;
2233 }
2234 else
2235 {
2236 return this.searchResults;
2237 }
2238 }
2239
2240 this.setDepth = function(depth, depthCollapsed)
2241 {
2242 this.depth = depth;
2243 this.depthCollapsed = depthCollapsed;
2244
2245 if
2246 (
2247 this.children.length == 1 &&
2248 // this.magnitude > 0 &&
2249 this.children[0].magnitude == this.magnitude &&
2250 ( head.children.length > 1 || this.children[0].children.length )
2251 )
2252 {
2253 this.collapse = true;
2254 }
2255 else
2256 {
2257 this.collapse = false;
2258 depthCollapsed++;
2259 }
2260
2261 for ( var i = 0; i < this.children.length; i++ )
2262 {
2263 this.children[i].setDepth(depth + 1, depthCollapsed);
2264 }
2265 }
2266
2267 this.setHighlightStyle = function()
2268 {
2269 context.lineWidth = highlightLineWidth;
2270
2271 if ( this.hasChildren() || this != focusNode || this != highlightedNode )
2272 {
2273 context.strokeStyle = 'black';
2274 context.fillStyle = "rgba(255, 255, 255, .3)";
2275 }
2276 else
2277 {
2278 context.strokeStyle = 'rgb(90,90,90)';
2279 context.fillStyle = "rgba(155, 155, 155, .3)";
2280 }
2281 }
2282
2283 this.setLabelWidth = function(node)
2284 {
2285 if ( ! shorten || this.radial )
2286 {
2287 return; // don't need to set width
2288 }
2289
2290 if ( node.hide )
2291 {
2292 alert('wtf');
2293 return;
2294 }
2295
2296 var angle = (this.angleStart.end + this.angleEnd.end) / 2;
2297 var a; // angle difference
2298
2299 if ( node == selectedNode )
2300 {
2301 a = Math.abs(angle - node.angleOther);
2302 }
2303 else
2304 {
2305 a = Math.abs(angle - (node.angleStart.end + node.angleEnd.end) / 2);
2306 }
2307
2308 if ( a == 0 )
2309 {
2310 return;
2311 }
2312
2313 if ( a > Math.PI )
2314 {
2315 a = 2 * Math.PI - a;
2316 }
2317
2318 if ( node.radial || node == selectedNode )
2319 {
2320 var nodeLabelRadius;
2321
2322 if ( node == selectedNode )
2323 {
2324 // radial 'other' label
2325
2326 nodeLabelRadius = (node.children[0].radiusInner.end + 1) / 2;
2327 }
2328 else
2329 {
2330 nodeLabelRadius = (node.radiusInner.end + 1) / 2;
2331 }
2332
2333 if ( a < Math.PI / 2 )
2334 {
2335 var r = this.labelRadius.end * gRadius + .5 * fontSize
2336 var hypotenuse = r / Math.cos(a);
2337 var opposite = r * Math.tan(a);
2338 var fontRadius = .8 * fontSize;
2339
2340 if
2341 (
2342 nodeLabelRadius * gRadius < hypotenuse &&
2343 this.labelWidth.end / 2 + fontRadius > opposite
2344 )
2345 {
2346 this.labelWidth.end = 2 * (opposite - fontRadius);
2347 }
2348 }
2349 }
2350 else if
2351 (
2352 this.labelRadius.end == node.labelRadius.end &&
2353 a < Math.PI / 4
2354 )
2355 {
2356 // same radius with small angle; use circumferential approximation
2357
2358 var dist = a * this.labelRadius.end * gRadius - fontSize * (1 - a * 4 / Math.PI) * 1.3;
2359
2360 if ( this.labelWidth.end < dist )
2361 {
2362 node.restrictLabelWidth((dist - this.labelWidth.end / 2) * 2);
2363 }
2364 else if ( node.labelWidth.end < dist )
2365 {
2366 this.restrictLabelWidth((dist - node.labelWidth.end / 2) * 2);
2367 }
2368 else
2369 {
2370 // both labels reach halfway point; restrict both
2371
2372 this.labelWidth.end = dist;
2373 node.labelWidth.end = dist
2374 }
2375 }
2376 else
2377 {
2378 var r1 = this.labelRadius.end * gRadius;
2379 var r2 = node.labelRadius.end * gRadius;
2380
2381 // first adjust the radii to account for the height of the font by shifting them
2382 // toward each other
2383 //
2384 var fontFudge = .35 * fontSize;
2385 //
2386 if ( this.labelRadius.end < node.labelRadius.end )
2387 {
2388 r1 += fontFudge;
2389 r2 -= fontFudge;
2390 }
2391 else if ( this.labelRadius.end > node.labelRadius.end )
2392 {
2393 r1 -= fontFudge;
2394 r2 += fontFudge;
2395 }
2396 else
2397 {
2398 r1 -= fontFudge;
2399 r2 -= fontFudge;
2400 }
2401
2402 var r1s = r1 * r1;
2403 var r2s = r2 * r2;
2404
2405 // distance between the centers of the two labels
2406 //
2407 var dist = Math.sqrt(r1s + r2s - 2 * r1 * r2 * Math.cos(a));
2408
2409 // angle at our label center between our radius and the line to the other label center
2410 //
2411 var b = Math.acos((r1s + dist * dist - r2s) / (2 * r1 * dist));
2412
2413 // distance from our label center to the intersection of the two tangents
2414 //
2415 var l1 = Math.sin(a + b - Math.PI / 2) * dist / Math.sin(Math.PI - a);
2416
2417 // distance from other label center the the intersection of the two tangents
2418 //
2419 var l2 = Math.sin(Math.PI / 2 - b) * dist / Math.sin(Math.PI - a);
2420
2421 l1 = Math.abs(l1) - .4 * fontSize;
2422 l2 = Math.abs(l2) - .4 * fontSize;
2423 /*
2424 // amount to shorten the distances because of the height of the font
2425 //
2426 var l3 = 0;
2427 var fontRadius = fontSize * .55;
2428 //
2429 if ( l1 < 0 || l2 < 0 )
2430 {
2431 var l4 = fontRadius / Math.tan(a);
2432 l1 = Math.abs(l1);
2433 l2 = Math.abs(l2);
2434
2435 l1 -= l4;
2436 l2 -= l4;
2437 }
2438 else
2439 {
2440 var c = Math.PI - a;
2441
2442 l3 = fontRadius * Math.tan(c / 2);
2443 }
2444 */
2445 if ( this.labelWidth.end / 2 > l1 && node.labelWidth.end / 2 > l2 )
2446 {
2447 // shorten the farthest one from the intersection
2448
2449 if ( l1 > l2 )
2450 {
2451 this.restrictLabelWidth(2 * (l1));// - l3 - fontRadius));
2452 }
2453 else
2454 {
2455 node.restrictLabelWidth(2 * (l2));// - l3 - fontRadius));
2456 }
2457 }/*
2458 else if ( this.labelWidth.end / 2 > l1 + l3 && node.labelWidth.end / 2 > l2 - l3 )
2459 {
2460 node.restrictLabelWidth(2 * (l2 - l3));
2461 }
2462 else if ( this.labelWidth.end / 2 > l1 - l3 && node.labelWidth.end / 2 > l2 + l3 )
2463 {
2464 this.restrictLabelWidth(2 * (l1 - l3));
2465 }*/
2466 }
2467 }
2468
2469 this.setMagnitudes = function(baseMagnitude)
2470 {
2471 this.magnitude = this.getMagnitude();
2472 this.baseMagnitude = baseMagnitude;
2473
2474 for ( var i = 0; i < this.children.length; i++ )
2475 {
2476 this.children[i].setMagnitudes(baseMagnitude);
2477 baseMagnitude += this.children[i].magnitude;
2478 }
2479
2480 this.maxChildMagnitude = baseMagnitude;
2481 }
2482
2483 this.setMaxDepths = function()
2484 {
2485 this.maxDepth = this.depth;
2486 this.maxDepthCollapsed = this.depthCollapsed;
2487
2488 for ( i in this.children )
2489 {
2490 var child = this.children[i];
2491
2492 child.setMaxDepths();
2493
2494 if ( child.maxDepth > this.maxDepth )
2495 {
2496 this.maxDepth = child.maxDepth;
2497 }
2498
2499 if
2500 (
2501 child.maxDepthCollapsed > this.maxDepthCollapsed &&
2502 (child.depth <= maxAbsoluteDepth || maxAbsoluteDepth == 0)
2503 )
2504 {
2505 this.maxDepthCollapsed = child.maxDepthCollapsed;
2506 }
2507 }
2508 }
2509
2510 this.setTargetLabelRadius = function()
2511 {
2512 var depth = this.getDepth() - selectedNode.getDepth() + 1;
2513 var index = depth - 2;
2514 var labelOffset = labelOffsets[index];
2515
2516 if ( this.radial )
2517 {
2518 //this.labelRadius.setTarget((this.radiusInner.end + 1) / 2);
2519 var max =
2520 depth == maxDisplayDepth ?
2521 1 :
2522 compressedRadii[index + 1];
2523
2524 this.labelRadius.setTarget((compressedRadii[index] + max) / 2);
2525 }
2526 else
2527 {
2528 var radiusCenter;
2529 var width;
2530
2531 if ( compress )
2532 {
2533 if ( nLabelOffsets[index] > 1 )
2534 {
2535 this.labelRadius.setTarget
2536 (
2537 lerp
2538 (
2539 labelOffset + .75,
2540 0,
2541 nLabelOffsets[index] + .5,
2542 compressedRadii[index],
2543 compressedRadii[index + 1]
2544 )
2545 );
2546 }
2547 else
2548 {
2549 this.labelRadius.setTarget((compressedRadii[index] + compressedRadii[index + 1]) / 2);
2550 }
2551 }
2552 else
2553 {
2554 radiusCenter =
2555 nodeRadius * (depth - 1) +
2556 nodeRadius / 2;
2557 width = nodeRadius;
2558
2559 this.labelRadius.setTarget
2560 (
2561 radiusCenter + width * ((labelOffset + 1) / (nLabelOffsets[index] + 1) - .5)
2562 );
2563 }
2564 }
2565
2566 if ( ! this.hide && ! this.keyed && nLabelOffsets[index] )
2567 {
2568 // check last and first labels in each track for overlap
2569
2570 for ( var i = 0; i < maxDisplayDepth - 1; i++ )
2571 {
2572 for ( var j = 0; j <= nLabelOffsets[i]; j++ )
2573 {
2574 var last = labelLastNodes[i][j];
2575 var first = labelFirstNodes[i][j];
2576
2577 if ( last )
2578 {
2579 if ( j == nLabelOffsets[i] )
2580 {
2581 // last is radial
2582 this.setLabelWidth(last);
2583 }
2584 else
2585 {
2586 last.setLabelWidth(this);
2587 }
2588 }
2589
2590 if ( first )
2591 {
2592 if ( j == nLabelOffsets[i] )
2593 {
2594 this.setLabelWidth(first);
2595 }
2596 else
2597 {
2598 first.setLabelWidth(this);
2599 }
2600 }
2601 }
2602 }
2603
2604 if ( selectedNode.canDisplayLabelOther )
2605 {
2606 this.setLabelWidth(selectedNode); // in case there is an 'other' label
2607 }
2608
2609 if ( this.radial )
2610 {
2611 // use the last 'track' of this depth for radial
2612
2613 labelLastNodes[index][nLabelOffsets[index]] = this;
2614
2615 if ( labelFirstNodes[index][nLabelOffsets[index]] == 0 )
2616 {
2617 labelFirstNodes[index][nLabelOffsets[index]] = this;
2618 }
2619 }
2620 else
2621 {
2622 labelLastNodes[index][labelOffset] = this;
2623
2624 // update offset
2625
2626 labelOffsets[index] += 1;
2627
2628 if ( labelOffsets[index] > nLabelOffsets[index] )
2629 {
2630 labelOffsets[index] -= nLabelOffsets[index];
2631
2632 if ( !(nLabelOffsets[index] & 1) )
2633 {
2634 labelOffsets[index]--;
2635 }
2636 }
2637 else if ( labelOffsets[index] == nLabelOffsets[index] )
2638 {
2639 labelOffsets[index] -= nLabelOffsets[index];
2640
2641 if ( false && !(nLabelOffsets[index] & 1) )
2642 {
2643 labelOffsets[index]++;
2644 }
2645 }
2646
2647 if ( labelFirstNodes[index][labelOffset] == 0 )
2648 {
2649 labelFirstNodes[index][labelOffset] = this;
2650 }
2651 }
2652 }
2653 else if ( this.hide )
2654 {
2655 this.labelWidth.end = 0;
2656 }
2657 }
2658
2659 this.setTargets = function()
2660 {
2661 if ( this == selectedNode )
2662 {
2663 this.setTargetsSelected
2664 (
2665 0,
2666 1,
2667 lightnessBase,
2668 false,
2669 false
2670 );
2671 return;
2672 }
2673
2674 var depthRelative = this.getDepth() - selectedNode.getDepth();
2675
2676 var parentOfSelected = selectedNode.hasParent(this);
2677 /* (
2678 // ! this.getCollapse() &&
2679 this.baseMagnitude <= selectedNode.baseMagnitude &&
2680 this.baseMagnitude + this.magnitude >=
2681 selectedNode.baseMagnitude + selectedNode.magnitude
2682 );
2683 */
2684 if ( parentOfSelected )
2685 {
2686 this.resetLabelWidth();
2687 }
2688 else
2689 {
2690 //context.font = fontNormal;
2691 var dim = context.measureText(this.name);
2692 this.nameWidth = dim.width;
2693 //this.labelWidth.setTarget(this.labelWidth.end);
2694 this.labelWidth.setTarget(0);
2695 }
2696
2697 // set angles
2698 //
2699 if ( this.baseMagnitude <= selectedNode.baseMagnitude )
2700 {
2701 this.angleStart.setTarget(0);
2702 }
2703 else
2704 {
2705 this.angleStart.setTarget(Math.PI * 2);
2706 }
2707 //
2708 if
2709 (
2710 parentOfSelected ||
2711 this.baseMagnitude + this.magnitude >=
2712 selectedNode.baseMagnitude + selectedNode.magnitude
2713 )
2714 {
2715 this.angleEnd.setTarget(Math.PI * 2);
2716 }
2717 else
2718 {
2719 this.angleEnd.setTarget(0);
2720 }
2721
2722 // children
2723 //
2724 for ( var i = 0; i < this.children.length; i++ )
2725 {
2726 this.children[i].setTargets();
2727 }
2728
2729 if ( this.getDepth() <= selectedNode.getDepth() )
2730 {
2731 // collapse in
2732
2733 this.radiusInner.setTarget(0);
2734
2735 if ( parentOfSelected )
2736 {
2737 this.labelRadius.setTarget
2738 (
2739 (depthRelative) *
2740 historySpacingFactor * fontSize / gRadius
2741 );
2742 //this.scale.setTarget(1 - (selectedNode.getDepth() - this.getDepth()) / 18); // TEMP
2743 }
2744 else
2745 {
2746 this.labelRadius.setTarget(0);
2747 //this.scale.setTarget(1); // TEMP
2748 }
2749 }
2750 else if ( depthRelative + 1 > maxDisplayDepth )
2751 {
2752 // collapse out
2753
2754 this.radiusInner.setTarget(1);
2755 this.labelRadius.setTarget(1);
2756 //this.scale.setTarget(1); // TEMP
2757 }
2758 else
2759 {
2760 // don't collapse
2761
2762 if ( compress )
2763 {
2764 this.radiusInner.setTarget(compressedRadii[depthRelative - 1]);
2765 }
2766 else
2767 {
2768 this.radiusInner.setTarget(nodeRadius * (depthRelative));
2769 }
2770
2771 //this.scale.setTarget(1); // TEMP
2772
2773 if ( this == selectedNode )
2774 {
2775 this.labelRadius.setTarget(0);
2776 }
2777 else
2778 {
2779 if ( compress )
2780 {
2781 this.labelRadius.setTarget
2782 (
2783 (compressedRadii[depthRelative - 1] + compressedRadii[depthRelative]) / 2
2784 );
2785 }
2786 else
2787 {
2788 this.labelRadius.setTarget(nodeRadius * (depthRelative) + nodeRadius / 2);
2789 }
2790 }
2791 }
2792
2793 // this.r.start = this.r.end;
2794 // this.g.start = this.g.end;
2795 // this.b.start = this.b.end;
2796
2797 this.r.setTarget(255);
2798 this.g.setTarget(255);
2799 this.b.setTarget(255);
2800
2801 this.alphaLine.setTarget(0);
2802 this.alphaArc.setTarget(0);
2803 this.alphaWedge.setTarget(0);
2804 this.alphaPattern.setTarget(0);
2805 this.alphaOther.setTarget(0);
2806
2807 if ( parentOfSelected && ! this.getCollapse() )
2808 {
2809 var alpha =
2810 (
2811 1 -
2812 (selectedNode.getDepth() - this.getDepth()) /
2813 (Math.floor((compress ? compressedRadii[0] : nodeRadius) * gRadius / (historySpacingFactor * fontSize) - .5) + 1)
2814 );
2815
2816 if ( alpha < 0 )
2817 {
2818 alpha = 0;
2819 }
2820
2821 this.alphaLabel.setTarget(alpha);
2822 this.radial = false;
2823 }
2824 else
2825 {
2826 this.alphaLabel.setTarget(0);
2827 }
2828
2829 this.hideAlonePrev = this.hideAlone;
2830 this.hidePrev = this.hide;
2831
2832 if ( parentOfSelected )
2833 {
2834 this.hideAlone = false;
2835 this.hide = false;
2836 }
2837
2838 if ( this.getParent() == selectedNode.getParent() )
2839 {
2840 this.hiddenEnd = null;
2841 }
2842
2843 this.radialPrev = this.radial;
2844 }
2845
2846 this.setTargetsSelected = function(hueMin, hueMax, lightness, hide, nextSiblingHidden)
2847 {
2848 var collapse = this.getCollapse();
2849 var depth = this.getDepth() - selectedNode.getDepth() + 1;
2850 var canDisplayChildLabels = false;
2851 var lastChild;
2852
2853 if ( this.hasChildren() )//&& ! hide )
2854 {
2855 lastChild = this.children[this.children.length - 1];
2856 this.hideAlone = true;
2857 }
2858 else
2859 {
2860 this.hideAlone = false;
2861 }
2862
2863 // set child wedges
2864 //
2865 for ( var i = 0; i < this.children.length; i++ )
2866 {
2867 this.children[i].setTargetWedge();
2868
2869 if
2870 (
2871 ! this.children[i].hide &&
2872 ( collapse || depth < maxDisplayDepth ) &&
2873 this.depth < maxAbsoluteDepth
2874 )
2875 {
2876 canDisplayChildLabels = true;
2877 this.hideAlone = false;
2878 }
2879 }
2880
2881 if ( this == selectedNode || lastChild && lastChild.angleEnd.end < this.angleEnd.end - .01)
2882 {
2883 this.hideAlone = false;
2884 }
2885
2886 if ( this.hideAlonePrev == undefined )
2887 {
2888 this.hideAlonePrev = this.hideAlone;
2889 }
2890
2891 if ( this == selectedNode )
2892 {
2893 var otherArc =
2894 angleFactor *
2895 (
2896 this.baseMagnitude + this.magnitude -
2897 lastChild.baseMagnitude - lastChild.magnitude
2898 );
2899 this.canDisplayLabelOther =
2900 otherArc *
2901 (this.children[0].radiusInner.end + 1) * gRadius >=
2902 minWidth();
2903
2904 this.keyUnclassified = false;
2905
2906 if ( this.canDisplayLabelOther )
2907 {
2908 this.angleOther = Math.PI * 2 - otherArc / 2;
2909 }
2910 else if ( otherArc > 0.0000000001 )
2911 {
2912 this.keyUnclassified = true;
2913 keys++;
2914 }
2915
2916 this.angleStart.setTarget(0);
2917 this.angleEnd.setTarget(Math.PI * 2);
2918 this.radiusInner.setTarget(0);
2919 this.hidePrev = this.hide;
2920 this.hide = false;
2921 this.hideAlonePrev = this.hideAlone;
2922 this.hideAlone = false;
2923 this.keyed = false;
2924 }
2925
2926 if ( hueMax - hueMin > 1 / 12 )
2927 {
2928 hueMax = hueMin + 1 / 12;
2929 }
2930
2931 // set lightness
2932 //
2933 if ( ! ( hide || this.hideAlone ) )
2934 {
2935 if ( useHue() )
2936 {
2937 lightness = (lightnessBase + lightnessMax) / 2;
2938 }
2939 else
2940 {
2941 lightness = lightnessBase + (depth - 1) * lightnessFactor;
2942
2943 if ( lightness > lightnessMax )
2944 {
2945 lightness = lightnessMax;
2946 }
2947 }
2948 }
2949
2950 if ( hide )
2951 {
2952 this.hide = true;
2953 }
2954
2955 if ( this.hidePrev == undefined )
2956 {
2957 this.hidePrev = this.hide;
2958 }
2959
2960 var hiddenStart = -1;
2961 var hiddenHueNumer = 0;
2962 var hiddenHueDenom = 0;
2963 var i = 0;
2964
2965 if ( ! this.hide )
2966 {
2967 this.hiddenEnd = null;
2968 }
2969
2970 while ( true )
2971 {
2972 if ( ! this.hideAlone && ! hide && ( i == this.children.length || ! this.children[i].hide ) )
2973 {
2974 // reached a non-hidden child or the end; set targets for
2975 // previous group of hidden children (if any) using their
2976 // average hue
2977
2978 if ( hiddenStart != -1 )
2979 {
2980 var hiddenHue = hiddenHueDenom ? hiddenHueNumer / hiddenHueDenom : hueMin;
2981
2982 for ( var j = hiddenStart; j < i; j++ )
2983 {
2984 this.children[j].setTargetsSelected
2985 (
2986 hiddenHue,
2987 null,
2988 lightness,
2989 false,
2990 j < i - 1
2991 );
2992
2993 this.children[j].hiddenEnd = null;
2994 }
2995
2996 this.children[hiddenStart].hiddenEnd = i - 1;
2997 }
2998 }
2999
3000 if ( i == this.children.length )
3001 {
3002 break;
3003 }
3004
3005 var child = this.children[i];
3006 var childHueMin;
3007 var childHueMax;
3008
3009 if ( this.magnitude > 0 && ! this.hide && ! this.hideAlone )
3010 {
3011 if ( useHue() )
3012 {
3013 childHueMin = child.hues[currentDataset];
3014 }
3015 else if ( this == selectedNode )
3016 {
3017 var min = 0.0;
3018 var max = 1.0;
3019
3020 if ( this.children.length > 6 )
3021 {
3022 childHueMin = lerp((1 - Math.pow(1 - i / this.children.length, 1.4)) * .95, 0, 1, min, max);
3023 childHueMax = lerp((1 - Math.pow(1 - (i + .55) / this.children.length, 1.4)) * .95, 0, 1, min, max);
3024 }
3025 else
3026 {
3027 childHueMin = lerp(i / this.children.length, 0, 1, min, max);
3028 childHueMax = lerp((i + .55) / this.children.length, 0, 1, min, max);
3029 }
3030 }
3031 else
3032 {
3033 childHueMin = lerp
3034 (
3035 child.baseMagnitude,
3036 this.baseMagnitude,
3037 this.baseMagnitude + this.magnitude,
3038 hueMin,
3039 hueMax
3040 );
3041 childHueMax = lerp
3042 (
3043 child.baseMagnitude + child.magnitude * .99,
3044 this.baseMagnitude,
3045 this.baseMagnitude + this.magnitude,
3046 hueMin,
3047 hueMax
3048 );
3049 }
3050 }
3051 else
3052 {
3053 childHueMin = hueMin;
3054 childHueMax = hueMax;
3055 }
3056
3057 if ( ! this.hideAlone && ! hide && ! this.hide && child.hide )
3058 {
3059 if ( hiddenStart == -1 )
3060 {
3061 hiddenStart = i;
3062 }
3063
3064 if ( useHue() )
3065 {
3066 hiddenHueNumer += childHueMin * child.magnitude;
3067 hiddenHueDenom += child.magnitude;
3068 }
3069 else
3070 {
3071 hiddenHueNumer += childHueMin;
3072 hiddenHueDenom++;
3073 }
3074 }
3075 else
3076 {
3077 hiddenStart = -1;
3078
3079 this.children[i].setTargetsSelected
3080 (
3081 childHueMin,
3082 childHueMax,
3083 lightness,
3084 hide || this.keyed || this.hideAlone || this.hide && ! collapse,
3085 false
3086 );
3087 }
3088
3089 i++;
3090 }
3091
3092 if ( this.hue && this.magnitude )
3093 {
3094 this.hue.setTarget(this.hues[currentDataset]);
3095
3096 if ( this.attributes[magnitudeIndex][lastDataset] == 0 )
3097 {
3098 this.hue.start = this.hue.end;
3099 }
3100 }
3101
3102 this.radialPrev = this.radial;
3103
3104 if ( this == selectedNode )
3105 {
3106 this.resetLabelWidth();
3107 this.labelWidth.setTarget(this.nameWidth * labelWidthFudge);
3108 this.alphaWedge.setTarget(0);
3109 this.alphaLabel.setTarget(1);
3110 this.alphaOther.setTarget(1);
3111 this.alphaArc.setTarget(0);
3112 this.alphaLine.setTarget(0);
3113 this.alphaPattern.setTarget(0);
3114 this.r.setTarget(255);
3115 this.g.setTarget(255);
3116 this.b.setTarget(255);
3117 this.radial = false;
3118 this.labelRadius.setTarget(0);
3119 }
3120 else
3121 {
3122 var rgb = hslToRgb
3123 (
3124 hueMin,
3125 saturation,
3126 lightness
3127 );
3128
3129 this.r.setTarget(rgb.r);
3130 this.g.setTarget(rgb.g);
3131 this.b.setTarget(rgb.b);
3132 this.alphaOther.setTarget(0);
3133
3134 this.alphaWedge.setTarget(1);
3135
3136 if ( this.hide || this.hideAlone )
3137 {
3138 this.alphaPattern.setTarget(1);
3139 }
3140 else
3141 {
3142 this.alphaPattern.setTarget(0);
3143 }
3144
3145 // set radial
3146 //
3147 if ( ! ( hide || this.hide ) )//&& ! this.keyed )
3148 {
3149 if ( this.hideAlone )
3150 {
3151 this.radial = true;
3152 }
3153 else if ( false && canDisplayChildLabels )
3154 {
3155 this.radial = false;
3156 }
3157 else
3158 {
3159 this.radial = true;
3160
3161 if ( this.hasChildren() && depth < maxDisplayDepth )
3162 {
3163 var lastChild = this.children[this.children.length - 1];
3164
3165 if
3166 (
3167 lastChild.angleEnd.end == this.angleEnd.end ||
3168 (
3169 (this.angleStart.end + this.angleEnd.end) / 2 -
3170 lastChild.angleEnd.end
3171 ) * (this.radiusInner.end + 1) * gRadius * 2 <
3172 minWidth()
3173 )
3174 {
3175 this.radial = false;
3176 }
3177 }
3178 }
3179 }
3180
3181 // set alphaLabel
3182 //
3183 if
3184 (
3185 collapse ||
3186 hide ||
3187 this.hide ||
3188 this.keyed ||
3189 depth > maxDisplayDepth ||
3190 ! this.canDisplayDepth()
3191 )
3192 {
3193 this.alphaLabel.setTarget(0);
3194 }
3195 else
3196 {
3197 if
3198 (
3199 (this.radial || nLabelOffsets[depth - 2])
3200 )
3201 {
3202 this.alphaLabel.setTarget(1);
3203 }
3204 else
3205 {
3206 this.alphaLabel.setTarget(0);
3207
3208 if ( this.radialPrev )
3209 {
3210 this.alphaLabel.start = 0;
3211 }
3212 }
3213 }
3214
3215 // set alphaArc
3216 //
3217 if
3218 (
3219 collapse ||
3220 hide ||
3221 depth > maxDisplayDepth ||
3222 ! this.canDisplayDepth()
3223 )
3224 {
3225 this.alphaArc.setTarget(0);
3226 }
3227 else
3228 {
3229 this.alphaArc.setTarget(1);
3230 }
3231
3232 // set alphaLine
3233 //
3234 if
3235 (
3236 hide ||
3237 this.hide && nextSiblingHidden ||
3238 depth > maxDisplayDepth ||
3239 ! this.canDisplayDepth()
3240 )
3241 {
3242 this.alphaLine.setTarget(0);
3243 }
3244 else
3245 {
3246 this.alphaLine.setTarget(1);
3247 }
3248
3249 //if ( ! this.radial )
3250 {
3251 this.resetLabelWidth();
3252 }
3253
3254 // set labelRadius target
3255 //
3256 if ( collapse )
3257 {
3258 this.labelRadius.setTarget(this.radiusInner.end);
3259 }
3260 else
3261 {
3262 if ( depth > maxDisplayDepth || ! this.canDisplayDepth() )
3263 {
3264 this.labelRadius.setTarget(1);
3265 }
3266 else
3267 {
3268 this.setTargetLabelRadius();
3269 }
3270 }
3271 }
3272 }
3273
3274 this.setTargetWedge = function()
3275 {
3276 var depth = this.getDepth() - selectedNode.getDepth() + 1;
3277
3278 // set angles
3279 //
3280 var baseMagnitudeRelative = this.baseMagnitude - selectedNode.baseMagnitude;
3281 //
3282 this.angleStart.setTarget(baseMagnitudeRelative * angleFactor);
3283 this.angleEnd.setTarget((baseMagnitudeRelative + this.magnitude) * angleFactor);
3284
3285 // set radiusInner
3286 //
3287 if ( depth > maxDisplayDepth || ! this.canDisplayDepth() )
3288 {
3289 this.radiusInner.setTarget(1);
3290 }
3291 else
3292 {
3293 if ( compress )
3294 {
3295 this.radiusInner.setTarget(compressedRadii[depth - 2]);
3296 }
3297 else
3298 {
3299 this.radiusInner.setTarget(nodeRadius * (depth - 1));
3300 }
3301 }
3302
3303 if ( this.hide != undefined )
3304 {
3305 this.hidePrev = this.hide;
3306 }
3307
3308 if ( this.hideAlone != undefined )
3309 {
3310 this.hideAlonePrev = this.hideAlone;
3311 }
3312
3313 // set hide
3314 //
3315 if
3316 (
3317 (this.angleEnd.end - this.angleStart.end) *
3318 (this.radiusInner.end * gRadius + gRadius) <
3319 minWidth()
3320 )
3321 {
3322 if ( depth == 2 && ! this.getCollapse() && this.depth <= maxAbsoluteDepth )
3323 {
3324 this.keyed = true;
3325 keys++;
3326 this.hide = false;
3327
3328 var percentage = this.getPercentage();
3329 this.keyLabel = this.name + ' ' + percentage + '%';
3330 var dim = context.measureText(this.keyLabel);
3331 this.keyNameWidth = dim.width;
3332 }
3333 else
3334 {
3335 this.keyed = false;
3336 this.hide = depth > 2;
3337 }
3338 }
3339 else
3340 {
3341 this.keyed = false;
3342 this.hide = false;
3343 }
3344 }
3345
3346 this.shortenLabel = function()
3347 {
3348 var label = this.name;
3349
3350 var labelWidth = this.nameWidth;
3351 var maxWidth = this.labelWidth.current();
3352 var minEndLength = 0;
3353
3354 if ( labelWidth > maxWidth && label.length > minEndLength * 2 )
3355 {
3356 var endLength =
3357 Math.floor((label.length - 1) * maxWidth / labelWidth / 2);
3358
3359 if ( endLength < minEndLength )
3360 {
3361 endLength = minEndLength;
3362 }
3363
3364 return (
3365 label.substring(0, endLength) +
3366 '...' +
3367 label.substring(label.length - endLength));
3368 }
3369 else
3370 {
3371 return label;
3372 }
3373 }
3374
3375 /* this.shouldAddSearchResultsString = function()
3376 {
3377 if ( this.isSearchResult )
3378 {
3379 return this.searchResults > 1;
3380 }
3381 else
3382 {
3383 return this.searchResults > 0;
3384 }
3385 }
3386 */
3387 this.sort = function()
3388 {
3389 this.children.sort(function(a, b){return b.getMagnitude() - a.getMagnitude()});
3390
3391 for (var i = 0; i < this.children.length; i++)
3392 {
3393 this.children[i].sort();
3394 }
3395 }
3396 }
3397
3398 var options;
3399
3400 function addOptionElement(position, innerHTML, title)
3401 {
3402 var div = document.createElement("div");
3403 // div.style.position = 'absolute';
3404 // div.style.top = position + 'px';
3405 div.innerHTML = innerHTML;
3406 // div.style.display = 'block';
3407 div.style.padding = '2px';
3408
3409 if ( title )
3410 {
3411 div.title = title;
3412 }
3413
3414 options.appendChild(div);
3415 var height = 0;//div.clientHeight;
3416 return position + height;
3417 }
3418
3419 function addOptionElements(hueName, hueDefault)
3420 {
3421 options = document.createElement('div');
3422 options.style.position = 'absolute';
3423 options.style.top = '0px';
3424 options.addEventListener('mousedown', function(e) {mouseClick(e)}, false);
3425 // options.onmouseup = function(e) {mouseUp(e)}
3426 document.body.appendChild(options);
3427
3428 document.body.style.font = '11px sans-serif';
3429 var position = 5;
3430
3431 details = document.createElement('div');
3432 details.style.position = 'absolute';
3433 details.style.top = '1%';
3434 details.style.right = '2%';
3435 details.style.textAlign = 'right';
3436 document.body.insertBefore(details, canvas);
3437 // <div id="details" style="position:absolute;top:1%;right:2%;text-align:right;">
3438
3439 details.innerHTML = '\
3440 <span id="detailsName" style="font-weight:bold"></span>&nbsp;\
3441 <input type="button" id="detailsExpand" onclick="expand(focusNode);"\
3442 value="&harr;" title="Expand this wedge to become the new focus of the chart"/><br/>\
3443 <div id="detailsInfo" style="float:right"></div>';
3444
3445 keyControl = document.createElement('input');
3446 keyControl.type = 'button';
3447 keyControl.value = showKeys ? 'x' : '…';
3448 keyControl.style.position = '';
3449 keyControl.style.position = 'fixed';
3450 keyControl.style.visibility = 'hidden';
3451
3452 document.body.insertBefore(keyControl, canvas);
3453
3454 var logoElement = document.getElementById('logo');
3455
3456 if ( logoElement )
3457 {
3458 logoImage = logoElement.src;
3459 }
3460 else
3461 {
3462 logoImage = 'http://krona.sourceforge.net/img/logo.png';
3463 }
3464
3465 // document.getElementById('options').style.fontSize = '9pt';
3466 position = addOptionElement
3467 (
3468 position,
3469 '<a style="margin:2px" target="_blank" href="http://krona.sourceforge.net"><div style="display:inline-block;vertical-align:middle;background-color:#EEEEEE;border:1px solid gray;padding:2px;font-size:18px"><img style="vertical-align:middle;" src="' + logoImage + '"/><span style="vertical-align:middle;color:#555555">Krona</span></div></a><input type="button" id="back" value="&larr;" title="Go back (Shortcut: &larr;)"/>\
3470 <input type="button" id="forward" value="&rarr;" title="Go forward (Shortcut: &rarr;)"/> \
3471 &nbsp;Search: <input type="text" id="search"/>\
3472 <input id="searchClear" type="button" value="x" onclick="clearSearch()"/> \
3473 <span id="searchResults"></span>'
3474 );
3475
3476 if ( datasets > 1 )
3477 {
3478 var size = datasets < datasetSelectSize ? datasets : datasetSelectSize;
3479
3480 var select =
3481 '<table style="border-collapse:collapse;padding:0px"><tr><td style="padding:0px">' +
3482 '<select id="datasets" style="min-width:100px" size="' + size + '" onchange="onDatasetChange()">';
3483
3484 for ( var i = 0; i < datasetNames.length; i++ )
3485 {
3486 select += '<option>' + datasetNames[i] + '</option>';
3487 }
3488
3489 select +=
3490 '</select></td><td style="vertical-align:top;padding:1px;">' +
3491 '<input style="display:block" title="Previous dataset (Shortcut: &uarr;)" id="prevDataset" type="button" value="&uarr;" onclick="prevDataset()" disabled="true"/>' +
3492 '<input title="Next dataset (Shortcut: &darr;)" id="nextDataset" type="button" value="&darr;" onclick="nextDataset()"/><br/></td>' +
3493 '<td style="padding-top:1px;vertical-align:top"><input title="Switch to the last dataset that was viewed (Shortcut: TAB)" id="lastDataset" type="button" style="font:11px Times new roman" value="last" onclick="selectLastDataset()"/></td></tr></table>';
3494
3495 position = addOptionElement(position + 5, select);
3496
3497 datasetDropDown = document.getElementById('datasets');
3498 datasetButtonLast = document.getElementById('lastDataset');
3499 datasetButtonPrev = document.getElementById('prevDataset');
3500 datasetButtonNext = document.getElementById('nextDataset');
3501
3502 position += datasetDropDown.clientHeight;
3503 }
3504
3505 position = addOptionElement
3506 (
3507 position + 5,
3508 '<input type="button" id="maxAbsoluteDepthDecrease" value="-"/>\
3509 <span id="maxAbsoluteDepth"></span>\
3510 &nbsp;<input type="button" id="maxAbsoluteDepthIncrease" value="+"/> Max depth',
3511 'Maximum depth to display, counted from the top level \
3512 and including collapsed wedges.'
3513 );
3514
3515 position = addOptionElement
3516 (
3517 position,
3518 '<input type="button" id="fontSizeDecrease" value="-"/>\
3519 <span id="fontSize"></span>\
3520 &nbsp;<input type="button" id="fontSizeIncrease" value="+"/> Font size'
3521 );
3522
3523 position = addOptionElement
3524 (
3525 position,
3526 '<input type="button" id="radiusDecrease" value="-"/>\
3527 <input type="button" id="radiusIncrease" value="+"/> Chart size'
3528 );
3529
3530 if ( hueName )
3531 {
3532 hueDisplayName = attributes[attributeIndex(hueName)].displayName;
3533
3534 position = addOptionElement
3535 (
3536 position + 5,
3537 '<input type="checkbox" id="useHue" style="float:left" ' +
3538 '/><div>Color by<br/>' + hueDisplayName +
3539 '</div>'
3540 );
3541
3542 useHueCheckBox = document.getElementById('useHue');
3543 useHueCheckBox.checked = hueDefault;
3544 useHueCheckBox.onclick = handleResize;
3545 useHueCheckBox.onmousedown = suppressEvent;
3546 }
3547 /*
3548 position = addOptionElement
3549 (
3550 position + 5,
3551 '&nbsp;<input type="checkbox" id="shorten" checked="checked" />Shorten labels</div>',
3552 'Prevent labels from overlapping by shortening them'
3553 );
3554
3555 position = addOptionElement
3556 (
3557 position,
3558 '&nbsp;<input type="checkbox" id="compress" checked="checked" />Compress',
3559 'Compress wedges if needed to show the entire depth'
3560 );
3561 */
3562 position = addOptionElement
3563 (
3564 position,
3565 '<input type="checkbox" id="collapse" checked="checked" />Collapse',
3566 'Collapse wedges that are redundant (entirely composed of another wedge)'
3567 );
3568
3569 position = addOptionElement
3570 (
3571 position + 5,
3572 '<input type="button" id="snapshot" value="Snapshot"/>',
3573 'Render the current view as SVG (Scalable Vector Graphics), a publication-\
3574 quality format that can be printed and saved (see Help for browser compatibility)'
3575 );
3576
3577 position = addOptionElement
3578 (
3579 position + 5,
3580 '<input type="button" id="linkButton" value="Link"/>\
3581 <input type="text" size="30" id="linkText"/>',
3582 'Show a link to this view that can be copied for bookmarking or sharing'
3583 );
3584
3585 position = addOptionElement
3586 (
3587 position + 5,
3588 '<input type="button" id="help" value="?"\
3589 onclick="window.open(\'https://sourceforge.net/p/krona/wiki/Browsing%20Krona%20charts/\', \'help\')"/>',
3590 'Help'
3591 );
3592 }
3593
3594 function arrow(angleStart, angleEnd, radiusInner)
3595 {
3596 if ( context.globalAlpha == 0 )
3597 {
3598 return;
3599 }
3600
3601 var angleCenter = (angleStart + angleEnd) / 2;
3602 var radiusArrowInner = radiusInner - gRadius / 10;//nodeRadius * gRadius;
3603 var radiusArrowOuter = gRadius * 1.1;//(1 + nodeRadius);
3604 var radiusArrowCenter = (radiusArrowInner + radiusArrowOuter) / 2;
3605 var pointLength = (radiusArrowOuter - radiusArrowInner) / 5;
3606
3607 context.fillStyle = highlightFill;
3608 context.lineWidth = highlightLineWidth;
3609
3610 // First, mask out the first half of the arrow. This will prevent the tips
3611 // from superimposing if the arrow goes most of the way around the circle.
3612 // Masking is done by setting the clipping region to the inverse of the
3613 // half-arrow, which is defined by cutting the half-arrow out of a large
3614 // rectangle
3615 //
3616 context.beginPath();
3617 context.arc(0, 0, radiusInner, angleCenter, angleEnd, false);
3618 context.lineTo
3619 (
3620 radiusArrowInner * Math.cos(angleEnd),
3621 radiusArrowInner * Math.sin(angleEnd)
3622 );
3623 context.lineTo
3624 (
3625 radiusArrowCenter * Math.cos(angleEnd) - pointLength * Math.sin(angleEnd),
3626 radiusArrowCenter * Math.sin(angleEnd) + pointLength * Math.cos(angleEnd)
3627 );
3628 context.lineTo
3629 (
3630 radiusArrowOuter * Math.cos(angleEnd),
3631 radiusArrowOuter * Math.sin(angleEnd)
3632 );
3633 context.arc(0, 0, gRadius, angleEnd, angleCenter, true);
3634 context.closePath();
3635 context.moveTo(-imageWidth, -imageHeight);
3636 context.lineTo(imageWidth, -imageHeight);
3637 context.lineTo(imageWidth, imageHeight);
3638 context.lineTo(-imageWidth, imageHeight);
3639 context.closePath();
3640 context.save();
3641 context.clip();
3642
3643 // Next, draw the other half-arrow with the first half masked out
3644 //
3645 context.beginPath();
3646 context.arc(0, 0, radiusInner, angleCenter, angleStart, true);
3647 context.lineTo
3648 (
3649 radiusArrowInner * Math.cos(angleStart),
3650 radiusArrowInner * Math.sin(angleStart)
3651 );
3652 context.lineTo
3653 (
3654 radiusArrowCenter * Math.cos(angleStart) + pointLength * Math.sin(angleStart),
3655 radiusArrowCenter * Math.sin(angleStart) - pointLength * Math.cos(angleStart)
3656 );
3657 context.lineTo
3658 (
3659 radiusArrowOuter * Math.cos(angleStart),
3660 radiusArrowOuter * Math.sin(angleStart)
3661 );
3662 context.arc(0, 0, gRadius, angleStart, angleCenter, false);
3663 context.fill();
3664 context.stroke();
3665
3666 // Finally, remove the clipping region and draw the first half-arrow. This
3667 // half is extended slightly to fill the seam.
3668 //
3669 context.restore();
3670 context.beginPath();
3671 context.arc(0, 0, radiusInner, angleCenter - 2 / (2 * Math.PI * radiusInner), angleEnd, false);
3672 context.lineTo
3673 (
3674 radiusArrowInner * Math.cos(angleEnd),
3675 radiusArrowInner * Math.sin(angleEnd)
3676 );
3677 context.lineTo
3678 (
3679 radiusArrowCenter * Math.cos(angleEnd) - pointLength * Math.sin(angleEnd),
3680 radiusArrowCenter * Math.sin(angleEnd) + pointLength * Math.cos(angleEnd)
3681 );
3682 context.lineTo
3683 (
3684 radiusArrowOuter * Math.cos(angleEnd),
3685 radiusArrowOuter * Math.sin(angleEnd)
3686 );
3687 context.arc(0, 0, gRadius, angleEnd, angleCenter - 2 / (2 * Math.PI * gRadius), true);
3688 context.fill();
3689 context.stroke();
3690 }
3691
3692 function attributeIndex(aname)
3693 {
3694 for ( var i = 0 ; i < attributes.length; i++ )
3695 {
3696 if ( aname == attributes[i].name )
3697 {
3698 return i;
3699 }
3700 }
3701
3702 return null;
3703 }
3704
3705 function checkHighlight()
3706 {
3707 var lastHighlightedNode = highlightedNode;
3708 var lastHighlightingHidden = highlightingHidden;
3709
3710 highlightedNode = selectedNode;
3711 resetKeyOffset();
3712
3713 if ( progress == 1 )
3714 {
3715 selectedNode.checkHighlight();
3716 if ( selectedNode.getParent() )
3717 {
3718 selectedNode.getParent().checkHighlightCenter();
3719 }
3720
3721 focusNode.checkHighlightMap();
3722 }
3723
3724 if ( highlightedNode != selectedNode )
3725 {
3726 if ( highlightedNode == focusNode )
3727 {
3728 // canvas.style.display='none';
3729 // window.resizeBy(1,0);
3730 // canvas.style.cursor='ew-resize';
3731 // window.resizeBy(-1,0);
3732 // canvas.style.display='inline';
3733 }
3734 else
3735 {
3736 // canvas.style.cursor='pointer';
3737 }
3738 }
3739 else
3740 {
3741 // canvas.style.cursor='auto';
3742 }
3743
3744 if
3745 (
3746 (
3747 true ||
3748 highlightedNode != lastHighlightedNode ||
3749 highlightingHidden != highlightingHiddenLast
3750 ) &&
3751 progress == 1
3752 )
3753 {
3754 draw(); // TODO: handle in update()
3755 }
3756 }
3757
3758 function checkSelectedCollapse()
3759 {
3760 var newNode = selectedNode;
3761
3762 while ( newNode.getCollapse() )
3763 {
3764 newNode = newNode.children[0];
3765 }
3766
3767 if ( newNode.children.length == 0 )
3768 {
3769 newNode = newNode.getParent();
3770 }
3771
3772 if ( newNode != selectedNode )
3773 {
3774 selectNode(newNode);
3775 }
3776 }
3777
3778 function clearSearch()
3779 {
3780 if ( search.value != '' )
3781 {
3782 search.value = '';
3783 onSearchChange();
3784 }
3785 }
3786
3787 function createSVG()
3788 {
3789 svgNS = "http://www.w3.org/2000/svg";
3790 var SVG = {};
3791 SVG.xlinkns = "http://www.w3.org/1999/xlink";
3792
3793 var newSVG = document.createElementNS(svgNS, "svg:svg");
3794
3795 newSVG.setAttribute("id", "canvas");
3796 // How big is the canvas in pixels
3797 newSVG.setAttribute("width", '100%');
3798 newSVG.setAttribute("height", '100%');
3799 // Set the coordinates used by drawings in the canvas
3800 // newSVG.setAttribute("viewBox", "0 0 " + imageWidth + " " + imageHeight);
3801 // Define the XLink namespace that SVG uses
3802 newSVG.setAttributeNS
3803 (
3804 "http://www.w3.org/2000/xmlns/",
3805 "xmlns:xlink",
3806 SVG.xlinkns
3807 );
3808
3809 return newSVG;
3810 }
3811
3812 function degrees(radians)
3813 {
3814 return radians * 180 / Math.PI;
3815 }
3816
3817 function draw()
3818 {
3819 tweenFrames++;
3820 //resize();
3821 // context.fillRect(0, 0, imageWidth, imageHeight);
3822 context.clearRect(0, 0, imageWidth, imageHeight);
3823
3824 context.font = fontNormal;
3825 context.textBaseline = 'middle';
3826
3827 //context.strokeStyle = 'rgba(0, 0, 0, 0.3)';
3828 context.translate(centerX, centerY);
3829
3830 resetKeyOffset();
3831
3832 head.draw(false, false); // draw pie slices
3833 head.draw(true, false); // draw labels
3834
3835 var pathRoot = selectedNode;
3836
3837 if ( focusNode != 0 && focusNode != selectedNode )
3838 {
3839 context.globalAlpha = 1;
3840 focusNode.drawHighlight(true);
3841 pathRoot = focusNode;
3842 }
3843
3844 if
3845 (
3846 highlightedNode &&
3847 highlightedNode.getDepth() >= selectedNode.getDepth() &&
3848 highlightedNode != focusNode
3849 )
3850 {
3851 if
3852 (
3853 progress == 1 &&
3854 highlightedNode != selectedNode &&
3855 (
3856 highlightedNode != focusNode ||
3857 focusNode.children.length > 0
3858 )
3859 )
3860 {
3861 context.globalAlpha = 1;
3862 highlightedNode.drawHighlight(true);
3863 }
3864
3865 //pathRoot = highlightedNode;
3866 }
3867 else if
3868 (
3869 progress == 1 &&
3870 highlightedNode.getDepth() < selectedNode.getDepth()
3871 )
3872 {
3873 context.globalAlpha = 1;
3874 highlightedNode.drawHighlightCenter();
3875 }
3876
3877 if ( quickLook && false) // TEMP
3878 {
3879 context.globalAlpha = 1 - progress / 2;
3880 selectedNode.drawHighlight(true);
3881 }
3882 else if ( progress < 1 )//&& zoomOut() )
3883 {
3884 if ( !zoomOut)//() )
3885 {
3886 context.globalAlpha = selectedNode.alphaLine.current();
3887 selectedNode.drawHighlight(true);
3888 }
3889 else if ( selectedNodeLast )
3890 {
3891 context.globalAlpha = 1 - 4 * Math.pow(progress - .5, 2);
3892 selectedNodeLast.drawHighlight(false);
3893 }
3894 }
3895
3896 drawDatasetName();
3897
3898 //drawHistory();
3899
3900 context.translate(-centerX, -centerY);
3901 context.globalAlpha = 1;
3902
3903 mapRadius =
3904 (imageHeight / 2 - details.clientHeight - details.offsetTop) /
3905 (pathRoot.getDepth() - 1) * 3 / 4 / 2;
3906
3907 if ( mapRadius > maxMapRadius )
3908 {
3909 mapRadius = maxMapRadius;
3910 }
3911
3912 mapBuffer = mapRadius / 2;
3913
3914 //context.font = fontNormal;
3915 pathRoot.drawMap(pathRoot);
3916
3917 if ( hueDisplayName && useHue() )
3918 {
3919 drawLegend();
3920 }
3921 }
3922
3923 function drawBubble(angle, radius, width, radial, flip)
3924 {
3925 var height = fontSize * 2;
3926 var x;
3927 var y;
3928
3929 width = width + fontSize;
3930
3931 if ( radial )
3932 {
3933 y = -fontSize;
3934
3935 if ( flip )
3936 {
3937 x = radius - width + fontSize / 2;
3938 }
3939 else
3940 {
3941 x = radius - fontSize / 2;
3942 }
3943 }
3944 else
3945 {
3946 x = -width / 2;
3947 y = -radius - fontSize;
3948 }
3949
3950 if ( snapshotMode )
3951 {
3952 drawBubbleSVG(x + centerX, y + centerY, width, height, fontSize, angle);
3953 }
3954 else
3955 {
3956 drawBubbleCanvas(x, y, width, height, fontSize, angle);
3957 }
3958 }
3959
3960 function drawBubbleCanvas(x, y, width, height, radius, rotation)
3961 {
3962 context.strokeStyle = 'black';
3963 context.lineWidth = highlightLineWidth;
3964 context.fillStyle = 'rgba(255, 255, 255, .75)';
3965 context.rotate(rotation);
3966 roundedRectangle(x, y, width, fontSize * 2, fontSize);
3967 context.fill();
3968 context.stroke();
3969 context.rotate(-rotation);
3970 }
3971
3972 function drawBubbleSVG(x, y, width, height, radius, rotation)
3973 {
3974 svg +=
3975 '<rect x="' + x + '" y="' + y +
3976 '" width="' + width +
3977 '" height="' + height +
3978 '" rx="' + radius +
3979 '" ry="' + radius +
3980 '" fill="rgba(255, 255, 255, .75)' +
3981 '" class="highlight" ' +
3982 'transform="rotate(' +
3983 degrees(rotation) + ',' + centerX + ',' + centerY +
3984 ')"/>';
3985 }
3986
3987 function drawDatasetName()
3988 {
3989 var alpha = datasetAlpha.current();
3990
3991 if ( alpha > 0 )
3992 {
3993 var radius = gRadius * compressedRadii[0] / -2;
3994
3995 if ( alpha > 1 )
3996 {
3997 alpha = 1;
3998 }
3999
4000 context.globalAlpha = alpha;
4001
4002 drawBubble(0, -radius, datasetWidths[currentDataset], false, false);
4003 drawText(datasetNames[currentDataset], 0, radius, 0, 'center', true);
4004 }
4005 }
4006
4007 function drawHistory()
4008 {
4009 var alpha = 1;
4010 context.textAlign = 'center';
4011
4012 for ( var i = 0; i < nodeHistoryPosition && alpha > 0; i++ )
4013 {
4014
4015 context.globalAlpha = alpha - historyAlphaDelta * tweenFactor;
4016 context.fillText
4017 (
4018 nodeHistory[nodeHistoryPosition - i - 1].name,
4019 0,
4020 (i + tweenFactor) * historySpacingFactor * fontSize - 1
4021 );
4022
4023 if ( alpha > 0 )
4024 {
4025 alpha -= historyAlphaDelta;
4026 }
4027 }
4028
4029 context.globalAlpha = 1;
4030 }
4031
4032 function drawLegend()
4033 {
4034 var left = imageWidth * .01;
4035 var width = imageHeight * .0265;
4036 var height = imageHeight * .15;
4037 var top = imageHeight - fontSize * 3.5 - height;
4038 var textLeft = left + width + fontSize / 2;
4039
4040 context.fillStyle = 'black';
4041 context.textAlign = 'start';
4042 context.font = fontNormal;
4043 // context.fillText(valueStartText, textLeft, top + height);
4044 // context.fillText(valueEndText, textLeft, top);
4045 context.fillText(hueDisplayName, left, imageHeight - fontSize * 1.5);
4046
4047 var gradient = context.createLinearGradient(0, top + height, 0, top);
4048
4049 for ( var i = 0; i < hueStopPositions.length; i++ )
4050 {
4051 gradient.addColorStop(hueStopPositions[i], hueStopHsl[i]);
4052
4053 var textY = top + (1 - hueStopPositions[i]) * height;
4054
4055 if
4056 (
4057 i == 0 ||
4058 i == hueStopPositions.length - 1 ||
4059 textY > top + fontSize && textY < top + height - fontSize
4060 )
4061 {
4062 context.fillText(hueStopText[i], textLeft, textY);
4063 }
4064 }
4065
4066 context.fillStyle = gradient;
4067 context.fillRect(left, top, width, height);
4068 context.lineWidth = thinLineWidth;
4069 context.strokeRect(left, top, width, height);
4070 }
4071
4072 function drawLegendSVG()
4073 {
4074 var left = imageWidth * .01;
4075 var width = imageHeight * .0265;
4076 var height = imageHeight * .15;
4077 var top = imageHeight - fontSize * 3.5 - height;
4078 var textLeft = left + width + fontSize / 2;
4079
4080 var text = '';
4081
4082 text += svgText(hueDisplayName, left, imageHeight - fontSize * 1.5);
4083
4084 var svgtest = '<linearGradient id="gradient" x1="0%" y1="100%" x2="0%" y2="0%">';
4085
4086 for ( var i = 0; i < hueStopPositions.length; i++ )
4087 {
4088 svgtest +=
4089 '<stop offset="' + round(hueStopPositions[i] * 100) +
4090 '%" style="stop-color:' + hueStopHsl[i] + '"/>';
4091
4092 var textY = top + (1 - hueStopPositions[i]) * height;
4093
4094 if
4095 (
4096 i == 0 ||
4097 i == hueStopPositions.length - 1 ||
4098 textY > top + fontSize && textY < top + height - fontSize
4099 )
4100 {
4101 text += svgText(hueStopText[i], textLeft, textY);
4102 }
4103 }
4104
4105 svgtest += '</linearGradient>';
4106 //alert(svgtest);
4107 svg += svgtest;
4108 svg +=
4109 '<rect style="fill:url(#gradient)" x="' + left + '" y="' + top +
4110 '" width="' + width + '" height="' + height + '"/>';
4111
4112 svg += text;
4113 }
4114
4115 function drawSearchHighlights(label, bubbleX, bubbleY, rotation, center)
4116 {
4117 var index = -1;
4118 var labelLength = label.length;
4119
4120 bubbleX -= fontSize / 4;
4121
4122 do
4123 {
4124 index = label.toLowerCase().indexOf(search.value.toLowerCase(), index + 1);
4125
4126 if ( index != -1 && index < labelLength )
4127 {
4128 var dim = context.measureText(label.substr(0, index));
4129 var x = bubbleX + dim.width;
4130
4131 dim = context.measureText(label.substr(index, search.value.length));
4132
4133 var y = bubbleY - fontSize * 3 / 4;
4134 var width = dim.width + fontSize / 2;
4135 var height = fontSize * 3 / 2;
4136 var radius = fontSize / 2;
4137
4138 if ( snapshotMode )
4139 {
4140 if ( center )
4141 {
4142 x += centerX;
4143 y += centerY;
4144 }
4145
4146 svg +=
4147 '<rect x="' + x + '" y="' + y +
4148 '" width="' + width +
4149 '" height="' + height +
4150 '" rx="' + radius +
4151 '" ry="' + radius +
4152 '" class="searchHighlight' +
4153 '" transform="rotate(' +
4154 degrees(rotation) + ',' + centerX + ',' + centerY +
4155 ')"/>';
4156 }
4157 else
4158 {
4159 context.fillStyle = 'rgb(255, 255, 100)';
4160 context.rotate(rotation);
4161 roundedRectangle(x, y, width, height, radius);
4162 context.fill();
4163 context.rotate(-rotation);
4164 }
4165 }
4166 }
4167 while ( index != -1 && index < labelLength );
4168 }
4169
4170 function drawText(text, x, y, angle, anchor, bold, color)
4171 {
4172 if ( color == undefined )
4173 {
4174 color = 'black';
4175 }
4176
4177 if ( snapshotMode )
4178 {
4179 svg +=
4180 '<text x="' + (centerX + x) + '" y="' + (centerY + y) +
4181 '" text-anchor="' + anchor + '" style="font-color:' + color + ';font-weight:' + (bold ? 'bold' : 'normal') +
4182 '" transform="rotate(' + degrees(angle) + ',' + centerX + ',' + centerY + ')">' +
4183 text + '</text>';
4184 }
4185 else
4186 {
4187 context.fillStyle = color;
4188 context.textAlign = anchor;
4189 context.font = bold ? fontBold : fontNormal;
4190 context.rotate(angle);
4191 context.fillText(text, x, y);
4192 context.rotate(-angle);
4193 }
4194 }
4195
4196 function drawTextPolar
4197 (
4198 text,
4199 innerText,
4200 angle,
4201 radius,
4202 radial,
4203 bubble,
4204 bold,
4205 searchResult,
4206 searchResults
4207 )
4208 {
4209 var anchor;
4210 var textX;
4211 var textY;
4212 var spacer;
4213 var totalText = text;
4214 var flip;
4215
4216 if ( snapshotMode )
4217 {
4218 spacer = '&#160;&#160;&#160;';
4219 }
4220 else
4221 {
4222 spacer = ' ';
4223 }
4224
4225 if ( radial )
4226 {
4227 flip = angle < 3 * Math.PI / 2;
4228
4229 if ( flip )
4230 {
4231 angle -= Math.PI;
4232 radius = -radius;
4233 anchor = 'end';
4234
4235 if ( innerText )
4236 {
4237 totalText = text + spacer + innerText;
4238 }
4239 }
4240 else
4241 {
4242 anchor = 'start';
4243
4244 if ( innerText )
4245 {
4246 totalText = innerText + spacer + text;
4247 }
4248 }
4249
4250 textX = radius;
4251 textY = 0;
4252 }
4253 else
4254 {
4255 flip = angle < Math.PI || angle > 2 * Math.PI;
4256 var label;
4257
4258 anchor = snapshotMode ? 'middle' : 'center';
4259
4260 if ( flip )
4261 {
4262 angle -= Math.PI;
4263 radius = -radius;
4264 }
4265
4266 angle += Math.PI / 2;
4267 textX = 0;
4268 textY = -radius;
4269 }
4270
4271 if ( bubble )
4272 {
4273 var textActual = totalText;
4274
4275 if ( innerText && snapshotMode )
4276 {
4277 if ( flip )
4278 {
4279 textActual = text + ' ' + innerText;
4280 }
4281 else
4282 {
4283 textActual = innerText + ' ' + text;
4284 }
4285 }
4286
4287 if ( searchResults )
4288 {
4289 textActual = textActual + searchResultString(searchResults);
4290 }
4291
4292 var textWidth = measureText(textActual, bold);
4293
4294 var x = textX;
4295
4296 if ( anchor == 'end' )
4297 {
4298 x -= textWidth;
4299 }
4300 else if ( anchor != 'start' )
4301 {
4302 // centered
4303 x -= textWidth / 2;
4304 }
4305
4306 drawBubble(angle, radius, textWidth, radial, flip);
4307
4308 if ( searchResult )
4309 {
4310 drawSearchHighlights
4311 (
4312 textActual,
4313 x,
4314 textY,
4315 angle,
4316 true
4317 )
4318 }
4319 }
4320
4321 if ( searchResults )
4322 {
4323 totalText = totalText + searchResultString(searchResults);
4324 }
4325
4326 drawText(totalText, textX, textY, angle, anchor, bold);
4327
4328 return flip;
4329 }
4330
4331 function drawTick(start, length, angle)
4332 {
4333 if ( snapshotMode )
4334 {
4335 svg +=
4336 '<line x1="' + (centerX + start) +
4337 '" y1="' + centerY +
4338 '" x2="' + (centerX + start + length) +
4339 '" y2="' + centerY +
4340 '" class="tick" transform="rotate(' +
4341 degrees(angle) + ',' + centerX + ',' + centerY +
4342 ')"/>';
4343 }
4344 else
4345 {
4346 context.rotate(angle);
4347 context.beginPath();
4348 context.moveTo(start, 0);
4349 context.lineTo(start + length, 0);
4350 context.lineWidth = thinLineWidth * 2;
4351 context.stroke();
4352 context.rotate(-angle);
4353 }
4354 }
4355
4356 function drawWedge
4357 (
4358 angleStart,
4359 angleEnd,
4360 radiusInner,
4361 radiusOuter,
4362 color,
4363 patternAlpha,
4364 highlight
4365 )
4366 {
4367 if ( context.globalAlpha == 0 )
4368 {
4369 return;
4370 }
4371
4372 if ( snapshotMode )
4373 {
4374 if ( angleEnd == angleStart + Math.PI * 2 )
4375 {
4376 // fudge to prevent overlap, which causes arc ambiguity
4377 //
4378 angleEnd -= .1 / gRadius;
4379 }
4380
4381 var longArc = angleEnd - angleStart > Math.PI ? 1 : 0;
4382
4383 var x1 = centerX + radiusInner * Math.cos(angleStart);
4384 var y1 = centerY + radiusInner * Math.sin(angleStart);
4385
4386 var x2 = centerX + gRadius * Math.cos(angleStart);
4387 var y2 = centerY + gRadius * Math.sin(angleStart);
4388
4389 var x3 = centerX + gRadius * Math.cos(angleEnd);
4390 var y3 = centerY + gRadius * Math.sin(angleEnd);
4391
4392 var x4 = centerX + radiusInner * Math.cos(angleEnd);
4393 var y4 = centerY + radiusInner * Math.sin(angleEnd);
4394
4395 var dArray =
4396 [
4397 " M ", x1, ",", y1,
4398 " L ", x2, ",", y2,
4399 " A ", gRadius, ",", gRadius, " 0 ", longArc, ",1 ", x3, ",", y3,
4400 " L ", x4, ",", y4,
4401 " A ", radiusInner, ",", radiusInner, " 0 ", longArc, " 0 ", x1, ",", y1,
4402 " Z "
4403 ];
4404
4405 svg +=
4406 '<path class="'+ (highlight ? 'highlight' : 'wedge') + '" fill="' + color +
4407 '" d="' + dArray.join('') + '"/>';
4408
4409 if ( patternAlpha > 0 )
4410 {
4411 svg +=
4412 '<path class="wedge" fill="url(#hiddenPattern)" d="' +
4413 dArray.join('') + '"/>';
4414 }
4415 }
4416 else
4417 {
4418 // fudge to prevent seams during animation
4419 //
4420 angleEnd += 1 / gRadius;
4421
4422 context.fillStyle = color;
4423 context.beginPath();
4424 context.arc(0, 0, radiusInner, angleStart, angleEnd, false);
4425 context.arc(0, 0, radiusOuter, angleEnd, angleStart, true);
4426 context.closePath();
4427 context.fill();
4428
4429 if ( patternAlpha > 0 )
4430 {
4431 context.save();
4432 context.clip();
4433 context.globalAlpha = patternAlpha;
4434 context.fillStyle = hiddenPattern;
4435 context.fill();
4436 context.restore();
4437 }
4438
4439 if ( highlight )
4440 {
4441 context.lineWidth = highlight ? highlightLineWidth : thinLineWidth;
4442 context.strokeStyle = 'black';
4443 context.stroke();
4444 }
4445 }
4446 }
4447
4448 function expand(node)
4449 {
4450 selectNode(node);
4451 updateView();
4452 }
4453
4454 function focusLost()
4455 {
4456 mouseX = -1;
4457 mouseY = -1;
4458 checkHighlight();
4459 document.body.style.cursor = 'auto';
4460 }
4461
4462 function fontSizeDecrease()
4463 {
4464 if ( fontSize > 1 )
4465 {
4466 fontSize--;
4467 updateViewNeeded = true;
4468 }
4469 }
4470
4471 function fontSizeIncrease()
4472 {
4473 fontSize++;
4474 updateViewNeeded = true;
4475 }
4476
4477 function getGetString(name, value, bool)
4478 {
4479 return name + '=' + (bool ? value ? 'true' : 'false' : value);
4480 }
4481
4482 function hideLink()
4483 {
4484 hide(linkText);
4485 show(linkButton);
4486 }
4487
4488 function show(object)
4489 {
4490 object.style.display = 'inline';
4491 }
4492
4493 function hide(object)
4494 {
4495 object.style.display = 'none';
4496 }
4497
4498 function showLink()
4499 {
4500 var urlHalves = String(document.location).split('?');
4501 var newGetVariables = new Array();
4502
4503 newGetVariables.push
4504 (
4505 getGetString('dataset', currentDataset, false),
4506 getGetString('node', selectedNode.id, false),
4507 getGetString('collapse', collapse, true),
4508 getGetString('color', useHue(), true),
4509 getGetString('depth', maxAbsoluteDepth - 1, false),
4510 getGetString('font', fontSize, false),
4511 getGetString('key', showKeys, true)
4512 );
4513
4514 hide(linkButton);
4515 show(linkText);
4516 linkText.value = urlHalves[0] + '?' + getVariables.concat(newGetVariables).join('&');
4517 //linkText.disabled = false;
4518 linkText.focus();
4519 linkText.select();
4520 //linkText.disabled = true;
4521 // document.location = urlHalves[0] + '?' + getVariables.join('&');
4522 }
4523
4524 function getFirstChild(element)
4525 {
4526 element = element.firstChild;
4527
4528 if ( element && element.nodeType != 1 )
4529 {
4530 element = getNextSibling(element);
4531 }
4532
4533 return element;
4534 }
4535
4536 function getNextSibling(element)
4537 {
4538 do
4539 {
4540 element = element.nextSibling;
4541 }
4542 while ( element && element.nodeType != 1 );
4543
4544 return element;
4545 }
4546
4547 function getPercentage(fraction)
4548 {
4549 return round(fraction * 100);
4550 }
4551
4552 function hslText(hue)
4553 {
4554 if ( 1 || snapshotMode )
4555 {
4556 // Safari doesn't seem to allow hsl() in SVG
4557
4558 var rgb = hslToRgb(hue, saturation, (lightnessBase + lightnessMax) / 2);
4559
4560 return rgbText(rgb.r, rgb.g, rgb.b);
4561 }
4562 else
4563 {
4564 var hslArray =
4565 [
4566 'hsl(',
4567 Math.floor(hue * 360),
4568 ',',
4569 Math.floor(saturation * 100),
4570 '%,',
4571 Math.floor((lightnessBase + lightnessMax) * 50),
4572 '%)'
4573 ];
4574
4575 return hslArray.join('');
4576 }
4577 }
4578
4579 function hslToRgb(h, s, l)
4580 {
4581 var m1, m2;
4582 var r, g, b;
4583
4584 if (s == 0)
4585 {
4586 r = g = b = Math.floor((l * 255));
4587 }
4588 else
4589 {
4590 if (l <= 0.5)
4591 {
4592 m2 = l * (s + 1);
4593 }
4594 else
4595 {
4596 m2 = l + s - l * s;
4597 }
4598
4599 m1 = l * 2 - m2;
4600
4601 r = Math.floor(hueToRgb(m1, m2, h + 1 / 3));
4602 g = Math.floor(hueToRgb(m1, m2, h));
4603 b = Math.floor(hueToRgb(m1, m2, h - 1/3));
4604 }
4605
4606 return {r: r, g: g, b: b};
4607 }
4608
4609 function hueToRgb(m1, m2, hue)
4610 {
4611 var v;
4612
4613 while (hue < 0)
4614 {
4615 hue += 1;
4616 }
4617
4618 while (hue > 1)
4619 {
4620 hue -= 1;
4621 }
4622
4623 if (6 * hue < 1)
4624 v = m1 + (m2 - m1) * hue * 6;
4625 else if (2 * hue < 1)
4626 v = m2;
4627 else if (3 * hue < 2)
4628 v = m1 + (m2 - m1) * (2/3 - hue) * 6;
4629 else
4630 v = m1;
4631
4632 return 255 * v;
4633 }
4634
4635 function interpolateHue(hueStart, hueEnd, valueStart, valueEnd)
4636 {
4637 // since the gradient will be RGB based, we need to add stops to hit all the
4638 // colors in the hue spectrum
4639
4640 hueStopPositions = new Array();
4641 hueStopHsl = new Array();
4642 hueStopText = new Array();
4643
4644 hueStopPositions.push(0);
4645 hueStopHsl.push(hslText(hueStart));
4646 hueStopText.push(round(valueStart));
4647
4648 for
4649 (
4650 var i = (hueStart > hueEnd ? 5 / 6 : 1 / 6);
4651 (hueStart > hueEnd ? i > 0 : i < 1);
4652 i += (hueStart > hueEnd ? -1 : 1) / 6
4653 )
4654 {
4655 if
4656 (
4657 hueStart > hueEnd ?
4658 i > hueEnd && i < hueStart :
4659 i > hueStart && i < hueEnd
4660 )
4661 {
4662 hueStopPositions.push(lerp(i, hueStart, hueEnd, 0, 1));
4663 hueStopHsl.push(hslText(i));
4664 hueStopText.push(round(lerp
4665 (
4666 i,
4667 hueStart,
4668 hueEnd,
4669 valueStart,
4670 valueEnd
4671 )));
4672 }
4673 }
4674
4675 hueStopPositions.push(1);
4676 hueStopHsl.push(hslText(hueEnd));
4677 hueStopText.push(round(valueEnd));
4678 }
4679
4680 function keyLineAngle(angle, keyAngle, bendRadius, keyX, keyY, pointsX, pointsY)
4681 {
4682 if ( angle < Math.PI / 2 && keyY < bendRadius * Math.sin(angle)
4683 || angle > Math.PI / 2 && keyY < bendRadius)
4684 {
4685 return Math.asin(keyY / bendRadius);
4686 }
4687 else
4688 {
4689 // find the angle of the normal to a tangent line that goes to
4690 // the label
4691
4692 var textDist = Math.sqrt
4693 (
4694 Math.pow(keyX, 2) +
4695 Math.pow(keyY, 2)
4696 );
4697
4698 var tanAngle = Math.acos(bendRadius / textDist) + keyAngle;
4699
4700 if ( angle < tanAngle || angle < Math.PI / 2 )//|| labelLeft < centerX )
4701 {
4702 // angle doesn't reach far enough for tangent; collapse and
4703 // connect directly to label
4704
4705 if ( keyY / Math.tan(angle) > 0 )
4706 {
4707 pointsX.push(keyY / Math.tan(angle));
4708 pointsY.push(keyY);
4709 }
4710 else
4711 {
4712 pointsX.push(bendRadius * Math.cos(angle));
4713 pointsY.push(bendRadius * Math.sin(angle));
4714 }
4715
4716 return angle;
4717 }
4718 else
4719 {
4720 return tanAngle;
4721 }
4722 }
4723 }
4724
4725 function keyOffset()
4726 {
4727 return imageHeight - (keys - currentKey + 1) * (keySize + keyBuffer) + keyBuffer - margin;
4728 }
4729
4730 function lerp(value, fromStart, fromEnd, toStart, toEnd)
4731 {
4732 return (value - fromStart) *
4733 (toEnd - toStart) /
4734 (fromEnd - fromStart) +
4735 toStart;
4736 }
4737
4738 function createCanvas()
4739 {
4740 canvas = document.createElement('canvas');
4741 document.body.appendChild(canvas);
4742 context = canvas.getContext('2d');
4743 }
4744
4745 function load()
4746 {
4747 document.body.style.overflow = "hidden";
4748 document.body.style.margin = 0;
4749
4750 createCanvas();
4751
4752 if ( context == undefined )
4753 {
4754 document.body.innerHTML = '\
4755 <br/>This browser does not support HTML5 (see \
4756 <a href="http://sourceforge.net/p/krona/wiki/Browser%20support/">Browser support</a>).\
4757 ';
4758 return;
4759 }
4760
4761 if ( typeof context.fillText != 'function' )
4762 {
4763 document.body.innerHTML = '\
4764 <br/>This browser does not support HTML5 canvas text (see \
4765 <a href="http://sourceforge.net/p/krona/wiki/Browser%20support/">Browser support</a>).\
4766 ';
4767 return;
4768 }
4769
4770 resize();
4771
4772 var kronaElement = document.getElementsByTagName('krona')[0];
4773
4774 var magnitudeName;
4775 var hueName;
4776 var hueDefault;
4777 var hueStart;
4778 var hueEnd;
4779 var valueStart;
4780 var valueEnd;
4781
4782 if ( kronaElement.getAttribute('collapse') != undefined )
4783 {
4784 collapse = kronaElement.getAttribute('collapse') == 'true';
4785 }
4786
4787 if ( kronaElement.getAttribute('key') != undefined )
4788 {
4789 showKeys = kronaElement.getAttribute('key') == 'true';
4790 }
4791
4792 for
4793 (
4794 var element = getFirstChild(kronaElement);
4795 element;
4796 element = getNextSibling(element)
4797 )
4798 {
4799 switch ( element.tagName.toLowerCase() )
4800 {
4801 case 'attributes':
4802 magnitudeName = element.getAttribute('magnitude');
4803 //
4804 for
4805 (
4806 var attributeElement = getFirstChild(element);
4807 attributeElement;
4808 attributeElement = getNextSibling(attributeElement)
4809 )
4810 {
4811 var tag = attributeElement.tagName.toLowerCase();
4812
4813 if ( tag == 'attribute' )
4814 {
4815 var attribute = new Attribute();
4816 attribute.name = attributeElement.firstChild.nodeValue.toLowerCase();
4817 attribute.displayName = attributeElement.getAttribute('display');
4818
4819 if ( attributeElement.getAttribute('hrefBase') )
4820 {
4821 attribute.hrefBase = attributeElement.getAttribute('hrefBase');
4822 }
4823
4824 if ( attributeElement.getAttribute('target') )
4825 {
4826 attribute.target = attributeElement.getAttribute('target');
4827 }
4828
4829 if ( attribute.name == magnitudeName )
4830 {
4831 magnitudeIndex = attributes.length;
4832 }
4833
4834 if ( attributeElement.getAttribute('listAll') )
4835 {
4836 attribute.listAll = attributeElement.getAttribute('listAll').toLowerCase();
4837 }
4838 else if ( attributeElement.getAttribute('listNode') )
4839 {
4840 attribute.listNode = attributeElement.getAttribute('listNode').toLowerCase();
4841 }
4842 else if ( attributeElement.getAttribute('dataAll') )
4843 {
4844 attribute.dataAll = attributeElement.getAttribute('dataAll').toLowerCase();
4845 }
4846 else if ( attributeElement.getAttribute('dataNode') )
4847 {
4848 attribute.dataNode = attributeElement.getAttribute('dataNode').toLowerCase();
4849 }
4850
4851 if ( attributeElement.getAttribute('postUrl') )
4852 {
4853 attribute.postUrl = attributeElement.getAttribute('postUrl');
4854 }
4855
4856 if ( attributeElement.getAttribute('postVar') )
4857 {
4858 attribute.postVar = attributeElement.getAttribute('postVar');
4859 }
4860
4861 if ( attributeElement.getAttribute('mono') )
4862 {
4863 attribute.mono = true;
4864 }
4865
4866 attributes.push(attribute);
4867 }
4868 else if ( tag == 'list' )
4869 {
4870 var attribute = new Attribute();
4871
4872 attribute.name = attributeElement.firstChild.nodeValue;
4873 attribute.list = true;
4874 attributes.push(attribute);
4875 }
4876 else if ( tag == 'data' )
4877 {
4878 var attribute = new Attribute();
4879
4880 attribute.name = attributeElement.firstChild.nodeValue;
4881 attribute.data = true;
4882 attributes.push(attribute);
4883
4884 var enableScript = document.createElement('script');
4885 var date = new Date();
4886 enableScript.src =
4887 attributeElement.getAttribute('enable') + '?' +
4888 date.getTime();
4889 document.body.appendChild(enableScript);
4890 }
4891 }
4892 break;
4893
4894 case 'color':
4895 hueName = element.getAttribute('attribute');
4896 hueStart = Number(element.getAttribute('hueStart')) / 360;
4897 hueEnd = Number(element.getAttribute('hueEnd')) / 360;
4898 valueStart = Number(element.getAttribute('valueStart'));
4899 valueEnd = Number(element.getAttribute('valueEnd'));
4900 //
4901 interpolateHue(hueStart, hueEnd, valueStart, valueEnd);
4902 //
4903 if ( element.getAttribute('default') == 'true' )
4904 {
4905 hueDefault = true;
4906 }
4907 break;
4908
4909 case 'datasets':
4910 datasetNames = new Array();
4911 //
4912 for ( j = getFirstChild(element); j; j = getNextSibling(j) )
4913 {
4914 datasetNames.push(j.firstChild.nodeValue);
4915 }
4916 datasets = datasetNames.length;
4917 break;
4918
4919 case 'node':
4920 head = loadTreeDOM
4921 (
4922 element,
4923 magnitudeName,
4924 hueName,
4925 hueStart,
4926 hueEnd,
4927 valueStart,
4928 valueEnd
4929 );
4930 break;
4931 }
4932 }
4933
4934 // get GET options
4935 //
4936 var urlHalves = String(document.location).split('?');
4937 var datasetDefault = 0;
4938 var maxDepthDefault;
4939 var nodeDefault = 0;
4940 //
4941 if ( urlHalves[1] )
4942 {
4943 var vars = urlHalves[1].split('&');
4944
4945 for ( i = 0; i < vars.length; i++ )
4946 {
4947 var pair = vars[i].split('=');
4948
4949 switch ( pair[0] )
4950 {
4951 case 'collapse':
4952 collapse = pair[1] == 'true';
4953 break;
4954
4955 case 'color':
4956 hueDefault = pair[1] == 'true';
4957 break;
4958
4959 case 'dataset':
4960 datasetDefault = Number(pair[1]);
4961 break;
4962
4963 case 'depth':
4964 maxDepthDefault = Number(pair[1]) + 1;
4965 break;
4966
4967 case 'key':
4968 showKeys = pair[1] == 'true';
4969 break;
4970
4971 case 'font':
4972 fontSize = Number(pair[1]);
4973 break;
4974
4975 case 'node':
4976 nodeDefault = Number(pair[1]);
4977 break;
4978
4979 default:
4980 getVariables.push(pair[0] + '=' + pair[1]);
4981 break;
4982 }
4983 }
4984 }
4985
4986 addOptionElements(hueName, hueDefault);
4987 setCallBacks();
4988
4989 head.sort();
4990 maxAbsoluteDepth = 0;
4991 selectDataset(datasetDefault);
4992
4993 if ( maxDepthDefault && maxDepthDefault < head.maxDepth )
4994 {
4995 maxAbsoluteDepth = maxDepthDefault;
4996 }
4997 else
4998 {
4999 maxAbsoluteDepth = head.maxDepth;
5000 }
5001
5002 selectNode(nodes[nodeDefault]);
5003
5004 setInterval(update, 20);
5005
5006 window.onresize = handleResize;
5007 updateMaxAbsoluteDepth();
5008 updateViewNeeded = true;
5009 }
5010
5011 function loadTreeDOM
5012 (
5013 domNode,
5014 magnitudeName,
5015 hueName,
5016 hueStart,
5017 hueEnd,
5018 valueStart,
5019 valueEnd
5020 )
5021 {
5022 var newNode = new Node();
5023
5024 newNode.name = domNode.getAttribute('name');
5025
5026 if ( domNode.getAttribute('href') )
5027 {
5028 newNode.href = domNode.getAttribute('href');
5029 }
5030
5031 if ( hueName )
5032 {
5033 newNode.hues = new Array();
5034 }
5035
5036 for ( var i = getFirstChild(domNode); i; i = getNextSibling(i) )
5037 {
5038 switch ( i.tagName.toLowerCase() )
5039 {
5040 case 'node':
5041 var newChild = loadTreeDOM
5042 (
5043 i,
5044 magnitudeName,
5045 hueName,
5046 hueStart,
5047 hueEnd,
5048 valueStart,
5049 valueEnd
5050 );
5051 newChild.parent = newNode;
5052 newNode.children.push(newChild);
5053 break;
5054
5055 default:
5056 var attributeName = i.tagName.toLowerCase();
5057 var index = attributeIndex(attributeName);
5058 //
5059 newNode.attributes[index] = new Array();
5060 //
5061 for ( var j = getFirstChild(i); j; j = getNextSibling(j) )
5062 {
5063 if ( attributes[index] == undefined )
5064 {
5065 var x = 5;
5066 }
5067 if ( attributes[index].list )
5068 {
5069 newNode.attributes[index].push(new Array());
5070
5071 for ( var k = getFirstChild(j); k; k = getNextSibling(k) )
5072 {
5073 newNode.attributes[index][newNode.attributes[index].length - 1].push(k.firstChild.nodeValue);
5074 }
5075 }
5076 else
5077 {
5078 var value = j.firstChild ? j.firstChild.nodeValue : '';
5079
5080 if ( j.getAttribute('href') )
5081 {
5082 var target;
5083
5084 if ( attributes[index].target )
5085 {
5086 target = ' target="' + attributes[index].target + '"';
5087 }
5088
5089 value = '<a href="' + attributes[index].hrefBase + j.getAttribute('href') + '"' + target + '>' + value + '</a>';
5090 }
5091
5092 newNode.attributes[index].push(value);
5093 }
5094 }
5095 //
5096 if ( attributeName == magnitudeName || attributeName == hueName )
5097 {
5098 for ( j = 0; j < datasets; j++ )
5099 {
5100 var value = newNode.attributes[index][j] == undefined ? 0 : Number(newNode.attributes[index][j]);
5101
5102 newNode.attributes[index][j] = value;
5103
5104 if ( attributeName == hueName )
5105 {
5106 var hue = lerp
5107 (
5108 value,
5109 valueStart,
5110 valueEnd,
5111 hueStart,
5112 hueEnd
5113 );
5114
5115 if ( hue < hueStart == hueStart < hueEnd )
5116 {
5117 hue = hueStart;
5118 }
5119 else if ( hue > hueEnd == hueStart < hueEnd )
5120 {
5121 hue = hueEnd;
5122 }
5123
5124 newNode.hues[j] = hue;
5125 }
5126 }
5127
5128 if ( attributeName == hueName )
5129 {
5130 newNode.hue = new Tween(newNode.hues[0], newNode.hues[0]);
5131 }
5132 }
5133 break;
5134 }
5135 }
5136
5137 return newNode;
5138 }
5139
5140 function maxAbsoluteDepthDecrease()
5141 {
5142 if ( maxAbsoluteDepth > 2 )
5143 {
5144 maxAbsoluteDepth--;
5145 head.setMaxDepths();
5146 handleResize();
5147 }
5148 }
5149
5150 function maxAbsoluteDepthIncrease()
5151 {
5152 if ( maxAbsoluteDepth < head.maxDepth )
5153 {
5154 maxAbsoluteDepth++;
5155 head.setMaxDepths();
5156 handleResize();
5157 }
5158 }
5159
5160 function measureText(text, bold)
5161 {
5162 context.font = bold ? fontBold : fontNormal;
5163 var dim = context.measureText(text);
5164 return dim.width;
5165 }
5166
5167 function min(a, b)
5168 {
5169 return a < b ? a : b;
5170 }
5171
5172 function minWidth()
5173 {
5174 // Min wedge width (at center) for displaying a node (or for displaying a
5175 // label if it's at the highest level being viewed, multiplied by 2 to make
5176 // further calculations simpler
5177
5178 return (fontSize * 2.3);
5179 }
5180
5181 function mouseMove(e)
5182 {
5183 mouseX = e.pageX;
5184 mouseY = e.pageY - headerHeight;
5185
5186 if ( head && ! quickLook )
5187 {
5188 checkHighlight();
5189 }
5190 }
5191
5192 function mouseClick(e)
5193 {
5194 if ( highlightedNode == focusNode && focusNode != selectedNode || selectedNode.hasParent(highlightedNode) )
5195 {
5196 if ( highlightedNode.hasChildren() )
5197 {
5198 expand(highlightedNode);
5199 }
5200 }
5201 else if ( progress == 1 )//( highlightedNode != selectedNode )
5202 {
5203 setFocus(highlightedNode);
5204 // document.body.style.cursor='ew-resize';
5205 draw();
5206 checkHighlight();
5207 var date = new Date();
5208 mouseDownTime = date.getTime();
5209 mouseDown = true;
5210 }
5211 }
5212
5213 function mouseUp(e)
5214 {
5215 if ( quickLook )
5216 {
5217 navigateBack();
5218 quickLook = false;
5219 }
5220
5221 mouseDown = false;
5222 }
5223
5224 function navigateBack()
5225 {
5226 if ( nodeHistoryPosition > 0 )
5227 {
5228 nodeHistory[nodeHistoryPosition] = selectedNode;
5229 nodeHistoryPosition--;
5230
5231 if ( nodeHistory[nodeHistoryPosition].collapse )
5232 {
5233 collapseCheckBox.checked = collapse = false;
5234 }
5235
5236 setSelectedNode(nodeHistory[nodeHistoryPosition]);
5237 updateDatasetButtons();
5238 updateView();
5239 }
5240 }
5241
5242 function navigateUp()
5243 {
5244 if ( selectedNode.getParent() )
5245 {
5246 selectNode(selectedNode.getParent());
5247 updateView();
5248 }
5249 }
5250
5251 function navigateForward()
5252 {
5253 if ( nodeHistoryPosition < nodeHistory.length - 1 )
5254 {
5255 nodeHistoryPosition++;
5256 var newNode = nodeHistory[nodeHistoryPosition];
5257
5258 if ( newNode.collapse )
5259 {
5260 collapseCheckBox.checked = collapse = false;
5261 }
5262
5263 if ( nodeHistoryPosition == nodeHistory.length - 1 )
5264 {
5265 // this will ensure the forward button is disabled
5266
5267 nodeHistory.length = nodeHistoryPosition;
5268 }
5269
5270 setSelectedNode(newNode);
5271 updateDatasetButtons();
5272 updateView();
5273 }
5274 }
5275
5276 function nextDataset()
5277 {
5278 var newDataset = currentDataset;
5279
5280 do
5281 {
5282 if ( newDataset == datasets - 1 )
5283 {
5284 newDataset = 0;
5285 }
5286 else
5287 {
5288 newDataset++;
5289 }
5290 }
5291 while ( datasetDropDown.options[newDataset].disabled )
5292
5293 selectDataset(newDataset);
5294 }
5295
5296 function onDatasetChange()
5297 {
5298 selectDataset(datasetDropDown.selectedIndex);
5299 }
5300
5301 function onKeyDown(event)
5302 {
5303 if
5304 (
5305 event.keyCode == 37 &&
5306 document.activeElement.id != 'search' &&
5307 document.activeElement.id != 'linkText'
5308 )
5309 {
5310 navigateBack();
5311 event.preventDefault();
5312 }
5313 else if
5314 (
5315 event.keyCode == 39 &&
5316 document.activeElement.id != 'search' &&
5317 document.activeElement.id != 'linkText'
5318 )
5319 {
5320 navigateForward();
5321 event.preventDefault();
5322 }
5323 else if ( event.keyCode == 38 && datasets > 1 )
5324 {
5325 prevDataset();
5326
5327 //if ( document.activeElement.id == 'datasets' )
5328 {
5329 event.preventDefault();
5330 }
5331 }
5332 else if ( event.keyCode == 40 && datasets > 1 )
5333 {
5334 nextDataset();
5335
5336 //if ( document.activeElement.id == 'datasets' )
5337 {
5338 event.preventDefault();
5339 }
5340 }
5341 else if ( event.keyCode == 9 && datasets > 1 )
5342 {
5343 selectLastDataset();
5344 event.preventDefault();
5345 }
5346 else if ( event.keyCode == 83 )
5347 {
5348 progress += .2;
5349 }
5350 else if ( event.keyCode == 66 )
5351 {
5352 progress -= .2;
5353 }
5354 else if ( event.keyCode == 70 )
5355 {
5356 progress = 1;
5357 }
5358 }
5359
5360 function onKeyPress(event)
5361 {
5362 if ( event.keyCode == 38 && datasets > 1 )
5363 {
5364 // prevDataset();
5365
5366 //if ( document.activeElement.id == 'datasets' )
5367 {
5368 event.preventDefault();
5369 }
5370 }
5371 else if ( event.keyCode == 40 && datasets > 1 )
5372 {
5373 // nextDataset();
5374
5375 //if ( document.activeElement.id == 'datasets' )
5376 {
5377 event.preventDefault();
5378 }
5379 }
5380 }
5381
5382 function onKeyUp(event)
5383 {
5384 if ( event.keyCode == 27 && document.activeElement.id == 'search' )
5385 {
5386 search.value = '';
5387 onSearchChange();
5388 }
5389 else if ( event.keyCode == 38 && datasets > 1 )
5390 {
5391 // prevDataset();
5392
5393 //if ( document.activeElement.id == 'datasets' )
5394 {
5395 event.preventDefault();
5396 }
5397 }
5398 else if ( event.keyCode == 40 && datasets > 1 )
5399 {
5400 // nextDataset();
5401
5402 //if ( document.activeElement.id == 'datasets' )
5403 {
5404 event.preventDefault();
5405 }
5406 }
5407 }
5408
5409 function onSearchChange()
5410 {
5411 nSearchResults = 0;
5412 head.search();
5413
5414 if ( search.value == '' )
5415 {
5416 searchResults.innerHTML = '';
5417 }
5418 else
5419 {
5420 searchResults.innerHTML = nSearchResults + ' results';
5421 }
5422
5423 setFocus(selectedNode);
5424 draw();
5425 }
5426
5427 function post(url, variable, value, postWindow)
5428 {
5429 var form = document.createElement('form');
5430 var input = document.createElement('input');
5431 var inputDataset = document.createElement('input');
5432
5433 form.appendChild(input);
5434 form.appendChild(inputDataset);
5435
5436 form.method = "POST";
5437 form.action = url;
5438
5439 if ( postWindow == undefined )
5440 {
5441 form.target = '_blank';
5442 postWindow = window;
5443 }
5444
5445 input.type = 'hidden';
5446 input.name = variable;
5447 input.value = value;
5448
5449 inputDataset.type = 'hidden';
5450 inputDataset.name = 'dataset';
5451 inputDataset.value = currentDataset;
5452
5453 postWindow.document.body.appendChild(form);
5454 form.submit();
5455 }
5456
5457 function prevDataset()
5458 {
5459 var newDataset = currentDataset;
5460
5461 do
5462 {
5463 if ( newDataset == 0 )
5464 {
5465 newDataset = datasets - 1;
5466 }
5467 else
5468 {
5469 newDataset--;
5470 }
5471 }
5472 while ( datasetDropDown.options[newDataset].disabled );
5473
5474 selectDataset(newDataset);
5475 }
5476
5477 function radiusDecrease()
5478 {
5479 if ( bufferFactor < .309 )
5480 {
5481 bufferFactor += .03;
5482 updateViewNeeded = true;
5483 }
5484 }
5485
5486 function radiusIncrease()
5487 {
5488 if ( bufferFactor > .041 )
5489 {
5490 bufferFactor -= .03;
5491 updateViewNeeded = true;
5492 }
5493 }
5494
5495 function resetKeyOffset()
5496 {
5497 currentKey = 1;
5498 keyMinTextLeft = centerX + gRadius + buffer - buffer / (keys + 1) / 2 + fontSize / 2;
5499 keyMinAngle = 0;
5500 }
5501
5502 function rgbText(r, g, b)
5503 {
5504 var rgbArray =
5505 [
5506 "rgb(",
5507 Math.floor(r),
5508 ",",
5509 Math.floor(g),
5510 ",",
5511 Math.floor(b),
5512 ")"
5513 ];
5514
5515 return rgbArray.join('');
5516 }
5517
5518 function round(number)
5519 {
5520 if ( number >= 1 || number <= -1 )
5521 {
5522 return number.toFixed(0);
5523 }
5524 else
5525 {
5526 return number.toPrecision(1);
5527 }
5528 }
5529
5530 function roundedRectangle(x, y, width, height, radius)
5531 {
5532 if ( radius * 2 > width )
5533 {
5534 radius = width / 2;
5535 }
5536
5537 if ( radius * 2 > height )
5538 {
5539 radius = height / 2;
5540 }
5541
5542 context.beginPath();
5543 context.arc(x + radius, y + radius, radius, Math.PI, Math.PI * 3 / 2, false);
5544 context.lineTo(x + width - radius, y);
5545 context.arc(x + width - radius, y + radius, radius, Math.PI * 3 / 2, Math.PI * 2, false);
5546 context.lineTo(x + width, y + height - radius);
5547 context.arc(x + width - radius, y + height - radius, radius, 0, Math.PI / 2, false);
5548 context.lineTo(x + radius, y + height);
5549 context.arc(x + radius, y + height - radius, radius, Math.PI / 2, Math.PI, false);
5550 context.lineTo(x, y + radius);
5551 }
5552
5553 function passClick(e)
5554 {
5555 mouseClick(e);
5556 }
5557
5558 function searchResultString(results)
5559 {
5560 var searchResults = this.searchResults;
5561
5562 if ( this.isSearchResult )
5563 {
5564 // don't count ourselves
5565 searchResults--;
5566 }
5567
5568 return ' - ' + results + (results > 1 ? ' results' : ' result');
5569 }
5570
5571 function setCallBacks()
5572 {
5573 canvas.onselectstart = function(){return false;} // prevent unwanted highlighting
5574 options.onselectstart = function(){return false;} // prevent unwanted highlighting
5575 document.onmousemove = mouseMove;
5576 window.onblur = focusLost;
5577 window.onmouseout = focusLost;
5578 document.onkeyup = onKeyUp;
5579 document.onkeydown = onKeyDown;
5580 canvas.onmousedown = mouseClick;
5581 document.onmouseup = mouseUp;
5582 keyControl.onclick = toggleKeys;
5583 collapseCheckBox = document.getElementById('collapse');
5584 collapseCheckBox.checked = collapse;
5585 collapseCheckBox.onclick = handleResize;
5586 collapseCheckBox.onmousedown = suppressEvent;
5587 maxAbsoluteDepthText = document.getElementById('maxAbsoluteDepth');
5588 maxAbsoluteDepthButtonDecrease = document.getElementById('maxAbsoluteDepthDecrease');
5589 maxAbsoluteDepthButtonIncrease = document.getElementById('maxAbsoluteDepthIncrease');
5590 maxAbsoluteDepthButtonDecrease.onclick = maxAbsoluteDepthDecrease;
5591 maxAbsoluteDepthButtonIncrease.onclick = maxAbsoluteDepthIncrease;
5592 maxAbsoluteDepthButtonDecrease.onmousedown = suppressEvent;
5593 maxAbsoluteDepthButtonIncrease.onmousedown = suppressEvent;
5594 fontSizeText = document.getElementById('fontSize');
5595 fontSizeButtonDecrease = document.getElementById('fontSizeDecrease');
5596 fontSizeButtonIncrease = document.getElementById('fontSizeIncrease');
5597 fontSizeButtonDecrease.onclick = fontSizeDecrease;
5598 fontSizeButtonIncrease.onclick = fontSizeIncrease;
5599 fontSizeButtonDecrease.onmousedown = suppressEvent;
5600 fontSizeButtonIncrease.onmousedown = suppressEvent;
5601 radiusButtonDecrease = document.getElementById('radiusDecrease');
5602 radiusButtonIncrease = document.getElementById('radiusIncrease');
5603 radiusButtonDecrease.onclick = radiusDecrease;
5604 radiusButtonIncrease.onclick = radiusIncrease;
5605 radiusButtonDecrease.onmousedown = suppressEvent;
5606 radiusButtonIncrease.onmousedown = suppressEvent;
5607 maxAbsoluteDepth = 0;
5608 backButton = document.getElementById('back');
5609 backButton.onclick = navigateBack;
5610 backButton.onmousedown = suppressEvent;
5611 forwardButton = document.getElementById('forward');
5612 forwardButton.onclick = navigateForward;
5613 forwardButton.onmousedown = suppressEvent;
5614 snapshotButton = document.getElementById('snapshot');
5615 snapshotButton.onclick = snapshot;
5616 snapshotButton.onmousedown = suppressEvent;
5617 detailsName = document.getElementById('detailsName');
5618 detailsExpand = document.getElementById('detailsExpand');
5619 detailsInfo = document.getElementById('detailsInfo');
5620 search = document.getElementById('search');
5621 search.onkeyup = onSearchChange;
5622 search.onmousedown = suppressEvent;
5623 searchResults = document.getElementById('searchResults');
5624 useHueDiv = document.getElementById('useHueDiv');
5625 linkButton = document.getElementById('linkButton');
5626 linkButton.onclick = showLink;
5627 linkButton.onmousedown = suppressEvent;
5628 linkText = document.getElementById('linkText');
5629 linkText.onblur = hideLink;
5630 linkText.onmousedown = suppressEvent;
5631 hide(linkText);
5632 var helpButton = document.getElementById('help');
5633 helpButton.onmousedown = suppressEvent;
5634 var searchClear = document.getElementById('searchClear');
5635 searchClear.onmousedown = suppressEvent;
5636 if ( datasets > 1 )
5637 {
5638 datasetDropDown.onmousedown = suppressEvent;
5639 var prevDatasetButton = document.getElementById('prevDataset');
5640 prevDatasetButton.onmousedown = suppressEvent;
5641 var nextDatasetButton = document.getElementById('nextDataset');
5642 nextDatasetButton.onmousedown = suppressEvent;
5643 var lastDatasetButton = document.getElementById('lastDataset');
5644 lastDatasetButton.onmousedown = suppressEvent;
5645 }
5646
5647 image = document.getElementById('hiddenImage');
5648
5649 if ( image.complete )
5650 {
5651 hiddenPattern = context.createPattern(image, 'repeat');
5652 }
5653 else
5654 {
5655 image.onload = function()
5656 {
5657 hiddenPattern = context.createPattern(image, 'repeat');
5658 }
5659 }
5660
5661 var loadingImageElement = document.getElementById('loadingImage');
5662
5663 if ( loadingImageElement )
5664 {
5665 loadingImage = loadingImageElement.src;
5666 }
5667 }
5668
5669 function selectDataset(newDataset)
5670 {
5671 lastDataset = currentDataset;
5672 currentDataset = newDataset
5673 if ( datasets > 1 )
5674 {
5675 datasetDropDown.selectedIndex = currentDataset;
5676 updateDatasetButtons();
5677 datasetAlpha.start = 1.5;
5678 datasetChanged = true;
5679 }
5680 head.setMagnitudes(0);
5681 head.setDepth(1, 1);
5682 head.setMaxDepths();
5683 handleResize();
5684 }
5685
5686 function selectLastDataset()
5687 {
5688 selectDataset(lastDataset);
5689 handleResize();
5690 }
5691
5692 function selectNode(newNode)
5693 {
5694 if ( selectedNode != newNode )
5695 {
5696 // truncate history at current location to create a new branch
5697 //
5698 nodeHistory.length = nodeHistoryPosition;
5699
5700 if ( selectedNode != 0 )
5701 {
5702 nodeHistory.push(selectedNode);
5703 nodeHistoryPosition++;
5704 }
5705
5706 setSelectedNode(newNode);
5707 //updateView();
5708 }
5709
5710 updateDatasetButtons();
5711 }
5712
5713 function setFocus(node)
5714 {
5715 if ( node == focusNode )
5716 {
5717 // return;
5718 }
5719
5720 focusNode = node;
5721
5722 if ( node.href )
5723 {
5724 detailsName.innerHTML =
5725 '<a target="_blank" href="' + node.href + '">' + node.name + '</a>';
5726 }
5727 else
5728 {
5729 detailsName.innerHTML = node.name;
5730 }
5731
5732 var table = '<table>';
5733
5734 table += '<tr><td></td></tr>';
5735
5736 for ( var i = 0; i < node.attributes.length; i++ )
5737 {
5738 if ( attributes[i].displayName && node.attributes[i] != undefined )
5739 {
5740 var index = node.attributes[i].length == 1 && attributes[i].mono ? 0 : currentDataset;
5741
5742 if ( typeof node.attributes[i][currentDataset] == 'number' || node.attributes[i][index] != undefined && node.attributes[i][currentDataset] != '' )
5743 {
5744 var value = node.attributes[i][index];
5745
5746 if ( attributes[i].listNode != undefined )
5747 {
5748 value =
5749 '<a href="" onclick="showList(' +
5750 attributeIndex(attributes[i].listNode) + ',' + i +
5751 ',false);return false;" title="Show list">' +
5752 value + '</a>';
5753 }
5754 else if ( attributes[i].listAll != undefined )
5755 {
5756 value =
5757 '<a href="" onclick="showList(' +
5758 attributeIndex(attributes[i].listAll) + ',' + i +
5759 ',true);return false;" title="Show list">' +
5760 value + '</a>';
5761 }
5762 else if ( attributes[i].dataNode != undefined && dataEnabled )
5763 {
5764 value =
5765 '<a href="" onclick="showData(' +
5766 attributeIndex(attributes[i].dataNode) + ',' + i +
5767 ',false);return false;" title="Show data">' +
5768 value + '</a>';
5769 }
5770 else if ( attributes[i].dataAll != undefined && dataEnabled )
5771 {
5772 value =
5773 '<a href="" onclick="showData(' +
5774 attributeIndex(attributes[i].dataAll) + ',' + i +
5775 ',true);return false;" title="Show data">' +
5776 value + '</a>';
5777 }
5778
5779 table +=
5780 '<tr><td><strong>' + attributes[i].displayName + ':</strong></td><td>' +
5781 value + '</td></tr>';
5782 }
5783 }
5784 }
5785
5786 table += '</table>';
5787 detailsInfo.innerHTML = table;
5788
5789 detailsExpand.disabled = !focusNode.hasChildren() || focusNode == selectedNode;
5790 }
5791
5792 function setSelectedNode(newNode)
5793 {
5794 if ( selectedNode && selectedNode.hasParent(newNode) )
5795 {
5796 zoomOut = true;
5797 }
5798 else
5799 {
5800 zoomOut = false;
5801 }
5802
5803 selectedNodeLast = selectedNode;
5804 selectedNode = newNode;
5805
5806 //if ( focusNode != selectedNode )
5807 {
5808 setFocus(selectedNode);
5809 }
5810 }
5811
5812 function waitForData(dataWindow, target, title, time, postUrl, postVar)
5813 {
5814 if ( nodeData.length == target )
5815 {
5816 if ( postUrl != undefined )
5817 {
5818 for ( var i = 0; i < nodeData.length; i++ )
5819 {
5820 nodeData[i] = nodeData[i].replace(/\n/g, ',');
5821 }
5822
5823 var postString = nodeData.join('');
5824 postString = postString.slice(0, -1);
5825
5826 dataWindow.document.body.removeChild(dataWindow.document.getElementById('loading'));
5827 document.body.removeChild(document.getElementById('data'));
5828
5829 post(postUrl, postVar, postString, dataWindow);
5830 }
5831 else
5832 {
5833 //dataWindow.document.body.removeChild(dataWindow.document.getElementById('loading'));
5834 //document.body.removeChild(document.getElementById('data'));
5835
5836 dataWindow.document.open();
5837 dataWindow.document.write('<pre>' + nodeData.join('') + '</pre>');
5838 dataWindow.document.close();
5839 }
5840
5841 dataWindow.document.title = title; // replace after document.write()
5842 }
5843 else
5844 {
5845 var date = new Date();
5846
5847 if ( date.getTime() - time > 10000 )
5848 {
5849 dataWindow.document.body.removeChild(dataWindow.document.getElementById('loading'));
5850 document.body.removeChild(document.getElementById('data'));
5851 dataWindow.document.body.innerHTML =
5852 'Timed out loading supplemental files for:<br/>' + document.location;
5853 }
5854 else
5855 {
5856 setTimeout(function() {waitForData(dataWindow, target, title, time, postUrl, postVar);}, 100);
5857 }
5858 }
5859 }
5860
5861 function data(newData)
5862 {
5863 nodeData.push(newData);
5864 }
5865
5866 function enableData()
5867 {
5868 dataEnabled = true;
5869 }
5870
5871 function showData(indexData, indexAttribute, summary)
5872 {
5873 var dataWindow = window.open('', '_blank');
5874 var title = 'Krona - ' + attributes[indexAttribute].displayName + ' - ' + focusNode.name;
5875 dataWindow.document.title = title;
5876
5877 nodeData = new Array();
5878
5879 if ( dataWindow && dataWindow.document && dataWindow.document.body != null )
5880 {
5881 //var loadImage = document.createElement('img');
5882 //loadImage.src = "file://localhost/Users/ondovb/Krona/KronaTools/img/loading.gif";
5883 //loadImage.id = "loading";
5884 //loadImage.alt = "Loading...";
5885 //dataWindow.document.body.appendChild(loadImage);
5886 dataWindow.document.body.innerHTML =
5887 '<img id="loading" src="' + loadingImage + '" alt="Loading..."></img>';
5888 }
5889
5890 var scripts = document.createElement('div');
5891 scripts.id = 'data';
5892 document.body.appendChild(scripts);
5893
5894 var files = focusNode.getData(indexData, summary);
5895
5896 var date = new Date();
5897 var time = date.getTime();
5898
5899 for ( var i = 0; i < files.length; i++ )
5900 {
5901 var script = document.createElement('script');
5902 script.src = files[i] + '?' + time;
5903 scripts.appendChild(script);
5904 }
5905
5906 waitForData(dataWindow, files.length, title, time, attributes[indexAttribute].postUrl, attributes[indexAttribute].postVar);
5907
5908 return false;
5909 }
5910
5911 function showList(indexList, indexAttribute, summary)
5912 {
5913 var list = focusNode.getList(indexList, summary);
5914
5915 if ( attributes[indexAttribute].postUrl != undefined )
5916 {
5917 post(attributes[indexAttribute].postUrl, attributes[indexAttribute].postVar, list.join(','));
5918 }
5919 else
5920 {
5921 var dataWindow = window.open('', '_blank');
5922
5923 if ( true || navigator.appName == 'Microsoft Internet Explorer' ) // :(
5924 {
5925 dataWindow.document.open();
5926 dataWindow.document.write('<pre>' + list.join('\n') + '</pre>');
5927 dataWindow.document.close();
5928 }
5929 else
5930 {
5931 var pre = document.createElement('pre');
5932 dataWindow.document.body.appendChild(pre);
5933 pre.innerHTML = list;
5934 }
5935
5936 dataWindow.document.title = 'Krona - ' + attributes[indexAttribute].displayName + ' - ' + focusNode.name;
5937 }
5938 }
5939
5940 function snapshot()
5941 {
5942 svg = svgHeader();
5943
5944 resetKeyOffset();
5945
5946 snapshotMode = true;
5947
5948 selectedNode.draw(false, true);
5949 selectedNode.draw(true, true);
5950
5951 if ( focusNode != 0 && focusNode != selectedNode )
5952 {
5953 context.globalAlpha = 1;
5954 focusNode.drawHighlight(true);
5955 }
5956
5957 if ( hueDisplayName && useHue() )
5958 {
5959 drawLegendSVG();
5960 }
5961
5962 snapshotMode = false;
5963
5964 svg += svgFooter();
5965
5966 snapshotWindow = window.open
5967 (
5968 'data:image/svg+xml;charset=utf-8,' + encodeURIComponent(svg),
5969 '_blank'
5970 );
5971 /* var data = window.open('data:text/plain;charset=utf-8,hello', '_blank');
5972 var data = window.open('', '_blank');
5973 data.document.open('text/plain');
5974 data.document.write('hello');
5975 data.document.close();
5976 var button = document.createElement('input');
5977 button.type = 'button';
5978 button.value = 'save';
5979 button.onclick = save;
5980 data.document.body.appendChild(button);
5981 // snapshotWindow.document.write(svg);
5982 // snapshotWindow.document.close();
5983 */
5984 }
5985
5986 function save()
5987 {
5988 alert(document.body.innerHTML);
5989 }
5990
5991 function spacer()
5992 {
5993 if ( snapshotMode )
5994 {
5995 return '&#160;&#160;&#160;';
5996 }
5997 else
5998 {
5999 return ' ';
6000 }
6001 }
6002
6003 function suppressEvent(e)
6004 {
6005 e.cancelBubble = true;
6006 if (e.stopPropagation) e.stopPropagation();
6007 }
6008
6009 function svgFooter()
6010 {
6011 return '</svg>';
6012 }
6013
6014 function svgHeader()
6015 {
6016 var patternWidth = fontSize * .6;//radius / 50;
6017
6018 return '\
6019 <?xml version="1.0" standalone="no"?>\
6020 <!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" \
6021 "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">\
6022 <svg width="' + imageWidth + '" height="' + imageHeight + '" version="1.1"\
6023 xmlns="http://www.w3.org/2000/svg">\
6024 <title>Krona (snapshot) - ' +
6025 (datasets > 1 ? datasetNames[currentDataset] + ' - ' : '') + selectedNode.name +
6026 '</title>\
6027 <defs>\
6028 <style type="text/css">\
6029 text {font-size: ' + fontSize + 'px; font-family: ' + fontFamily + '; dominant-baseline:central}\
6030 path {stroke-width:' + thinLineWidth * fontSize / 12 + ';}\
6031 path.wedge {stroke:none}\
6032 path.line {fill:none;stroke:black;}\
6033 line {stroke:black;stroke-width:' + thinLineWidth * fontSize / 12 + ';}\
6034 line.tick {stroke-width:' + thinLineWidth * fontSize / 6 + ';}\
6035 line.pattern {stroke-width:' + thinLineWidth * fontSize / 18 + ';}\
6036 circle {fill:none;stroke:black;stroke-width:' + thinLineWidth * fontSize / 12 + ';}\
6037 rect {stroke:black;stroke-width:' + thinLineWidth * fontSize / 12 + ';}\
6038 .highlight {stroke:black;stroke-width:'+ highlightLineWidth * fontSize / 12 + ';}\
6039 .searchHighlight {fill:rgb(255, 255, 100);stroke:none;}\
6040 </style>\
6041 <pattern id="hiddenPattern" patternUnits="userSpaceOnUse" \
6042 x="0" y="0" width="' + patternWidth + '" height="' + patternWidth + '">\
6043 <line class="pattern" x1="0" y1="0" x2="' + patternWidth / 2 + '" y2="' + patternWidth / 2 + '"/>\
6044 <line class="pattern" x1="' + patternWidth / 2 + '" y1="' + patternWidth +
6045 '" x2="' + patternWidth + '" y2="' + patternWidth / 2 + '"/>\
6046 </pattern>\
6047 </defs>\
6048 ';
6049 }
6050
6051 function svgText(text, x, y, anchor, bold, color)
6052 {
6053 if ( typeof(anchor) == 'undefined' )
6054 {
6055 anchor = 'start';
6056 }
6057
6058 if ( color == undefined )
6059 {
6060 color = 'black';
6061 }
6062
6063 return '<text x="' + x + '" y="' + y +
6064 '" style="font-color:' + color + ';font-weight:' + (bold ? 'bold' : 'normal') +
6065 '" text-anchor="' + anchor + '">' + text + '</text>';
6066 }
6067
6068 function toggleKeys()
6069 {
6070 if ( showKeys )
6071 {
6072 keyControl.value = '…';
6073 showKeys = false;
6074 }
6075 else
6076 {
6077 keyControl.value = 'x';
6078 showKeys = true;
6079 }
6080
6081 updateKeyControl();
6082
6083 if ( progress == 1 )
6084 {
6085 draw();
6086 }
6087 }
6088
6089 function update()
6090 {
6091 if ( ! head )
6092 {
6093 return;
6094 }
6095
6096 if ( mouseDown && focusNode != selectedNode )
6097 {
6098 var date = new Date();
6099
6100 if ( date.getTime() - mouseDownTime > quickLookHoldLength )
6101 {
6102 if ( focusNode.hasChildren() )
6103 {
6104 expand(focusNode);
6105 quickLook = true;
6106 }
6107 }
6108 }
6109
6110 if ( updateViewNeeded )
6111 {
6112 resize();
6113 mouseX = -1;
6114 mouseY = -1;
6115
6116 collapse = collapseCheckBox.checked;
6117 compress = true;//compressCheckBox.checked;
6118 shorten = true;//shortenCheckBox.checked;
6119
6120 checkSelectedCollapse();
6121 updateMaxAbsoluteDepth();
6122
6123 if ( focusNode.getCollapse() || focusNode.depth > maxAbsoluteDepth )
6124 {
6125 setFocus(selectedNode);
6126 }
6127 else
6128 {
6129 setFocus(focusNode);
6130 }
6131
6132 updateView();
6133
6134 updateViewNeeded = false;
6135 }
6136
6137 var date = new Date();
6138 progress = (date.getTime() - tweenStartTime) / tweenLength;
6139 // progress += .01;
6140
6141 if ( progress >= 1 )
6142 {
6143 progress = 1;
6144 }
6145
6146 if ( progress != progressLast )
6147 {
6148 tweenFactor =// progress;
6149 (1 / (1 + Math.exp(-tweenCurvature * (progress - .5))) - .5) /
6150 (tweenMax - .5) / 2 + .5;
6151
6152 if ( progress == 1 )
6153 {
6154 snapshotButton.disabled = false;
6155 zoomOut = false;
6156
6157 //updateKeyControl();
6158
6159 if ( ! quickLook )
6160 {
6161 //checkHighlight();
6162 }
6163
6164
6165 if ( fpsDisplay )
6166 {
6167 fpsDisplay.innerHTML = 'fps: ' + Math.round(tweenFrames * 1000 / tweenLength);
6168 }
6169 }
6170
6171 draw();
6172 }
6173
6174 progressLast = progress;
6175 }
6176
6177 function updateDatasetButtons()
6178 {
6179 if ( datasets == 1 )
6180 {
6181 return;
6182 }
6183
6184 var node = selectedNode ? selectedNode : head;
6185
6186 datasetButtonLast.disabled =
6187 node.attributes[magnitudeIndex][lastDataset] == 0;
6188
6189 datasetButtonPrev.disabled = true;
6190 datasetButtonNext.disabled = true;
6191
6192 for ( var i = 0; i < datasets; i++ )
6193 {
6194 var disable = node.attributes[magnitudeIndex][i] == 0;
6195
6196 datasetDropDown.options[i].disabled = disable;
6197
6198 if ( ! disable )
6199 {
6200 if ( i != currentDataset )
6201 {
6202 datasetButtonPrev.disabled = false;
6203 datasetButtonNext.disabled = false;
6204 }
6205 }
6206 }
6207 }
6208
6209 function updateDatasetWidths()
6210 {
6211 if ( datasets > 1 )
6212 {
6213 for ( var i = 0; i < datasets; i++ )
6214 {
6215 context.font = fontBold;
6216 var dim = context.measureText(datasetNames[i]);
6217 datasetWidths[i] = dim.width;
6218 }
6219 }
6220 }
6221
6222 function updateKeyControl()
6223 {
6224 if ( keys == 0 )//|| progress != 1 )
6225 {
6226 keyControl.style.visibility = 'hidden';
6227 }
6228 else
6229 {
6230 keyControl.style.visibility = 'visible';
6231 keyControl.style.right = margin + 'px';
6232
6233 if ( showKeys )
6234 {
6235 keyControl.style.top =
6236 imageHeight -
6237 (
6238 keys * (keySize + keyBuffer) -
6239 keyBuffer +
6240 margin +
6241 keyControl.clientHeight * 1.5
6242 ) + 'px';
6243 }
6244 else
6245 {
6246 keyControl.style.top =
6247 (imageHeight - margin - keyControl.clientHeight) + 'px';
6248 }
6249 }
6250 }
6251
6252 function updateView()
6253 {
6254 if ( selectedNode.depth > maxAbsoluteDepth - 1 )
6255 {
6256 maxAbsoluteDepth = selectedNode.depth + 1;
6257 }
6258
6259 highlightedNode = selectedNode;
6260
6261 angleFactor = 2 * Math.PI / (selectedNode.magnitude);
6262
6263 maxPossibleDepth = Math.floor(gRadius / (fontSize * minRingWidthFactor));
6264
6265 if ( maxPossibleDepth < 4 )
6266 {
6267 maxPossibleDepth = 4;
6268 }
6269
6270 var minRadiusInner = fontSize * 8 / gRadius;
6271 var minRadiusFirst = fontSize * 6 / gRadius;
6272 var minRadiusOuter = fontSize * 5 / gRadius;
6273
6274 if ( .25 < minRadiusInner )
6275 {
6276 minRadiusInner = .25;
6277 }
6278
6279 if ( .15 < minRadiusFirst )
6280 {
6281 minRadiusFirst = .15;
6282 }
6283
6284 if ( .15 < minRadiusOuter )
6285 {
6286 minRadiusOuter = .15;
6287 }
6288
6289 // visibility of nodes depends on the depth they are displayed at,
6290 // so we need to set the max depth assuming they can all be displayed
6291 // and iterate it down based on the deepest child node we can display
6292 //
6293 var maxDepth;
6294 var newMaxDepth = selectedNode.getMaxDepth() - selectedNode.getDepth() + 1;
6295 //
6296 do
6297 {
6298 maxDepth = newMaxDepth;
6299
6300 if ( ! compress && maxDepth > maxPossibleDepth )
6301 {
6302 maxDepth = maxPossibleDepth;
6303 }
6304
6305 if ( compress )
6306 {
6307 compressedRadii = new Array(maxDepth);
6308
6309 compressedRadii[0] = minRadiusInner;
6310
6311 var offset = 0;
6312
6313 while
6314 (
6315 lerp
6316 (
6317 Math.atan(offset + 2),
6318 Math.atan(offset + 1),
6319 Math.atan(maxDepth + offset - 1),
6320 minRadiusInner,
6321 1 - minRadiusOuter
6322 ) - minRadiusInner > minRadiusFirst &&
6323 offset < 10
6324 )
6325 {
6326 offset++;
6327 }
6328
6329 offset--;
6330
6331 for ( var i = 1; i < maxDepth; i++ )
6332 {
6333 compressedRadii[i] = lerp
6334 (
6335 Math.atan(i + offset),
6336 Math.atan(offset),
6337 Math.atan(maxDepth + offset - 1),
6338 minRadiusInner,
6339 1 - minRadiusOuter
6340 )
6341 }
6342 }
6343 else
6344 {
6345 nodeRadius = 1 / maxDepth;
6346 }
6347
6348 newMaxDepth = selectedNode.maxVisibleDepth(maxDepth);
6349
6350 if ( compress )
6351 {
6352 if ( newMaxDepth <= maxPossibleDepth )
6353 {
6354 // compress
6355 }
6356 }
6357 else
6358 {
6359 if ( newMaxDepth > maxPossibleDepth )
6360 {
6361 newMaxDepth = maxPossibleDepth;
6362 }
6363 }
6364 }
6365 while ( newMaxDepth < maxDepth );
6366
6367 maxDisplayDepth = maxDepth;
6368
6369 lightnessFactor = (lightnessMax - lightnessBase) / (maxDepth > 8 ? 8 : maxDepth);
6370 keys = 0;
6371
6372 nLabelOffsets = new Array(maxDisplayDepth - 1);
6373 labelOffsets = new Array(maxDisplayDepth - 1);
6374 labelLastNodes = new Array(maxDisplayDepth - 1);
6375 labelFirstNodes = new Array(maxDisplayDepth - 1);
6376
6377 for ( var i = 0; i < maxDisplayDepth - 1; i++ )
6378 {
6379 if ( compress )
6380 {
6381 if ( i == maxDisplayDepth - 1 )
6382 {
6383 nLabelOffsets[i] = 0;
6384 }
6385 else
6386 {
6387 var width =
6388 (compressedRadii[i + 1] - compressedRadii[i]) *
6389 gRadius;
6390
6391 nLabelOffsets[i] = Math.floor(width / fontSize / 1.2);
6392
6393 if ( nLabelOffsets[i] > 2 )
6394 {
6395 nLabelOffsets[i] = min
6396 (
6397 Math.floor(width / fontSize / 1.75),
6398 5
6399 );
6400 }
6401 }
6402 }
6403 else
6404 {
6405 nLabelOffsets[i] = Math.max
6406 (
6407 Math.floor(Math.sqrt((nodeRadius * gRadius / fontSize)) * 1.5),
6408 3
6409 );
6410 }
6411
6412 labelOffsets[i] = Math.floor((nLabelOffsets[i] - 1) / 2);
6413 labelLastNodes[i] = new Array(nLabelOffsets[i] + 1);
6414 labelFirstNodes[i] = new Array(nLabelOffsets[i] + 1);
6415
6416 for ( var j = 0; j <= nLabelOffsets[i]; j++ )
6417 {
6418 // these arrays will allow nodes with neighboring labels to link to
6419 // each other to determine max label length
6420
6421 labelLastNodes[i][j] = 0;
6422 labelFirstNodes[i][j] = 0;
6423 }
6424 }
6425
6426 fontSizeText.innerHTML = fontSize;
6427 fontNormal = fontSize + 'px ' + fontFamily;
6428 context.font = fontNormal;
6429 fontBold = 'bold ' + fontSize + 'px ' + fontFamily;
6430 tickLength = fontSize * .7;
6431
6432 head.setTargets(0);
6433
6434 keySize = ((imageHeight - margin * 3) * 1 / 2) / keys * 3 / 4;
6435
6436 if ( keySize > fontSize * maxKeySizeFactor )
6437 {
6438 keySize = fontSize * maxKeySizeFactor;
6439 }
6440
6441 keyBuffer = keySize / 3;
6442
6443 fontSizeLast = fontSize;
6444
6445 if ( datasetChanged )
6446 {
6447 datasetChanged = false;
6448 }
6449 else
6450 {
6451 datasetAlpha.start = 0;
6452 }
6453
6454 var date = new Date();
6455 tweenStartTime = date.getTime();
6456 progress = 0;
6457 tweenFrames = 0;
6458
6459 updateKeyControl();
6460 updateDatasetWidths();
6461
6462 document.title = 'Krona - ' + selectedNode.name;
6463 updateNavigationButtons();
6464 snapshotButton.disabled = true;
6465
6466 maxAbsoluteDepthText.innerHTML = maxAbsoluteDepth - 1;
6467
6468 maxAbsoluteDepthButtonDecrease.disabled = (maxAbsoluteDepth == 2);
6469 maxAbsoluteDepthButtonIncrease.disabled = (maxAbsoluteDepth == head.maxDepth);
6470
6471 if ( collapse != collapseLast && search.value != '' )
6472 {
6473 onSearchChange();
6474 collapseLast = collapse;
6475 }
6476 }
6477
6478 function updateMaxAbsoluteDepth()
6479 {
6480 while ( selectedNode.depth > maxAbsoluteDepth - 1 )
6481 {
6482 selectedNode = selectedNode.getParent();
6483 }
6484 }
6485
6486 function updateNavigationButtons()
6487 {
6488 backButton.disabled = (nodeHistoryPosition == 0);
6489 // upButton.disabled = (selectedNode.getParent() == 0);
6490 forwardButton.disabled = (nodeHistoryPosition == nodeHistory.length);
6491 }
6492
6493 function useHue()
6494 {
6495 return useHueCheckBox && useHueCheckBox.checked;
6496 }
6497 /*
6498 function zoomOut()
6499 {
6500 return (
6501 selectedNodeLast != 0 &&
6502 selectedNodeLast.getDepth() < selectedNode.getDepth());
6503 }
6504 */
6505 </script>
6506 </head>
6507 <body>
6508 <img id="hiddenImage" src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAgAAAAICAYAAADED76LAAAAAXNSR0IArs4c6QAAAAZiS0dEAP8A/wD/oL2nkwAAAAlwSFlzAAALEwAACxMBAJqcGAAAAAd0SU1FB9oLCBQhNQwWVnsAAAAidEVYdENvbW1lbnQAQ3JlYXRlZCB3aXRoIEdJTVAgb24gYSBNYWOHqHdDAAABE0lEQVQYGQEIAff+AwAAABkAAAAAAAAA+gAAAAAAAAAAAAAAAAAAAAAAAAAMAwAAAAAAAAANAAAAAAAAAPoAAAAAAAAADAAAAAYAAAD0AwAAAPoAAAAAAAAAAAAAAPoAAAAMAAAADQAAAPoAAAD6AAAAAAAAAAAAAAAAAAAAAAwAAAAZAAAADAAAAAAAAAAAAAAAAAAAAAAAAAAADAAAABkAAAAMAAAAAAAAAAAAAAAAAAAAAAAAAAAMAAAAGQAAAAwAAAAAAAAADAAAAAwAAAAABAAAAAAAAAAAAAAA8wAAAPQAAAAAAAAAAAAAAA0AAAAAAAAAAAwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAwAAAAZRssKC5OpXwYAAAAASUVORK5CYII=" style="display:none"/>
6509 <img id="loadingImage" src="data:image/gif;base64,R0lGODlhEAAQAPIAAP///wAAAMLCwkJCQgAAAGJiYoKCgpKSkiH/C05FVFNDQVBFMi4wAwEAAAAh/hpDcmVhdGVkIHdpdGggYWpheGxvYWQuaW5mbwAh+QQJCgAAACwAAAAAEAAQAAADMwi63P4wyklrE2MIOggZnAdOmGYJRbExwroUmcG2LmDEwnHQLVsYOd2mBzkYDAdKa+dIAAAh+QQJCgAAACwAAAAAEAAQAAADNAi63P5OjCEgG4QMu7DmikRxQlFUYDEZIGBMRVsaqHwctXXf7WEYB4Ag1xjihkMZsiUkKhIAIfkECQoAAAAsAAAAABAAEAAAAzYIujIjK8pByJDMlFYvBoVjHA70GU7xSUJhmKtwHPAKzLO9HMaoKwJZ7Rf8AYPDDzKpZBqfvwQAIfkECQoAAAAsAAAAABAAEAAAAzMIumIlK8oyhpHsnFZfhYumCYUhDAQxRIdhHBGqRoKw0R8DYlJd8z0fMDgsGo/IpHI5TAAAIfkECQoAAAAsAAAAABAAEAAAAzIIunInK0rnZBTwGPNMgQwmdsNgXGJUlIWEuR5oWUIpz8pAEAMe6TwfwyYsGo/IpFKSAAAh+QQJCgAAACwAAAAAEAAQAAADMwi6IMKQORfjdOe82p4wGccc4CEuQradylesojEMBgsUc2G7sDX3lQGBMLAJibufbSlKAAAh+QQJCgAAACwAAAAAEAAQAAADMgi63P7wCRHZnFVdmgHu2nFwlWCI3WGc3TSWhUFGxTAUkGCbtgENBMJAEJsxgMLWzpEAACH5BAkKAAAALAAAAAAQABAAAAMyCLrc/jDKSatlQtScKdceCAjDII7HcQ4EMTCpyrCuUBjCYRgHVtqlAiB1YhiCnlsRkAAAOwAAAAAAAAAAAA==" style="display:none"/>
6510 <img id="logo" src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAKRGlDQ1BJQ0MgUHJvZmlsZQAAeAGdlndUFNcXx9/MbC+0XZYiZem9twWkLr1IlSYKy+4CS1nWZRewN0QFIoqICFYkKGLAaCgSK6JYCAgW7AEJIkoMRhEVlczGHPX3Oyf5/U7eH3c+8333nnfn3vvOGQAoASECYQ6sAEC2UCKO9PdmxsUnMPG9AAZEgAM2AHC4uaLQKL9ogK5AXzYzF3WS8V8LAuD1LYBaAK5bBIQzmX/p/+9DkSsSSwCAwtEAOx4/l4tyIcpZ+RKRTJ9EmZ6SKWMYI2MxmiDKqjJO+8Tmf/p8Yk8Z87KFPNRHlrOIl82TcRfKG/OkfJSREJSL8gT8fJRvoKyfJc0WoPwGZXo2n5MLAIYi0yV8bjrK1ihTxNGRbJTnAkCgpH3FKV+xhF+A5gkAO0e0RCxIS5cwjbkmTBtnZxYzgJ+fxZdILMI53EyOmMdk52SLOMIlAHz6ZlkUUJLVlokW2dHG2dHRwtYSLf/n9Y+bn73+GWS9/eTxMuLPnkGMni/al9gvWk4tAKwptDZbvmgpOwFoWw+A6t0vmv4+AOQLAWjt++p7GLJ5SZdIRC5WVvn5+ZYCPtdSVtDP6386fPb8e/jqPEvZeZ9rx/Thp3KkWRKmrKjcnKwcqZiZK+Jw+UyL/x7ifx34VVpf5WEeyU/li/lC9KgYdMoEwjS03UKeQCLIETIFwr/r8L8M+yoHGX6aaxRodR8BPckSKPTRAfJrD8DQyABJ3IPuQJ/7FkKMAbKbF6s99mnuUUb3/7T/YeAy9BXOFaQxZTI7MprJlYrzZIzeCZnBAhKQB3SgBrSAHjAGFsAWOAFX4Al8QRAIA9EgHiwCXJAOsoEY5IPlYA0oAiVgC9gOqsFeUAcaQBM4BtrASXAOXARXwTVwE9wDQ2AUPAOT4DWYgSAID1EhGqQGaUMGkBlkC7Egd8gXCoEioXgoGUqDhJAUWg6tg0qgcqga2g81QN9DJ6Bz0GWoH7oDDUPj0O/QOxiBKTAd1oQNYSuYBXvBwXA0vBBOgxfDS+FCeDNcBdfCR+BW+Bx8Fb4JD8HP4CkEIGSEgeggFggLYSNhSAKSioiRlUgxUonUIk1IB9KNXEeGkAnkLQaHoWGYGAuMKyYAMx/DxSzGrMSUYqoxhzCtmC7MdcwwZhLzEUvFamDNsC7YQGwcNg2bjy3CVmLrsS3YC9ib2FHsaxwOx8AZ4ZxwAbh4XAZuGa4UtxvXjDuL68eN4KbweLwa3gzvhg/Dc/ASfBF+J/4I/gx+AD+Kf0MgE7QJtgQ/QgJBSFhLqCQcJpwmDBDGCDNEBaIB0YUYRuQRlxDLiHXEDmIfcZQ4Q1IkGZHcSNGkDNIaUhWpiXSBdJ/0kkwm65KdyRFkAXk1uYp8lHyJPEx+S1GimFLYlESKlLKZcpBylnKH8pJKpRpSPakJVAl1M7WBep76kPpGjiZnKRcox5NbJVcj1yo3IPdcnihvIO8lv0h+qXyl/HH5PvkJBaKCoQJbgaOwUqFG4YTCoMKUIk3RRjFMMVuxVPGw4mXFJ0p4JUMlXyWeUqHSAaXzSiM0hKZHY9O4tHW0OtoF2igdRzeiB9Iz6CX07+i99EllJWV75RjlAuUa5VPKQwyEYcgIZGQxyhjHGLcY71Q0VbxU+CqbVJpUBlSmVeeoeqryVYtVm1Vvqr5TY6r5qmWqbVVrU3ugjlE3VY9Qz1ffo35BfWIOfY7rHO6c4jnH5tzVgDVMNSI1lmkc0OjRmNLU0vTXFGnu1DyvOaHF0PLUytCq0DqtNa5N03bXFmhXaJ/RfspUZnoxs5hVzC7mpI6GToCOVGe/Tq/OjK6R7nzdtbrNug/0SHosvVS9Cr1OvUl9bf1Q/eX6jfp3DYgGLIN0gx0G3QbThkaGsYYbDNsMnxipGgUaLTVqNLpvTDX2MF5sXGt8wwRnwjLJNNltcs0UNnUwTTetMe0zg80czQRmu836zbHmzuZC81rzQQuKhZdFnkWjxbAlwzLEcq1lm+VzK32rBKutVt1WH60drLOs66zv2SjZBNmstemw+d3W1JZrW2N7w45q52e3yq7d7oW9mT3ffo/9bQeaQ6jDBodOhw+OTo5ixybHcSd9p2SnXU6DLDornFXKuuSMdfZ2XuV80vmti6OLxOWYy2+uFq6Zroddn8w1msufWzd3xE3XjeO2323Ineme7L7PfchDx4PjUevxyFPPk+dZ7znmZeKV4XXE67m3tbfYu8V7mu3CXsE+64P4+PsU+/T6KvnO9632fein65fm1+g36e/gv8z/bAA2IDhga8BgoGYgN7AhcDLIKWhFUFcwJTgquDr4UYhpiDikIxQODQrdFnp/nsE84by2MBAWGLYt7EG4Ufji8B8jcBHhETURjyNtIpdHdkfRopKiDke9jvaOLou+N994vnR+Z4x8TGJMQ8x0rE9seexQnFXcirir8erxgvj2BHxCTEJ9wtQC3wXbF4wmOiQWJd5aaLSwYOHlReqLshadSpJP4iQdT8YmxyYfTn7PCePUcqZSAlN2pUxy2dwd3Gc8T14Fb5zvxi/nj6W6pZanPklzS9uWNp7ukV6ZPiFgC6oFLzICMvZmTGeGZR7MnM2KzWrOJmQnZ58QKgkzhV05WjkFOf0iM1GRaGixy+LtiyfFweL6XCh3YW67hI7+TPVIjaXrpcN57nk1eW/yY/KPFygWCAt6lpgu2bRkbKnf0m+XYZZxl3Uu11m+ZvnwCq8V+1dCK1NWdq7SW1W4anS1/+pDa0hrMtf8tNZ6bfnaV+ti13UUahauLhxZ77++sUiuSFw0uMF1w96NmI2Cjb2b7Dbt3PSxmFd8pcS6pLLkfSm39Mo3Nt9UfTO7OXVzb5lj2Z4tuC3CLbe2emw9VK5YvrR8ZFvottYKZkVxxavtSdsvV9pX7t1B2iHdMVQVUtW+U3/nlp3vq9Orb9Z41zTv0ti1adf0bt7ugT2ee5r2au4t2ftun2Df7f3++1trDWsrD+AO5B14XBdT1/0t69uGevX6kvoPB4UHhw5FHupqcGpoOKxxuKwRbpQ2jh9JPHLtO5/v2pssmvY3M5pLjoKj0qNPv0/+/tax4GOdx1nHm34w+GFXC62luBVqXdI62ZbeNtQe395/IuhEZ4drR8uPlj8ePKlzsuaU8qmy06TThadnzyw9M3VWdHbiXNq5kc6kznvn487f6Iro6r0QfOHSRb+L57u9us9ccrt08rLL5RNXWFfarjpebe1x6Gn5yeGnll7H3tY+p772a87XOvrn9p8e8Bg4d93n+sUbgTeu3px3s//W/Fu3BxMHh27zbj+5k3Xnxd28uzP3Vt/H3i9+oPCg8qHGw9qfTX5uHnIcOjXsM9zzKOrRvRHuyLNfcn95P1r4mPq4ckx7rOGJ7ZOT437j154ueDr6TPRsZqLoV8Vfdz03fv7Db56/9UzGTY6+EL+Y/b30pdrLg6/sX3VOhU89fJ39ema6+I3am0NvWW+738W+G5vJf49/X/XB5EPHx+CP92ezZ2f/AAOY8/xJsCmYAAAACXBIWXMAAAsTAAALEwEAmpwYAAAG40lEQVRIDXVWDWxT1xU+993n//g/tpOYJDQNPylkawmturZbK1SaULExaRvQqVuRNmnrxkgZfxGUIcqI6DaRUqjo1nZCqBNDa7UytVMZE1W3ZaCMpu2AJQXCsB2SOSROnNjPfr935zwnqNrPs6x73/XV993zne+cayaEgE89DB/ANVp0v3vmzIOKZa0ez008lM+ONWeHhwPV4Vg5HqlNxWvifS4Pf2dlx8qzuHdiFkPC0Zqd2wP7FAHDFZvtL+fPP9LX378tPzzc0TCZl5oCAVANAW9PpGHB2nngccuQvTEF2s0AVHvq+5e0Ljr0aPujxwlx79690p49e26TzBHMgfNfHDu28/rlyzseEMzXGgpBtCpg+V0OKZsvwpH0JWjbsRgSNX4oFQxRmC7ByNUJlu61IBlqen3j5qe3IMcYqiDhwW0SCuk2+AtHj/aMfdD/3FNVQd/KhkazLhgSTFiSompQMnQwLQuBS0DAhqWzQMTD7nqgwbzvyShMuq49eWDfz04iXoLAKRKc2+A2W/ehQ9vgytWffCNZDwm/3yzpOp+TT5YkGCso8MrwILR8vwFiMR+SCZBwXZZlcHtcQi2bVt9bN7hIxU8+u69rPYHjUwnl8LFj908NDHY9UTcP4lVVZlFVucDTYraBRssy8WtR8sHQddApGsOw10zThJKiMqeLS/eubgSlamTdwecPf8dGJ61wIg0ODm7+cnUsUuPzmQqCE3AF0LJlIR6O7pAYLqM4pqELAwkMIjKJiEjKzBNwmG1fTEIme/2HmUymjqTin2jafS3TMwceicVlGRkNy8KcICL6CVUAPIGQQLCRksL6pnLMe4eDeYMyA7SHZQo0Ne3AfciOHykQ9QqlVK5++7Xe1ONrHuuToz5fe5slXAGn01J0HbcRMBEI4Khg0dDZ2eyoekXX3i2CnB76oxIaTRTaFzwYjodrPJgIwbhTAhJD01BGUzPdVWVZKUytQKijsnd8/OGwP4ihmphs0tk+v32uMoKfyqTUfzQku3dteualpN9PBeV+4bmXv/Lh7849f+9XE0mHbAlDLbDJzCQM/XUcVB7nlj8GctJa+uPduxtlRy7XEozGQMdkCdIE47VwdHIuBgrT7EOf9/hru3bvxx9M/NJTfuZH3/1V1/ZM6NyJoSNaXmUlaZ7wL/0M83++DkJ1NeAJB+Hm6TONvZs2RyQ3sJgb7UYeJ1kI3MJI6D1XVuGXo6PkbXP58uUOTBFbu3Yt5hvg+k+7TxT5/Hzjt7dC68anxMIvPQbzPtcGocYkc/q8gvt8DgzXK9OZCRQrqjLa+iNZpScR1v98Argqx8MQakiCrmlgmWjnogLAuV0f2NFApj1lYd3CKq11YMHQyW8nGSMIOmT4Vm3tOtz33oULF/RZJluqui1d62ei0aBaKFKtoJnIQxIOTJiqyvTJvB4EUCQjEhmYVFVKaqWo8CSU6bJhska3B5bkp7+5af/+Z2/OzERnCVw9PXu//vd/DewMNt9JbhVYFAw1BWGY9A4GSutVSunH9+3LyVos9v7EeG5Fk8+HSpHlyUaUB8EcOFsVT7j+lM7sPLJly7IxXU+75Uiwmt/Z0SQtjw+9d1osWrWGyT4vyovgEqYHqfSZgsxvjV/8wYsvpuRcsXj6o5Kyc4Hqd3kkSegCw0UCSriGcze+tUerXSNlZc0btyagPrgCmkNLweSGOD/wJruonYDWNU9AhQTrvKzxwsVLIGnaWYzYkI53d/9tNBQ89c9iAbjE0KsVcCKgCtVMixnY2UKcW1UgW1WuaovLXDi5i33hs+tgYXo+XHzr16DPFMHp8ZjFkVFmfnLtSueuXW+SpCS91bJ4cc8fCjO50UJBdjKGDq1YttLw0LIYiYaFiG1EQhUkWeZ2F3PJXnj4nvWwaLgRLv/+DTGdGea53nOwKFF3sL6+foR6HSadSU9v2HA+uuSuA6fyU5BVitwlSSZVNbnKbhtkY3IYWgzBASOw2zRgwG6HF9rv3yDuzi2xPn6pB+Y7PSe/t2Prz+n0lH+6C6gUoKuz82Ckbdnh3ypFuJ7PcywLU8ZeRJJRRAZVOu7kXEICCRxoYY/bB06n0yyUphnzCH730nve3759WyfhzV44WF4VfLI/EfFX8cq8eunSjpapvK8ZayOM7nJj6RR1BX6TU2BZ63ZoCDdB2VTEjDIFN3PX2JD6Z0jM97/euXHrf12ZcwREOkcCdOl/8PFH2yZupDqCqbRUIzuQwoLeog7Nd3wNQq4IZPMZKHpTEE46+xcvbD20qn3VcQL5f5c+/UYPtRtbO5zbf1s0IVZP5HIP5bLZ5tFMJlAdC5djtZFUIl7TJ3PPOx0rO8iO9t+W/wQnwH8DU6LCKd+DXAkAAAAASUVORK5CYII=" style="display:none"/>
6511 <noscript>Javascript must be enabled to view this page.</noscript>
6512 <div style="display:none">
6513 <krona collapse="true" key="true">
6514 <attributes magnitude="count">
6515 <list>members</list>
6516 <attribute display="Reads" listAll="members">count</attribute>
6517 <attribute display="Unassigned" listNode="members">unassigned</attribute>
6518 <attribute display="Rank" mono="true">rank</attribute>
6519 </attributes>
6520 <datasets>
6521 <dataset>0</dataset>
6522 </datasets>
6523 <node name="Root">
6524 <count><val>100</val></count>
6525 <node name="Eukaryota">
6526 <count><val>94</val></count>
6527 <rank><val>superkingdom</val></rank>
6528 <node name="Metazoa">
6529 <rank><val>kingdom</val></rank>
6530 <count><val>94</val></count>
6531 <node name="Chordata">
6532 <rank><val>phylum</val></rank>
6533 <count><val>94</val></count>
6534 <node name="Craniata">
6535 <count><val>94</val></count>
6536 <rank><val>subphylum</val></rank>
6537 <node name="Gnathostomata">
6538 <count><val>94</val></count>
6539 <rank><val>superclass</val></rank>
6540 <node name="Mammalia">
6541 <rank><val>class</val></rank>
6542 <count><val>94</val></count>
6543 <node name="Euarchontoglires">
6544 <count><val>94</val></count>
6545 <rank><val>superorder</val></rank>
6546 <node name="Rodentia">
6547 <count><val>94</val></count>
6548 <rank><val>order</val></rank>
6549 <node name="Sciurognathi">
6550 <count><val>94</val></count>
6551 <rank><val>suborder</val></rank>
6552 <node name="Muridae">
6553 <count><val>94</val></count>
6554 <rank><val>family</val></rank>
6555 <node name="Murinae">
6556 <rank><val>subfamily</val></rank>
6557 <count><val>94</val></count>
6558 <node name="Rattus">
6559 <rank><val>genus</val></rank>
6560 <count><val>94</val></count>
6561 <members>
6562 <vals><val>IA_1-296315</val><val>IA_1-322295</val></vals>
6563 </members>
6564 <unassigned><val>2</val></unassigned>
6565 <node name="Rattus norvegicus">
6566 <rank><val>species</val></rank>
6567 <count><val>92</val></count>
6568 <members>
6569 <vals><val>IA_1-144417</val><val>IA_1-278966</val><val>IA_1-314709</val><val>IA_1-324951</val><val>IA_1-27817</val><val>IA_1-95255</val><val>IA_1-104173</val><val>IA_1-135979</val><val>IA_1-139090</val><val>IA_1-139090</val><val>IA_1-139090</val><val>IA_1-144996</val><val>IA_1-160446</val><val>IA_1-160446</val><val>IA_1-160446</val><val>IA_1-160446</val><val>IA_1-160446</val><val>IA_1-160446</val><val>IA_1-160446</val><val>IA_1-160446</val><val>IA_1-161439</val><val>IA_1-190855</val><val>IA_1-190855</val><val>IA_1-190855</val><val>IA_1-190855</val><val>IA_1-190855</val><val>IA_1-190855</val><val>IA_1-190855</val><val>IA_1-190855</val><val>IA_1-190855</val><val>IA_1-190855</val><val>IA_1-190855</val><val>IA_1-190855</val><val>IA_1-190855</val><val>IA_1-190855</val><val>IA_1-190855</val><val>IA_1-190855</val><val>IA_1-190855</val><val>IA_1-190855</val><val>IA_1-190855</val><val>IA_1-190855</val><val>IA_1-190855</val><val>IA_1-205154</val><val>IA_1-205154</val><val>IA_1-205154</val><val>IA_1-205154</val><val>IA_1-205154</val><val>IA_1-205154</val><val>IA_1-205154</val><val>IA_1-205154</val><val>IA_1-205154</val><val>IA_1-205154</val><val>IA_1-216231</val><val>IA_1-236286</val><val>IA_1-236286</val><val>IA_1-236286</val><val>IA_1-236286</val><val>IA_1-236286</val><val>IA_1-236286</val><val>IA_1-236286</val><val>IA_1-236286</val><val>IA_1-236286</val><val>IA_1-236286</val><val>IA_1-236286</val><val>IA_1-236286</val><val>IA_1-237681</val><val>IA_1-250166</val><val>IA_1-254274</val><val>IA_1-254274</val><val>IA_1-27817</val><val>IA_1-29000</val><val>IA_1-291427</val><val>IA_1-291427</val><val>IA_1-293054</val><val>IA_1-293054</val><val>IA_1-296315</val><val>IA_1-310974</val><val>IA_1-310974</val><val>IA_1-311282</val><val>IA_1-311282</val><val>IA_1-42600</val><val>IA_1-45102</val><val>IA_1-45102</val><val>IA_1-48105</val><val>IA_1-48105</val><val>IA_1-57254</val><val>IA_1-61975</val><val>IA_1-61975</val><val>IA_1-66943</val><val>IA_1-68288</val><val>IA_1-82334</val><val>IA_1-95526</val></vals>
6570 </members>
6571 </node>
6572 </node>
6573 </node>
6574 </node>
6575 </node>
6576 </node>
6577 </node>
6578 </node>
6579 </node>
6580 </node>
6581 </node>
6582 </node>
6583 </node>
6584 <node name="Bacteria">
6585 <rank><val>superkingdom</val></rank>
6586 <count><val>6</val></count>
6587 <node name="Proteobacteria">
6588 <count><val>6</val></count>
6589 <rank><val>phylum</val></rank>
6590 <node name="Gammaproteobacteria">
6591 <count><val>6</val></count>
6592 <rank><val>class</val></rank>
6593 <node name="Enterobacteriales">
6594 <count><val>6</val></count>
6595 <rank><val>order</val></rank>
6596 <node name="Enterobacteriaceae">
6597 <count><val>6</val></count>
6598 <rank><val>family</val></rank>
6599 <node name="Shigella">
6600 <rank><val>genus</val></rank>
6601 <count><val>6</val></count>
6602 <node name="Shigella flexneri">
6603 <count><val>6</val></count>
6604 <rank><val>species</val></rank>
6605 <members>
6606 <vals><val>IA_1-79371</val><val>IA_1-84488</val><val>IA_1-270826</val><val>IA_1-285361</val><val>IA_1-93958</val><val>IA_1-99821</val></vals>
6607 </members>
6608 </node>
6609 </node>
6610 </node>
6611 </node>
6612 </node>
6613 </node>
6614 </node>
6615 </node>
6616 </krona>
6617 </div></body></html>