Mercurial > repos > crs4 > taxonomy_krona_chart
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> \ | |
3561 <input type="button" id="detailsExpand" onclick="expand(focusNode);"\ | |
3562 value="↔" 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="←" title="Go back (Shortcut: ←)"/>\ | |
3590 <input type="button" id="forward" value="→" title="Go forward (Shortcut: →)"/> \ | |
3591 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: ↑)" id="prevDataset" type="button" value="↑" onclick="prevDataset()" disabled="true"/>' + | |
3612 '<input title="Next dataset (Shortcut: ↓)" id="nextDataset" type="button" value="↓" 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 <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 <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 ' <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 ' <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 = '   '; | |
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 '   '; | |
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> |