comparison test-data/kraken_test/test1_csv.rcf.html @ 0:09b7b0b2e2c2 draft

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