Mercurial > repos > iuc > recentrifuge
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> \ | |
3438 <input type="button" id="detailsExpand" onclick="expand(focusNode);"\ | |
3439 value="↔" 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="←" title="Go back (Shortcut: ←)"/>\ | |
3469 <input type="button" id="forward" value="→" title="Go forward (Shortcut: →)"/> \ | |
3470 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: ↑)" id="prevDataset" type="button"' + | |
3489 ' value="↑" onclick="prevDataset()" disabled="true"/>' + | |
3490 '<input title="Next dataset (Shortcut: ↓)" ' + | |
3491 'id="nextDataset" type="button" value="↓" ' + | |
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 <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 <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 ' <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 ' <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 = '   '; | |
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 '   '; | |
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&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> |