comparison test-data/krona_test1.html @ 10:e9005d1f3cfd draft default tip

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